The serializer mixin
To activate the serializer extensions, apply the SerializerExtensionsMixin
class to your serializers:
# serializers.py
from rest_framework.serializers import ModelSerializer
from rest_framework_serializer_extensions.serializers import SerializerExtensionsMixin
class OwnerSerializer(SerializerExtensionsMixin, ModelSerializer):
...
This enables the "only" and "exclude" features, which allow you to whitelist/blacklist fields as required, and provides a few helper methods.
Creating expandable fields
One of the core aims of this project is to reduce the need to create multiple serializers to represent a single model. The expandable fields feature is probably the most significant way in which this aim can be achieved. Imagine you've defined the following serializers:
# old_serializers.py
class OwnerSerializer(ModelSerializer):
class Meta:
model = models.Owner
fields = ('id', 'name')
class OwnerWithOrganizationSerializer(ModelSerializer):
organization = OrganizationSerializer()
class Meta:
model = models.Owner
fields = ('id', 'name', 'organization')
class OwnerWithCarsSerializer(ModelSerializer):
cars = SkuSerializer(many=True)
class Meta:
model = models.Owner
fields = ('id', 'name', 'cars')
As your API grows you may find situations in which you need to serialize an instance with some of it's foreign relations, and situations where you don't. For efficiency reasons you end up creating multiple serializers, each with a very specific task. This can quickly grow out of hand, and lead to inconsistencies.
We can solve this by using expandable fields. Here's how we could modify the serializers above to advantage of them:
class OwnerSerializer(SerializerExtensionsMixin, ModelSerializer):
class Meta:
model = models.Owner
fields = ('id', 'name')
expandable_fields = dict(
organization=OrganizationSerializer,
cars=dict(
serializer=SkuSerializer,
many=True
)
)
Our one serializer now handles all 3 use cases. If we want to serialize an owner along with their organization, their cars, or both, we can do so.
Single child expansion
A serialized owner instance will now look something like:
{
"id": 1,
"name": "Tyrell",
"organization_id": 1
}
The expandable fields mixin automatically adds an ID reference to the output, mimicing the ForeignKey API of Django models. This consistency provides users of your API with just enough information to make further queries if required, whilst maintaining the efficiency of your serializer.
Your child serializer can now be expanded, to produce:
{
"id": 1,
"name": "Tyrell",
"organization_id": 1,
"organization": {
"id": 1,
"name": "E Corp"
}
}
OneToOne Reverse ForeignKeys
You may want to create an expandable field for a OneToOne relationship where
the relation is stored on the other instance's table. Here, no _id
field
will be present on your model, and so the only way to retrieve the ID is to
perform an additional database query (or use a select_related()
join).
In these situations, you can use the id_source
property in your expandable
field definition to determine what happens:
# models.py
class Owner(models.Model):
...
class OwnerBio(models.Model):
owner = models.OneToOneField(Owner)
...
# serializers.py
class OwnerSerializer(SerializerExtensionsMixin, ModelSerializer):
class Meta:
...
expandable_fields = dict(
bio=dict(
serializer=OwnerBioSerializer,
id_source='bio.pk'
)
Setting id_source=False
results in no ID field being included.
Writable ForeignKey relationships
In some cases it may be appropriate to allow ForeignKey fields to be set during
a create or an update via your API. To create a ForeignKey relation that is
both expandable and writable, set read_only=False
in its definition.
# serializers.py
class OwnerSerializer(SerializerExtensionsMixin, ModelSerializer):
class Meta:
model = models.Owner
fields = ('id', 'name')
expandable_fields = dict(
organization=dict(
serializer=OrganizationSerializer,
read_only=False
)
)
As usual, the above serializer will have an organization_id
field by default.
To set or alter the organization of an owner, pass a value to this field:
>>> POST /owner/2/
{
"name": "Elliot",
"organization_id": 3
}
It is important to note that we are passing an organization's ID to the
organization_id
field, not a dictionary of properties to the organization
field.
During validation the serializer will find the corresponding organization and
add make it available in the deserialized data under the key
organization_id_resolved
:
{
"name": "Elliot",
"organization_id": 2,
"organization_id_resolved": <Organization: Allsafe>
}
Non-Model relations
Any child serializer can be expanded, not just Django model relations. A common use case for expanding fields is to avoid serializing unnecessary, perhaps time-consuming to compute, fields.
Multiple child expansion
As highlighted in our first example at the top of this section, you can also
expand *-to-many relationships. You may have noticed that by default however,
no IDs are provided, as doing so would necessarily require a further database
query (or a prefetch_related()
on the initial queryset). Instead, many
relationships must be expanded explicitly, resulting in something like:
{
"id": 1,
"name": "Tyrell",
"organization_id": 1,
"cars": [
{
"id": 1,
"variant": "P100D",
"model_id": 1
}
]
}
ID-only expansion
For many relationships, the option to expand by ID only is also provided. In the previous example, this would yield:
{
"id": 1,
"name": "Tyrell",
"organization_id": 1,
"cars": [1]
}
Custom expansion
In certain situations you may wish to optionally serialize a complex or
computed value. This can be achieved by using a SerializerMethodField
:
class OwnerSerializer(SerializerExtensionsMixin, ModelSerializer):
class Meta:
model = models.Owner
fields = ('id', 'name')
expandable_fields = dict(
status=serializers.SerializerMethodField,,
bio=serializer=SerializerMethodField
)
def get_status(self, owner):
# Complicated computations...
return True
def get_bio(self, owner):
# See full API documentation for more
if owner.show_bio:
return self.represent_child(
name='bio',
serializer=OwnerBioSerializer,
instance=owner.bio
)
Nested expansion
You can expand fields on child serializers too (provided they also take
advantage of the SerializerExtensionsMixin
). By doing so, we can achieve
something like the following:
{
"id": 1,
"name": "Tyrell",
"organization_id": 1,
"cars": [
{
"id": 1,
"variant": "P100D",
"model_id": 1,
"model": {
"id": 1,
"name": "Model S",
"manufacturer_id": 1,
"manufacturer": {
"id": 1,
"name": "Telsa"
}
}
}
}
]
}
It's all in the context
Now that you've redesigned your serializers, you're probably going to want to
take advantage of the extra features. Our serialized data depends
on the context passed to our serializer (in particular, the
only
, exclude
, expand
and expand_id_only
iterables). For this, you'll
need to make a few changes to your API views.