Serializers¶
drf-commons extends DRF’s serializer system with atomic write handling, dependency-ordered relation writes, and a comprehensive system of configurable relation field types.
BaseModelSerializer¶
from drf_commons.serializers import BaseModelSerializer
The base serializer for all drf-commons projects. Extends
rest_framework.serializers.ModelSerializer with:
Atomic writes — All
create()andupdate()operations are wrapped intransaction.atomic().Relation write ordering — Fields can be designated
root_firstordependency_firstto control the order in which nested relation saves are applied.
Relation write ordering:
class InvoiceSerializer(BaseModelSerializer):
# dependency_first: save the nested object first, then set FK on parent
customer = CustomerSerializer(dependency_first=True)
# root_first: save the parent first, then assign FK on children
line_items = LineItemSerializer(many=True, root_first=True)
class Meta:
model = Invoice
fields = ["id", "number", "customer", "line_items"]
BulkUpdateListSerializer¶
from drf_commons.serializers.base import BulkUpdateListSerializer
The list_serializer_class for BaseModelSerializer. Used automatically
by all bulk update operations.
Behavior:
Validates that the incoming data list length matches the queryset length
Applies audit field defaults (
updated_at,updated_by) when absentIn default mode: issues a single
bulk_update()callIn save mode: calls
instance.save()for each object within an atomic blockRejects deferred nested relation writes in bulk mode (would require N+1 saves)
Configurable Field Types¶
The configurable field system provides a consistent, composable approach to representing foreign key and many-to-many relations in different contexts.
All field names follow the convention: {WriteFormat}To{ReadFormat}Field.
Single Relation Fields¶
Field Class |
Write Accepts |
Read Returns |
Primary Use Case |
|---|---|---|---|
|
Primary key |
Nested serializer output |
Standard API pattern |
|
Primary key |
String ( |
Lightweight references |
|
Nested data |
Primary key |
Accept full payload, return ID |
|
Nested data |
String ( |
Accept nested, return label |
|
Nested data |
Nested serializer output |
Full nested read-write |
|
String (lookup) |
Nested serializer output |
Lookup by name/code |
|
Primary key |
Primary key |
Pure ID relation |
|
String (lookup) |
String ( |
String-keyed relations |
|
ID or string (auto-detected) |
Nested serializer output |
Flexible client contracts |
|
Primary key |
Custom function output |
Computed representations |
Many-to-Many Fields¶
All single relation fields have a Many prefixed variant:
Field Class |
Description |
|---|---|
|
Accept list of IDs, return list of nested data |
|
Accept list of nested data, return list of IDs |
|
Accept list of strings, return list of nested data |
|
Accept and return list of IDs |
|
Accept and return list of strings |
|
Accept mixed IDs/strings, return list of nested data |
Read-Only Fields¶
These fields do not accept write input:
Field Class |
Description |
|---|---|
|
Returns primary key only |
|
Returns |
|
Returns full nested serializer output |
|
Returns custom function output |
Import:
from drf_commons.serializers.fields import (
IdToDataField,
IdToStrField,
DataToIdField,
DataToDataField,
StrToDataField,
IdOnlyField,
StrOnlyField,
FlexibleField,
CustomOutputField,
ManyIdToDataField,
ManyDataToIdField,
ManyIdOnlyField,
ManyFlexibleField,
ReadOnlyIdField,
ReadOnlyStrField,
ReadOnlyDataField,
ReadOnlyCustomField,
)
Field Usage Examples¶
Standard foreign key (write by ID, read nested):
class ArticleSerializer(BaseModelSerializer):
author = IdToDataField(
queryset=User.objects.all(),
serializer=UserSerializer,
)
Many-to-many (write list of IDs, read list of nested data):
class ArticleSerializer(BaseModelSerializer):
tags = ManyIdToDataField(
queryset=Tag.objects.all(),
serializer=TagSerializer,
)
Flexible field (accept ID or string, return nested data):
class OrderSerializer(BaseModelSerializer):
product = FlexibleField(
queryset=Product.objects.all(),
serializer=ProductSerializer,
# Tries to parse as UUID (ID); falls back to string lookup
)
Read-only computed relation:
class UserSerializer(BaseModelSerializer):
department = ReadOnlyDataField(serializer=DepartmentSerializer)
role_label = ReadOnlyStrField() # returns str(instance.role)
Custom output:
class ArticleSerializer(BaseModelSerializer):
author = CustomOutputField(
queryset=User.objects.all(),
output_fn=lambda user: {"id": str(user.id), "display": user.get_full_name()},
)
Building Custom Field Types¶
All field types inherit from
ConfigurableRelatedFieldMixin.
Implementing a custom field type requires subclassing the appropriate base and
implementing the abstract transform methods:
from drf_commons.serializers.fields.base import ConfigurableRelatedField
class UrnToDataField(ConfigurableRelatedField):
"""
Accept a URN string (e.g., "urn:product:uuid-here"), resolve to the
object, return nested serializer data.
"""
def to_internal_value(self, data):
# Parse URN, extract ID, resolve object
_, _, raw_id = data.partition(":")
try:
return self.get_queryset().get(pk=raw_id)
except self.get_queryset().model.DoesNotExist:
self.fail("does_not_exist", pk_value=raw_id)
def to_representation(self, value):
serializer = self.serializer_class(value, context=self.context)
return serializer.data