Django Rest Framework general model serializer

In this post I am going to show you a way of creating a general model serializer. If you’re working with Django Rest Framework this can come in more than handy as it can handle any model object.

Introduction

In Django Rest Framework creating a basic serializer class for a model looks like this:

from rest_framework import serializers from models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User

If you are working with an activity stream like django-activity-stream probably you will have an endpoint (GET /feed/) that has to return an activity feed. The serializer will look like this:

class FeedSerializer(serializers.ModelSerializer): class Meta: model = Action

In this case the APIs json response would look like this:

[{ id: 1 actor_content_type: 22 actor_object_id: "3" verb: "report" description: null target_content_type: 46 target_object_id: "4" action_object_content_type: 30 action_object_object_id: "1" timestamp: "2014-12-11T11:55:11.774Z" public: true }]

The problems

As you can see we got the content types and object ids. The first problem with this is the API consumer does not now what does the content types represent (22 means a User or a Post object?). This can be solved easily by creating a dictionary to translate ids to meaningful content types ({22:'User'}). 
 Another problem is if the API consumer has to show the activities ('Adam likes your post') he has to make a GET request for each object to get more information about the objects.

The solution

A better solution is to return the objects serialized instead of their IDs. To be able to do this we need to create serializers for each type of model that would appear in the feed.

General model serializer

In many cases we just have to create a simple model serializer class where the only difference is the class name and the model attribute. We can save some time and lines of code by creating a general serializer which receives the model object that has to be serialized:

class GeneralModelSerializer(serializers.ModelSerializer): class Meta: model = None def __init__(self, instance): self.Meta.model = type(instance) super(GeneralModelSerializer, self).__init__(instance=instance)

To use this we have to modify our FeedSerializer:

class FeedSerializer(serializers.ModelSerializer): target_object = serializers.SerializerMethodField('get_serialized_target_object') class Meta: model = Action fields = ('id', 'target_object') def get_serialized_target_object(self, obj): content_type, pk = obj.target_content_type, obj.target_object_id if content_type and pk: model_class = content_type.model_class() try: instance = model_class.objects.get(pk=pk) except model_class.DoesNotExist: return None return GeneralModelSerializer(instance=instance).data else: return None

For simplicity the FeedSerializer returns details only about the target object. The code is the same for the actor, action objects too.

Now with this new serializer the response looks like this:

[{ id: 1 target_object: { id: 3 last_login: "2014-08-21T13:44:54.263Z" is_superuser: false username: "norbert" first_name: "Norbert" last_name: "Mate" is_staff: false is_active: true is_deleted: false date_joined: "2014-08-21T13:44:54.263Z" bio: "updated bio" profile_image: "" } }

This way the API consumer has all the details needed to be able to build meaningful feed.

P.S.

Of course there might be objects with sensitive data like: email address, password. For those cases you can use specific model serializers.

For the complete solution please check out my gist.


Originally published at blog.hipwerk.com on January 26, 2015.