HomeBack-End & DatabaseDjango Rest API – Part1 – Serializers

Django Rest API – Part1 – Serializers

Let’s get started with django rest api framework as we saw in the last blog.

First we need to install django rest api framework. Here are the steps

$ virtualenv env
$ source env/bin/activate
$ pip install django
$ pip install djangorestframework
$ django-admin startproject restapi
$ cd restapi
$ python manage.py migrate
$ django-admin startapp todo
$ python manage.py createsuperuser --email [email protected] --username admin

After these steps, rest api framework should be activated.

Reference
https://www.django-rest-framework.org/tutorial/quickstart/#project-setup

Also, once important thing to realize is that “DRF” is just a plugin on top of django. So everything related to django still works.

Serializers

Serializers is an important concept to understand. In django we have models to define our data structure. The problem we face is that, to return data from model in api in json format or any other format there is no way to do it. What serializes do is basically provide a way convert django models to different format’s like string, json, xml etc.

Before looking at how to code serializers its important to understand what they are. They are just a way to convert django model’s to different formats

Reference
https://www.django-rest-framework.org/api-guide/serializers/#serializers

Now let’s see this in action. First we will define a simple todo model

from django.db import models

# Create your models here.

class Todo(models.Model):
    task = models.CharField(max_length=255)

Let’s create a route to return a list of “Todo” follow basic steps to create a route which we have already seen in django part 1. so right now my route looks like this

from django.shortcuts import render

# Create your views here.

from django.http import HttpResponse

# Create your views here.

from todo.models import Todo

def index(request):
    todo = Todo.objects.all()
    print(todo)
    return HttpResponse("Hello, world!") 

First i need to insert some data, we will put in some random data for now. Creating a dummy route to insert data

//todo/urls.py

from django.urls import path

from . import views


urlpatterns = [
    path('', views.index, name='index'),
    path('dummy', views.dummy, name='dummy'),
]

//todo/views.py

from django.shortcuts import render

# Create your views here.

from django.http import HttpResponse

# Create your views here.

from todo.models import Todo

def index(request):
    todo = Todo.objects.all()
    print(todo)
    return HttpResponse("Hello, world!") 

def dummy(request):
    todo = Todo(task="dummy todo")
    todo.save()
    return HttpResponse(todo.id) 
There are few basic steps to do here which need to be done when setting up a new app "todo" in django
This is not part of DREF but still mentioning it here

Go to your main project urls.py file and add

path('todo/', include('todo.urls')),


next go to your main project settings.py and add under INSTALLED_APPS

'todo.apps.TodoConfig'

next run command

$ python manage.py makemigrations
$ python manage.py migrate

Next run server using $python manage.py runserver

Now fire the “dummy” route first in postman so that few data gets inserted.

Now, if we fire this route in our browser as we can see on our terminal. This is what shows up

<QuerySet [<Todo: Todo object (1)>, <Todo: Todo object (2)>]>

This format is of no use, we need json data. This is where is serializes come into picture

First define a simple serializer

//todo/serializers.py

from todo.models import Todo
from rest_framework import serializers


class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'task')

Next in our route we will use it like this

def index(request):
    todo = Todo.objects.get(pk=1)
    ser = TodoSerializer(todo)
    print(ser.data)
    return HttpResponse("") 

Now in terminal we see output like this

So data got transformed to json.

Django Shell

Django also offers a “shell” for dev’s to test our code quickly. For instance, above we want to just how model serializers work, but we had to define a full route to see the output and use postman to fire it. That not a good practices when we want to test out things in django. We should use shell, where we can write small code snippets and see there output.

If we are to test the same above in django shell, lets see how that would be

$ python manage.py shell
$ from todo.models import Todo
$ todo = Todo.objects.get(pk=1)
$ ser = TodoSerializer(todo)
$ ser.data
$ import io
$ from rest_framework.renderers import JSONRenderer
$ content = JSONRenderer().render(ser.data)
$ content
This is how my shell looks exactly!

Let’s continue further with serializers are there are lot of things understand in this

Let’s create more complex models

 
from django.db import models
from datetime import datetime
# Create your models here.

class Todo(models.Model):
task = models.CharField(max_length=255)
description = models.TextField(default="")
priority = models.IntegerField(default=1)
created = models.DateTimeField(default=datetime.now,blank=True)
class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'task','description','priority','created')
$ python manage.py makemigrations todo
$ python manage.py migrate
$ python manage.py shell


>>> from todo.models import Todo
>>> from todo.serializers import TodoSerializer
>>> todo = Todo(task="123")
>>> todo.save()
>>> ser = TodoSerializer(todo)
>>> ser.data
{'id': 4, 'task': '123', 'description': '', 'priority': 1, 'created': '2019-04-27T06:32:11.238866Z'}

Look’s good, lets see further. and try to fetch a list of Todo and use serializer

>>> todos = Todo.objects.all()
>>> ser = TodoSerializer(todos,many=True)
>>> ser.data
[OrderedDict([('id', 1), ('task', 'dummy todo'), ('description', ''), ('priority', 1), ('created', '2019-04-27T06:31:37.567277Z')]), OrderedDict([('id', 2), ('task', 'dummy todo'), ('description', ''), ('priority', 1), ('created', '2019-04-27T06:31:37.567277Z')]), OrderedDict([('id', 3), ('task', 'dummy task1'), ('description', ''), ('priority', 1), ('created', '2019-04-27T06:31:37.567277Z')]), OrderedDict([('id', 4), ('task', '123'), ('description', ''), ('priority', 1), ('created', '2019-04-27T06:32:11.238866Z')])]

So we just need to use many=True for multiple data

Next let’s see how we can validate data with serializers

To test our validate i will make small changes to model i.e add an email field

class Todo(models.Model):
    email = models.EmailField(max_length=70, null=True, blank=False, unique=True)
    task = models.CharField(max_length=255)
    description = models.TextField(default="")
    priority = models.IntegerField(default=1)
    created = models.DateTimeField(default=datetime.now,blank=True)

Now in the shell we can try out validations how it works

$ python manage.py makemigrations todo
$ python manage.py migrate
$ python manage.py shell

>>> from todo.serializers import TodoSerializer
>>> ser = TodoSerializer(data={"email":"123"})
>>> ser.is_valid()
False
>>> ser.errors
{'email': [ErrorDetail(string='Enter a valid email address.', code='invalid')], 'task': [ErrorDetail(string='This field is required.', code='required')]}

This way data can be validated.

In the above you would have noticed that we didn’t use Todo model at all. We can extend our serializers to return python object’s directly and do save, update via serializers itself.

e.g in the above code we can do this

>>> ser = TodoSerializer(data={"email":"[email protected]","task":"123"})
>>> ser.is_valid()
True
>>> ser.save()
>>> ser.data
{'id': 5, 'email': '[email protected]', 'task': '123', 'description': '', 'priority': 1, 'created': '2019-04-27T08:42:57.157928Z'}

cool this works!

Another another interesting thing, we can only call “save()” after “is_valid()” is called and return True. If you don’t you will get this error “AssertionError: You must call .is_valid() before calling .save().”

We can also configure the serializer to return instance of the object. What this means is that support we do this

>>> object = ser.save()
>>> object
{'id': 5, 'email': '[email protected]', 'task': '123', 'description': '', 'priority': 1, 'created': '2019-04-27T08:42:57.157928Z'}

Basically the return value of save() is string i.e serialized data and not the actual object. If we can actual object we need to do this further

from todo.models import Todo
from rest_framework import serializers
# from todo.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = "__all__"

    def create(self, validated_data):
        return Todo.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.task = validated_data.get('task', instance.task)
        instance.description = validated_data.get('description', instance.description)
        instance.priority = validated_data.get('priority', instance.priority)
        instance.created = validated_data.get('created', instance.created)
        instance.save()
        return instance

Here we have added two function for save/update. Now our serializes will return object’s instead of data. To see the output close your shell and open it again

>>> from todo.serializers import TodoSerializer
>>> ser = TodoSerializer(data={"email":"[email protected]","task":"123"})
>>> ser.is_valid()
True
>>> x = ser.save()
>>> x
<Todo: Todo object (6)>

So now we have a django object instead of string data. This is much better for further operations with models.

Also another thing to note. If i do this i.e put a duplicate email id

>> from todo.serializers import TodoSerializer
>>> ser = TodoSerializer(data={"email":"[email protected]","task":"123"})
>>> ser.is_valid()
False
>>> ser.errors
{'email': [ErrorDetail(string='todo with this email already exists.', code='unique')]}

which is cool! The validation happens at database level as well, not just at the object level.

Before going further with serialize another thing to note is that we are using ” ModelSerializer ” for our class. There are many other serializers and also 3rd party serializer available for different purposes. Open this link and see the left menu for different types

Now, next let’s look at how we can handle relationship’s between model’s and serializers

Let’s define another model “User” and assign “Todo” to user. Before moving further i would like to change our database engine to “mysql”. Till now we are on the default engine “sqlite3”

To make the changes first make sure mysql is installed on your system and create a user and database. For my case i created these details

username: user
password: user
database: restapi

Next install mysql client for python. i had to do these steps

 $ sudo apt install default-libmysqlclient-dev
// activate your virtual env and install
$ pip install mysqlclient

In your settings.py file make the changes to database engine.

previously existing

 
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

change this to

 
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'restapi',
        'USER': 'user',
        'PASSWORD': 'user',
        'HOST': 'localhost',
        'PORT': '',
    }
} 

next run

$ python manage.py migrate

after this all your database tables would be created in mysql.

Next, we can again start off where we left i.e User, Todo models association.

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: