Django REST framework – Serializers

Feb 23, 2020

I’ve been playing about with Django REST framework lately and it seems that the concept of serializers is central in the framework. So in an attempt to solidify my own understanding of serializers, I thought I’d write a short blog post on it!

What is a Serializer / serialization?

In Django REST framework, serialization is the process of converting complex datatypes such as querysets or model instances into native Python datatypes that can then be easily converted to JSON.

Serializers are typically used to convert Django model instances into an appropriate JSON representation.

What is deserialization?

Deserialization is the opposite of serialization – it allows us to convert string-based data types such as JSON into complex data types such as a Django model or queryset. The deserialization process is like this:

  • Convert raw data (e.g. JSON) into a stream
  • Convert stream into native Python data types
  • Convert Python data types into complex data type (e.g. Django model instance)

Here’s that process in code.

import io

# Convert JSON into stream
stream = io.BytesIO(content)

# Convert stream into Python data type
data = JSONParser().parse(stream)

# Convert Python data type into a Snippet instance (a.k.a deserialize)
serializer = SnippetSerializer(data=data)

# Do a bunch of things!
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

Defining a Serializer

A Snippet model

Assume we are building a simple web app to store code snippets and we have the following Snippet model:

# snippets/models.py

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])


class Snippet(models.Model):
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)

    class Meta:
        ordering = ['created']

A Snippet Serializer

Now that we have a Django model to represent a Snippet, we need a way to interact with it through a web REST API.

Here’s an example of a serializer for our Snippet model.

# snippets/serializers.py

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.language = validated_data.get('language', instance.language)
        instance.save()
        return instance

It has three main parts:

  • Field definitions – here we define which model fields should get serialized / deserialized as well as define any validation constraints.
  • create – defines how Django model instances can be created from deserialized / incoming data.
  • save – defines how existing instances can be updated from deserialized data.

A Snippet ModelSerializer

REST framework also provides a ModelSerializer that allows us to write much more concise model-based serializers.

We can make use of ModelSerializer to replace the previous serializer definition with the much more concise definition below:

# snippets/serializers.py

class SnippetSerializer(serializers.ModelSerializer):
  class Meta:
      model = Snippet
      fields = ['id', 'title', 'code', 'language']

Using a Serializer in a Django view

# snippets/views.py

@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)