As a Beginner, going through Django Rest Framework tutorial: #2
1. Serialization
Enough of Django basic settings. Now let's move on to the rest framework the very fisrt of which is Serialization. We start by creating a serializer as given in the tutorial
# tutorial/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'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
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.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
First things first, what is serializer
? My lame understanding about serialization is that we convert objects into bytes that is appropriate to transfer via network. Obviously, there are many formats to choose like JSON, XML, pickle, etc (Link) ; I will however only focus on JSON. Lets's continue a bit further with the tutorial.
from snippets.serializers import SnippetSerializer
snippet = Snippet(code='print("hello, world")\n')
serializer = SnippetSerializer(snippet)
With the snippet
instance as argument, the instance of SnippetSerializer
called serializer
is created and it has the instance field called data
which contains the following:
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
It looks like JSON but it is still a dictonary object of some kind as shown below:
type(serializer.data)
# <class 'rest_framework.utils.serializer_helpers.ReturnDict'>
So we need to continue on conversion. In this sense, I feel like the name serializer
is a bit deceptive because when I first get my hands on this I naturally assume that all the serialization processes must be completed just using serializer
. Let's finish serialization by polishing serializer.data
with JSONRenderer
:
from rest_framework.renderers import JSONRenderer
content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
type(content)
# <class 'bytes'>
Voilà! The information that the snippet
instance was carrying was successfully converted in to JSON format. It is worth mentioning that there is b
preceding a quotation mark in content which stands for bytes
.
Deserialization from content
to data
can be done using serializer
as well:
import io
from rest_framework.parsers import JSONParser
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
serializer = SnippetSerializer(data=data)
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>
It is worth paying attention to the difference between the two cases for instantiating SnippetSerializer
:
# First case
snippet = Snippet(code='print("hello, world")\n')
serializer = SnippetSerializer(snippet)
serializer.save()
# Success for save
# Second case
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
serializer = SnippetSerializer(data=data)
serializer.save()
# AssertionError: You must call `.is_valid()` before calling `.save()`.
serializer.is_valid()
# True
serializer.save()
# created called
# <Snippet: Snippet object (11)>
# Success for save
For the second case, save
method right after the instantiation led to an error saying You must call `.is_valid()` before calling `.save()
. This is to make sure that the data provided from a client through the network is consistent with the fields and the types of the values as defined in class SnippetSerializer
above. What seems to be critical is that what is defined in class SnippetSerializer
should be consistent with what is defined in the model definition of class Snippet
.
At this point, the compariosn of the fields bewteen class Snippet
and class SnippetSerializer
is as follows:
Fields | class Snippet(models.Model) | class SnippetSerializer(serializers.Serializer) |
---|---|---|
created | models.DateTimeField(auto_now_add=True) | |
id | serializers.IntegerField(read_only=True) | |
title | models.CharField(max_length=100, blank=True, default='') | serializers.CharField(required=False, allow_blank=True, max_length=100) |
code | models.TextField() | serializers.CharField(style={'base_template': 'textarea.html'}) |
linenos | models.BooleanField(default=False) | serializers.BooleanField(required=False) |
language | models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) | serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') |
style | models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) | serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') |
Basically, all the fields except created
and id
are almost the same to each other. Obviously, this is not optimal because the fields of a serializer have to be hard-coded every time according to the fields definition of a model class. This is prone to errors and not scalable either. This is where ModelSerializer
instead of just Serializer
comes into play.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
With this, we have the same implementation as the previous one with a lot less code. I repete here what is decribed in the tutorial:
It's important to remember that ModelSerializer classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes:
- An automatically determined set of fields.
- Simple default implementations for the create() and update() methods.