Usage

This page focuses on day-to-day endpoint behavior. For setting-level controls, see Advanced Topics. For option syntax and constructor-time usage, see Serializer Options.

Dynamic Field Expansion

To define expandable fields, add an expandable_fields dictionary to the serializer’s Meta class. Each key is the field name to expand dynamically. Each value is either a serializer class or a tuple of serializer class plus serializer keyword arguments. Related option combinations are documented in Serializer Options.

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class CountrySerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Country
        fields = ["name", "population"]


class PersonSerializer(FlexFieldsModelSerializer):
    country = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Person
        fields = ["id", "name", "country", "occupation"]
        expandable_fields = {
            "country": CountrySerializer,
        }

Default response:

{
    "id": 13322,
    "name": "John Doe",
    "country": 12,
    "occupation": "Programmer"
}

Expanded response for GET /person/13322?expand=country:

{
    "id": 13322,
    "name": "John Doe",
    "country": {
        "name": "United States",
        "population": 330000000
    },
    "occupation": "Programmer"
}

Deferred Fields

You can treat a relation as deferred by omitting it from the default field list and defining it only in expandable_fields. The field then appears only when explicitly expanded.

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class TeamSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Team
        fields = ["id", "name"]


class MemberSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Member
        fields = ["id", "email"]
        expandable_fields = {
            "team": TeamSerializer,
        }

Default response for GET /members/87/:

{
    "id": 87,
    "email": "sam@example.com"
}

Expanded response for GET /members/87/?expand=team:

{
    "id": 87,
    "email": "sam@example.com",
    "team": {
        "id": 11,
        "name": "API Platform"
    }
}

Deep Nested Expansion

This example demonstrates a two-level expansion: first country on PersonSerializer, then states on CountrySerializer. The resulting request expand=country.states returns a person payload with nested country and state details.

Nested expansions use dot notation:

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class StateSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = State
        fields = ["name", "population"]


class CountrySerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Country
        fields = ["name", "population"]
        expandable_fields = {
            # Expand states only when the client asks for them.
            "states": (StateSerializer, {"many": True}),
        }


class PersonSerializer(FlexFieldsModelSerializer):
    country = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Person
        fields = ["id", "name", "country", "occupation"]
        expandable_fields = {
            "country": CountrySerializer,
        }

Request:

GET /person/13322?expand=country.states

Response:

{
    "id": 13322,
    "name": "John Doe",
    "occupation": "Programmer",
    "country": {
        "id": 12,
        "name": "United States",
        "states": [
            {
                "name": "Ohio",
                "population": 11000000
            }
        ]
    }
}

Warning

Be deliberate with nested expansions on large result sets. They can increase query count substantially unless you pair them with select_related() or prefetch_related().

Expansion on List Views

Subclass FlexFieldsModelViewSet when you want to limit which fields may be expanded on list endpoints. This is useful when list endpoints would otherwise cause expensive relation loading for large result sets.

from rest_flex_fields2.utils import is_expanded
from rest_flex_fields2.views import FlexFieldsModelViewSet


class PersonViewSet(FlexFieldsModelViewSet):
    permit_list_expands = ["employer"]
    serializer_class = PersonSerializer

    def get_queryset(self):
        queryset = models.Person.objects.all()

        if is_expanded(self.request, "employer"):
            queryset = queryset.select_related("employer")

        return queryset

permit_list_expands is applied only for the list action. The view passes the allowed values through context["permitted_expands"] so the serializer can reject disallowed list-time expansions while still allowing detail-time expansions.

Expanding a To-Many Relationship

Set many=True in the serializer options when the expanded relation returns multiple objects.

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class CountrySerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Country
        fields = ["name", "population"]
        expandable_fields = {
            "states": (StateSerializer, {"many": True}),
        }

Lazy Serializer References

To avoid circular imports, reference a serializer lazily by dotted path:

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class OwnerSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Owner
        fields = ["id", "name"]


class RecordSerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Record
        fields = ["id", "title", "owner"]
        expandable_fields = {
            "owner": "accounts.api.serializers.UserSerializer",
            "record_set": (
                "records.api.serializers.RelatedSerializer",
                {"many": True},
            ),
        }

Fully qualified import paths are supported. Legacy <app>.serializers.SerializerName paths still work as well.

Use lazy references when two serializers would otherwise import each other.

Using FlexFieldsSerializerMixin with a Custom Base

If your project already has a custom serializer base class, use FlexFieldsSerializerMixin directly.

from rest_framework import serializers
from rest_flex_fields2.serializers import FlexFieldsSerializerMixin


class BaseAPISerializer(serializers.ModelSerializer):
    class Meta:
        abstract = True


class AccountSerializer(FlexFieldsSerializerMixin, BaseAPISerializer):
    class Meta:
        model = Account
        fields = ["id", "name", "owner"]
        expandable_fields = {
            "owner": "accounts.api.serializers.UserSerializer",
        }

Serializer Reuse

fields and omit can also be passed directly to nested serializer instances, which helps avoid maintaining multiple slightly different serializers.

from rest_flex_fields2.serializers import FlexFieldsModelSerializer


class CountrySerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Country
        fields = ["id", "name", "population", "capital", "square_miles"]


class PersonSerializer(FlexFieldsModelSerializer):
    country = CountrySerializer(fields=["id", "name"])

    class Meta:
        model = Person
        fields = ["id", "name", "country"]