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:
Rename serializer output fields
Attach serializer function response to data
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']
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
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
Last updated
Was this helpful?