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 which are only very slightly different. 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 might find situations in which one instance needs to be serialized with a foreign relation, and others in which it doesn't. For efficiency reasons you end up creating multiple serializers, each with a very specific task, but this can quickly grow out of hand.

We can solve this by using expandable fields. Here's how the serializers above could be unified by taking 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,
                source='cars.all'
            )
        )

Our one serializer now handles all 3 use cases. If we want to serialize the owner 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 provides users of your API with the minimum information to make further queries about the relation 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 using 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.

Non-Model relations

Any child serializer can be expanded, not just Django model relations. A common use case for expanding fields may also be to avoid the large overhead from the serialized result, or to avoid unnecessarily computing time-consuming 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 by default, 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
            )

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 now 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.