Using the source argument, SerializerMethodField, and to_representation

Akshar Raaj
Jul 23 · 5 min read
Django REST framework serializers

This post assumes a basic familiarity with the Django REST framework.

We will discuss how we can effectively use serializers during read operations. We will look at three powerful features that will help us achieve the desired results with less code.

We will discuss:

  • Multiple ways of using the serializer’s source argument.
  • How and when to use SerializerMethodField.
  • How and when to use to_representation.

How To Use the Source Keyword Argument

The DRF serializer provides a keyword argument, called source, which is extremely smart and can help avoid a lot of common patterns.

Let’s write a serializer that can create serialized representations of a User.

Let’s use this serializer to serialize a user:

In [1]: from accounts.serializers import UserSerializerIn [2]: from django.contrib.auth.models import UserIn [3]: user = User.objects.latest('pk')In [4]: serializer = UserSerializer(user)In [5]: serializer.data
Out[5]: {'username': 'akshar', 'email': 'akshar@agiliq.com', 'first_name': '', 'last_name': ''}

Suppose the front end or mobile app consuming this API wants the key to be user_name instead of username in the serialized representation.

We can add a CharField on the serializer with a source attribute to achieve this.

Ensure that username is replaced with user_name in Meta.fields. Restart the shell and instantiate the serializer.

In [6]: serializer = UserSerializer(user)In [7]: serializer.data
Out[7]: {'user_name': 'akshar', 'email': 'akshar@agiliq.com', 'first_name': '', 'last_name': ''}

In the last example, we saw how source works with a field of User. source can also work with methods on User.

User has a method called get_full_name. Let’s set first_name and last_name.

In [8]: user.first_name = 'akshar'In [9]: user.last_name = 'raaj'In [10]: user.save()In [11]: user.get_full_name()
Out[11]: 'akshar raaj'

Let’s add a field called full_name to the serializer. Set its source to use User.get_full_name.

Let’s restart the shell and get the serialized representation for a user.

In [3]: user = User.objects.latest('pk')In [4]: serializer = UserSerializer(user)In [5]: serializer.data
Out[5]: {'user_name': 'akshar', 'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'full_name': 'akshar raaj'}

Notice how full_name gives the desired result. Under the hood, DRF used get_full_name to populate the full_name.

source can seamlessly work with relationships too, i.e. with ForeignKey, OneToOneField, and ManyToMany.

Assume there is a Profile model which has a OneToOne relationship with User.

The profile model looks like this:

Let’s say we want the street and city sent in serialized representation. We could add a street and city field on the serializer and set the appropriate source.

Let’s restart the shell and serialize the user.

In [4]: user = User.objects.latest('pk')In [5]: serializer = UserSerializer(user)In [6]: serializer.data
Out[6]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'street': 'Pennsylvania Avenue', 'city': 'Washington'}

source also works seamlessly with methods of related objects, similar to how it works with methods of the object.

We want to get the full address of users, accessible using user.profile.get_full_address().

We can set source as profile.get_full_address in such cases.

Serialize the user again:

In [3]: user = User.objects.latest('pk')In [4]: serializer = UserSerializer(user)In [5]: serializer.data
Out[5]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'full_address': 'Pennsylvania Avenue, Washington'}

Let’s see how effortlessly source works with ManyToManyField.

We want to get associated groups of users in the serialized representation too. Let’s add few groups to the user first.

In [12]: g1 = Group.objects.create(name='BBC')In [13]: g2 = Group.objects.create(name='Sony')In [15]: user.groups.add(*[g1, g2])

We want the id and name for each group associated with the user.

We will need to write a GroupSerializer which can serialize a group instance.

It would look like this:

A naive way to add groups to serialized data would be to define a SerializerMethodField and add user.groups.all() in the method.

A DRFish way is to add an all_groups field on the serializer and set it to GroupSerializer.

Let’s serialize a user and verify that the associated group’s information is present in serialized data.

In [1]: from accounts.serializers import UserSerializerIn [2]: from django.contrib.auth.models import UserIn [3]: user = User.objects.latest('pk')In [5]: serializer = UserSerializer(user)In [6]: serializer.data
Out[6]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'groups': [OrderedDict([('id', 2), ('name', 'BBC')]), OrderedDict([('id', 3), ('name', 'Sony')])]}

DRF is smart enough to call user.groups.all(), even though we just set source=groups. DRF infers that groups is a ManyRelatedManager and thus calls .all() on the manager to get all the associated groups.

If we don’t want to provide a group’s information during POST calls, we would have to add the keyword argument read_only=True on GroupSerializer.

Assume there is a model called Article, and Article has a ForeignKey to User. We can add articles of a user in the serialized representation with:

articles = ArticleSerializer(source='article_set', many=True)

How To Use SerializerMethodField

There will be times when you need to run some custom code during serialization of a particular field.

Some examples could be:

  • Convert first_name to titlecase during serialization.
  • Convert full_name to uppercase.
  • Set groups as None instead of an empty list if no groups are associated with the user.

Let’s consider the first scenario. We want to change the user’s first_name to title case during serialization.

Untill now, we didn’t want to run any custom code for first_name and so having first_name in the Meta.fields was sufficient. We want to run some custom code now so we will have to explicitly set first_name as a SerializerMethodField.

When a field is set as SerializerMethodField, DRF calls a method called get_<field_name>while computing the value for this field. obj, here, refers to a user instance.

Restart the shell and serialize a user.

In [4]: user = User.objects.latest('pk')In [5]: serializer = UserSerializer(user)In [6]: serializer.data
Out[6]: {'email': 'akshar@agiliq.com', 'first_name': 'Akshar', 'last_name': 'raaj'}

Notice how first_name is serialized to title case now.

If we want to change full_name to uppercase, we would have to change the serializer to look like:

Let’s serialize the user again:

In [3]: user = User.objects.latest('pk')In [4]: serializer = UserSerializer(user)In [5]: serializer.data
Out[5]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'full_name': 'AKSHAR RAAJ'}

If we want to send groups as None instead of an empty list, our serializer would look like:


How To Use to_representation

Serializers provide a hook point, called to_representation.

Suppose we want to add a key called admin to serialized data, only when the user is a superuser. For non-superusers, the key admin shouldn’t be present in serialized data.

Our serializer would look like:

instance refers to the user instance serialized.

Let’s serialize a superuser.

In [2]: user = User.objects.latest('pk')In [5]: serializer = UserSerializer(user)In [6]: serializer.data
Out[6]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj', 'admin': True}

Let’s mark the user as non-superuser and serialize again.

In [7]: user.is_superuser = FalseIn [8]: user.save()In [9]: serializer = UserSerializer(user)In [10]: serializer.data
Out[10]: {'email': 'akshar@agiliq.com', 'first_name': 'akshar', 'last_name': 'raaj'}

Notice that the admin key is missing in the serialized data for a non-superuser.


Conclusion

This post covered the serializer’s read behavior. If you want to understand how to effectively use serializers during write operations, see this article.

Better Programming

Advice for programmers.

Akshar Raaj

Written by

Engineer | Open Source | Blogger | Speaker. My posts have been read over a million times. https://www.agiliq.com/authors/#Akshar https://bit.ly/2TaRqji

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade