docs
  • Overview
  • 🐍 PYTHON
    • Type Hints
    • PEP8 Style Guide for Python Code
    • 🏡Pipenv
    • Pathlib
  • 🕸Django
    • 🗄models
      • 🎯Best Practices
      • 🚦Django Signals
    • ⚙️ settings
    • DRF
      • Serializer
      • Authentication
      • Permissions
      • Viewsets
    • Testing
      • Faker and Factory Boy
    • 🧪Test Coverage
    • 💦Python-Decouple
    • Django Tips:
    • 💾Django ORM Queryset
    • Custom Exceptions
    • Celery
    • Resources
  • Deploy
    • 🚀Django Deployment
    • 🔒Setup SSL Certificate
  • 💾Database
    • MongoDB
  • 🛠️DevOps
    • 🖥Scripting
      • A First Script
      • Loops
      • Test
      • Variables
      • External programs
      • Functions
    • Command Line Shortcuts
    • Basic Linux Commands
    • 🎛Microservices
    • 🐳Docker
      • Docker Commands
      • Docker Compose
      • Django project
    • Kubernates
  • 📝Software IDE
    • EditorConfig
    • Linters
    • VsCode
Powered by GitBook
On this page
  • Custom Data Validation
  • Custom Outputs
  • Serializer Save
  • Serializer Context
  • Source Keyword
  • SerializerMethodField
  • Different Read and Write Serializers
  • Read-only Fields
  • Nested Serializers
  • Summary

Was this helpful?

  1. 🕸Django
  2. DRF

Serializer

Effectively using DRF serializers

How to use Django REST Framework (DRF) serializers more efficiently and effectively.

Custom Data Validation

DRF enforces data validation in the deserialization process, which is why we need to call is_valid() before accessing the validated data. if the data is invalid, errors are then appended to the serializer's error property and a ValidationError is thrown. There are two types of custom data validators: 1. Custom field 2. Object-level

Custom field validation

Custom field validation allows us to validate a specific field. We can use it by adding the validate<fieldname> method to our serializer like so:

from rest_framework import serializers
from examples.models import Movie


class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

    def validate_rating(self, value):
        if value < 1 or value > 10:
            raise serializers.ValidationError('Rating has to be between 1 and 10.')
        return value

Object-level validation

Sometimes we'll have to compare fields with one another in order to validate them. This is when we should use the object-level validation approach.

from rest_framework import serializers
from examples.models import Movie


class MovieSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = '__all__'

    def validate(self, data):
        if data['us_gross'] > data['worldwide_gross']:
            raise serializers.ValidationError('worldwide_gross cannot be bigger than us_gross')
        return data

We should avoid accessing additional fields in the custom field validator.

Function Validators

If we use the same validator in multiple serializers, we can create a function validator instead of writing the same code over and over again.

def is_rating(value):
    if value < 1:
        raise serializers.ValidationError('Value cannot be lower than 1.')
    elif value > 10:
        raise serializers.ValidationError('Value cannot be higher than 10')
        
from rest_framework import serializers
from examples.models import Movie


class MovieSerializer(serializers.ModelSerializer):
    rating = IntegerField(validators=[is_rating])
    ...

Custom Outputs

Two of the most useful functions inside the BaseSerializer class that we can override are to_representation() and to_internal_value() . By overriding them, we can change the serialization and deserialization behavior, respectively, to append additional data, extract data, and handle relationships. 1. to_representations() allows us to change the serialization output 2. to_internal_value() allows us to change the deserialization output

to_representation()

Now, let's say we want to add a total likes count to the serialized data.

from rest_framework import serializers
from examples.models import Resource


class ResourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Resource
        fields = '__all__'

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['likes'] = instance.liked_by.count()

        return representatio

to_internal_value()

Suppose the services that use our API appends unnecessary data to the endpoint when creating resources:

{
   "info": {
       "extra": "data",
       ...
   },
   "resource": {
      "id": 1,
      "title": "C++ with examples",
      "content": "This is the resource's content.",
      "liked_by": [
         2,
         3
      ],
      "likes": 2
   }
}
from rest_framework import serializers
from examples.models import Resource


class ResourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Resource
        fields = '__all__'

    def to_internal_value(self, data):
        resource_data = data['resource']

        return super().to_internal_value(resource_data)

Serializer Save

Calling save() will either create a new instance or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:

# this creates a new instance
serializer = MySerializer(data=data)

# this updates an existing instance
serializer = MySerializer(instance, data=data)

Passing data directly to save

Sometimes we'll want to pass additional data at the point of saving the instance. This additional data might include information like the current user, the current time, or request data. We can do so by including additional keyword arguments when calling save() .

# Keep in mind that values passed to save() won't be validated.

serializer.save(owner=request.user)

Serializer Context

There are some cases when we need to pass additional data to our serializers. we can do that by using the serializer context property. We can then use this data inside the serializer such as to_representation or when validating data. We can pass data as a dictionary via the context keyword:

from rest_framework import serializers
from examples.models import Resource

resource = Resource.objects.get(id=1)
serializer = ResourceSerializer(resource, context={'key': 'value'})


class ResourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Resource
        fields = '__all__'

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['key'] = self.context['key']

        return representation

Source Keyword

The DRF serializer comes with the source keyword, which is extremely powerful and can be used in multiple case scenarios. We can use it to:

  1. Rename serializer output fields

  2. Attach serializer function response to data

  3. Fetch data from one-to-one models

Rename serializer output fields

To rename a serializer output field we need to add a new field to our serializer and pass it to fields property.

class UserSerializer(serializers.ModelSerializer):
    # is_active field will be name as active
    active = serializers.BooleanField(source='is_active')

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'is_staff', 'active']

Attach Serializer function response to data

We can use source to add a field that equals to function's return.

class UserSerializer(serializers.ModelSerializer):
    full_name = serializers.CharField(source='get_full_name')

    class Meta:
        model = User
        fields = ['id', 'username', 'full_name', 'email', 'is_staff', 'active']

get_full_name() is a method from the Django User model that concatenates user.first_name and user.last_name .

Append data from one-to-one models

Now let's suppose we also wanted to include our user's bio and birth_date in UserSerializer . We can do that by adding extra fields to our serializer with the source keyword.

class UserSerializer(serializers.ModelSerializer):
    bio = serializers.CharField(source='userprofile.bio')
    birth_date = serializers.DateField(source='userprofile.birth_date')

    class Meta:
        model = User
        fields = [
            'id', 'username', 'email', 'is_staff',
            'is_active', 'bio', 'birth_date'
        ]  # note we also added the new fields here

SerializerMethodField

SerializerMethodField is a read-only field, which gets its value by calling a method on the serializer class that it is attached to. SerializerMethodFeild gets its data by calling get_<field_name> .

from django.contrib.auth.models import User
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    full_name = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = '__all__'

    def get_full_name(self, obj):
        return f'{obj.first_name} {obj.last_name}'

Different Read and Write Serializers

If our serializers contain a lot of nested data, which is not required for write operations, we can boost our API performance by creating separate read and write serializers. We do that by overriding the get_serializer_class() method in our ViewSet like so:

from rest_framework import viewsets

from .models import MyModel
from .serializers import MyModelWriteSerializer, MyModelReadSerializer


class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()

    def get_serializer_class(self):
        if self.action in ["create", "update", "partial_update", "destroy"]:
            return MyModelWriteSerializer

        return MyModelReadSerializer

Read-only Fields

Serializer fields come with the read_only option. By setting it to True , DRF includes the field in the API output, but ignores it during create and update operations:

from rest_framework import serializers


class AccountSerializer(serializers.Serializer):
    id = IntegerField(label='ID', read_only=True)
    username = CharField(max_length=32, required=True)

If we want to set multiple fields to read_only we can specify them using read_only_fields in Meta like so:

from rest_framework import serializers


class AccountSerializer(serializers.Serializer):
    id = IntegerField(label='ID')
    username = CharField(max_length=32, required=True)

    class Meta:
        read_only_fields = ['id', 'username']

Settings fields like id , created_date , etc to read-only will give us a performance boost during write operations.

Nested Serializers

There are two different ways of handling nested serialization with ModelSerializer : 1. Explicit definition

2. Using the depth field

Explicit definition

The explicit definition works by passing an external Serializer as a field to our main serializer.

from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username']


class CommentSerializer(serializers.ModelSerializer):
    author = UserSerializer()

    class Meta:
        model = Comment
        fields = '__all__'

Using the depth field

When it comes to nested serialization, the depth field is one of the most powerful features.

from rest_framework import serializers


class ModelASerializer(serializers.ModelSerializer):
    class Meta:
        model = ModelA
        fields = '__all__'
        depth = 2

# Output 
{
    "id": 1,
    "content": "A content",
    "model_b": {
        "id": 1,
        "content": "B content",
        "model_c": {
            "id": 1,
            "content": "C content"
        }
    }
}

The downside is that we have no control over a child's serializers. Using depth will include all fields obn the children.

Summary

Method

Validating data at the field level

validate<fieldname>

Validating data at the object level

validate

Customizing the serialization and deserialization output

to_representation, to_internal_value

Passing additional data at save

serializer.save(field=value)

Passing context to serializers

SampleSerializer(resource, context={'key': value})

Renaming serializer output fields

source keyword

Attaching serializer function responses to data

source keyword

Fetching data from one-to-one models

source keyword

Attaching data to the serialized output

SerializerMethodField

Creating Separate read and write serializers

get_serializer_class()

Setting read-only fields

read_only_fields

Handling nested serialization

depth field

PreviousDRFNextAuthentication

Last updated 2 years ago

Was this helpful?