Advanced usage examples

Contents:

Nested serializers

Unlimited nesting depth

Our imaginary Author model consist of the following (non-relational) Django model fields:

  • salutation: CharField

  • name: CharField

  • email: EmailField

  • birth_date: DateField

  • biography: TextField

  • phone_number: CharField

  • website: URLField

  • company: CharField

  • company_phone_number: CharField

  • company_email: EmailField

  • company_website: URLField

In our REST API, we split the Author serializer into parts using nested serializers to have the following structure:

{
    "id": "",
    "salutation": "",
    "name": "",
    "birth_date": "",
    "biography": "",
    "contact_information": {
        "personal_contact_information": {
            "email": "",
            "phone_number": "",
            "website": ""
        },
        "business_contact_information": {
            "company": "",
            "company_email": "",
            "company_phone_number": "",
            "company_website": ""
        }
    }
}

Sample models

The only variation from standard implementation here is that we declare two NestedProxyField fields on the Author model level for to be used in AuthorSerializer serializer.

Note, that the change does not cause model change (no migrations or whatsoever).

Required imports
from django.db import models

from rest_framework_tricks.models.fields import NestedProxyField
Model definition
class Author(models.Model):
    """Author."""

    salutation = models.CharField(max_length=10)
    name = models.CharField(max_length=200)
    email = models.EmailField()
    birth_date = models.DateField(null=True, blank=True)
    biography = models.TextField(null=True, blank=True)
    phone_number = models.CharField(max_length=200, null=True, blank=True)
    website = models.URLField(null=True, blank=True)
    company = models.CharField(max_length=200, null=True, blank=True)
    company_phone_number = models.CharField(max_length=200,
                                            null=True,
                                            blank=True)
    company_email = models.EmailField(null=True, blank=True)
    company_website = models.URLField(null=True, blank=True)

    # List the fields for `PersonalContactInformationSerializer` nested
    # serializer. This does not cause a model change.
    personal_contact_information = NestedProxyField(
        'email',
        'phone_number',
        'website',
    )

    # List the fields for `BusinessContactInformationSerializer` nested
    # serializer. This does not cause a model change.
    business_contact_information = NestedProxyField(
        'company',
        'company_email',
        'company_phone_number',
        'company_website',
    )

    # List the fields for `ContactInformationSerializer` nested
    # serializer. This does not cause a model change.
    contact_information = NestedProxyField(
        'personal_contact_information',
        'business_contact_information',
    )

    class Meta(object):
        """Meta options."""

        ordering = ["id"]

    def __str__(self):
        return self.name

Alternatively, you could rewrite the contact_information definition as follows (although at the moment it’s not the recommended approach):

# ...
# List the fields for `ContactInformationSerializer` nested
# serializer. This does not cause a model change.
contact_information = NestedProxyField(
    {
        'personal_contact_information': (
            'email',
            'phone_number',
            'website',
        )
    },
    {
        'business_contact_information': (
            'company',
            'company_email',
            'company_phone_number',
            'company_website',
        )
    },
)
# ...

Sample serializers

At first, we add nested_proxy_field property to the Meta class definitions of PersonalContactInformationSerializer, BusinessContactInformationSerializer and ContactInformationSerializer nested serializers.

Then we define our (main) AuthorSerializer class, which is going to be used a serializer_class of the AuthorViewSet. We inherit the AuthorSerializer from rest_framework_tricks.serializers.HyperlinkedModelSerializer instead of the one of the Django REST framework. There’s also a rest_framework_tricks.serializers.ModelSerializer available.

Required imports
from rest_framework import serializers
from rest_framework_tricks.serializers import (
    HyperlinkedModelSerializer,
    ModelSerializer,
)
Serializer definition

Note

If you get validation errors about null-values, add allow_null=True next to the required=False for serializer field definitions.

Nested serializer for `ContactInformationSerializer` nested serializer

class PersonalContactInformationSerializer(serializers.ModelSerializer):
    """Personal contact information serializer."""

    class Meta(object):
        """Meta options."""

        model = Author
        fields = (
            'email',
            'phone_number',
            'website',
        )
        nested_proxy_field = True

Nested serializer for `ContactInformationSerializer` nested serializer

class BusinessContactInformationSerializer(serializers.ModelSerializer):
    """Business contact information serializer."""

    class Meta(object):
        """Meta options."""

        model = Author
        fields = (
            'company',
            'company_email',
            'company_phone_number',
            'company_website',
        )
        nested_proxy_field = True

Nested serializer for `AuthorSerializer` (main) serializer

class ContactInformationSerializer(serializers.ModelSerializer):
    """Contact information serializer."""

    personal_contact_information = PersonalContactInformationSerializer(
        required=False
    )
    business_contact_information = BusinessContactInformationSerializer(
        required=False
    )

    class Meta(object):
        """Meta options."""

        model = Author
        fields = (
            'personal_contact_information',
            'business_contact_information',
        )
        nested_proxy_field = True

Main serializer to be used in the ViewSet

class AuthorSerializer(ModelSerializer):
    """Author serializer."""

    contact_information = ContactInformationSerializer(required=False)

    class Meta(object):
        """Meta options."""

        model = Author
        fields = (
            'id',
            'salutation',
            'name',
            'birth_date',
            'biography',
            'contact_information',
        )
If you can’t make use of rest_framework_tricks serializers

If somehow you can’t make use of the rest_framework_tricks.serializers.ModelSerializer or rest_framework_tricks.serializers.HyperlinkedModelSerializer serializers, there are handy functions to help you to make your serializer to work with NestedProxyField.

See the following example:

Required imports
from rest_framework import serializers
from rest_framework_tricks.serializers.nested_proxy import (
    extract_nested_serializers,
    set_instance_values,
)
Serializer definition
class BookSerializer(serializers.ModelSerializer):
    """BookSerializer."""

    # ...

    def create(self, validated_data):
        """Create.

        :param validated_data:
        :return:
        """
        # Collect information on nested serializers
        __nested_serializers, __nested_serializers_data = \
            extract_nested_serializers(
                self,
                validated_data,
            )

        # Create instance, but don't save it yet
        instance = self.Meta.model(**validated_data)

        # Assign fields to the `instance` one by one
        set_instance_values(
            __nested_serializers,
            __nested_serializers_data,
            instance
        )

        # Save the instance and return
        instance.save()
        return instance

    def update(self, instance, validated_data):
        """Update.

        :param instance:
        :param validated_data:
        :return:
        """
        # Collect information on nested serializers
        __nested_serializers, __nested_serializers_data = \
            extract_nested_serializers(
                self,
                validated_data,
            )

        # Update the instance
        instance = super(ModelSerializer, self).update(
            instance,
            validated_data
        )

        # Assign fields to the `instance` one by one
        set_instance_values(
            __nested_serializers,
            __nested_serializers_data,
            instance
        )

        # Save the instance and return
        instance.save()
        return instance

Sample ViewSet

Absolutely no variations from standard implementation here.

Required imports
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import AllowAny

from .models import Author
from .serializers import AuthorSerializer
ViewSet definition
class AuthorViewSet(ModelViewSet):
    """Author ViewSet."""

    queryset = Author.objects.all()
    serializer_class = AuthorSerializer
    permission_classes = [AllowAny]

Sample URLs/router definition

Absolutely no variations from standard implementation here.

Required imports
from django.conf.urls import url, include

from rest_framework_extensions.routers import ExtendedDefaultRouter

from .viewsets import AuthorViewSet
ViewSet definition
router = ExtendedDefaultRouter()
authors = router.register(r'authors',
                          AuthorViewSet,
                          base_name='author')

urlpatterns = [
    url(r'^api/', include(router.urls)),
]

Sample OPTIONS call

OPTIONS /books/api/authors/
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "name": "Author List",
    "description": "Author ViewSet.",
    "renders": [
        "application/json",
        "text/html"
    ],
    "parses": [
        "application/json",
        "application/x-www-form-urlencoded",
        "multipart/form-data"
    ],
    "actions": {
        "POST": {
            "id": {
                "type": "integer",
                "required": false,
                "read_only": true,
                "label": "ID"
            },
            "salutation": {
                "type": "string",
                "required": true,
                "read_only": false,
                "label": "Salutation",
                "max_length": 10
            },
            "name": {
                "type": "string",
                "required": true,
                "read_only": false,
                "label": "Name",
                "max_length": 200
            },
            "birth_date": {
                "type": "date",
                "required": false,
                "read_only": false,
                "label": "Birth date"
            },
            "biography": {
                "type": "string",
                "required": false,
                "read_only": false,
                "label": "Biography"
            },
            "contact_information": {
                "type": "nested object",
                "required": false,
                "read_only": false,
                "label": "Contact information",
                "children": {
                    "personal_contact_information": {
                        "type": "nested object",
                        "required": false,
                        "read_only": false,
                        "label": "Personal contact information",
                        "children": {
                            "email": {
                                "type": "email",
                                "required": true,
                                "read_only": false,
                                "label": "Email",
                                "max_length": 254
                            },
                            "phone_number": {
                                "type": "string",
                                "required": false,
                                "read_only": false,
                                "label": "Phone number",
                                "max_length": 200
                            },
                            "website": {
                                "type": "url",
                                "required": false,
                                "read_only": false,
                                "label": "Website",
                                "max_length": 200
                            }
                        }
                    },
                    "business_contact_information": {
                        "type": "nested object",
                        "required": false,
                        "read_only": false,
                        "label": "Business contact information",
                        "children": {
                            "company": {
                                "type": "string",
                                "required": false,
                                "read_only": false,
                                "label": "Company",
                                "max_length": 200
                            },
                            "company_email": {
                                "type": "email",
                                "required": false,
                                "read_only": false,
                                "label": "Company email",
                                "max_length": 254
                            },
                            "company_phone_number": {
                                "type": "string",
                                "required": false,
                                "read_only": false,
                                "label": "Company phone number",
                                "max_length": 200
                            },
                            "company_website": {
                                "type": "url",
                                "required": false,
                                "read_only": false,
                                "label": "Company website",
                                "max_length": 200
                            }
                        }
                    }
                }
            }
        }
    }
}

Sample POST call

POST /books/api/authors/
HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "salutation": "At eve",
    "name": "Shana Rodriquez",
    "birth_date": "2016-04-05",
    "biography": "Commodi facere voluptate ipsum veniam maxime obcaecati",
    "contact_information": {
        "personal_contact_information": {
            "email": "somasesu@yahoo.com",
            "phone_number": "+386-36-3715907",
            "website": "http://www.xazyvufugasi.biz"
        },
        "business_contact_information": {
            "company": "Hopkins and Mccoy Co",
            "company_email": "vevuciqa@yahoo.com",
            "company_phone_number": "+386-35-5689443",
            "company_website": "http://www.xifyhefiqom.com.au"
        }
    }
}