Tutorial on how to create a REST API using Django Rest Framework (DRF)
![]() |
Create a REST API with Django Rest Framework |
DRF is a library that allows us to build a REST API on Django in a simple way. Offering a high range of methods and functions for the management, definition and control of our resources (endpoints).
Let's start our installation
Creating our base directory
We must create the directory where our code will live
mkdir tutorial
cd tutorial
Setting the environment
Before we begin, we must first create our virtual environment. This will allow us to completely isolate the dependencies of the project, in such a way, there will be no conflict with the dependencies and local libraries existing in our system. To learn more about the installation of virtual environments.
virtualenv tutorial
source tutorial/bin/activate # On Windows use `env\Scripts\activate`
Once our virtual environment has been created and activated, we proceed to install the necessary packages.
pip install django
pip install djangorestframework
Creating our project and initial configuration
Once DRF is installed in our virtual environment, we proceed to create our project in Django and establish the initial configuration. For the purposes of this tutorial, we are going to create an API to obtain information about movies and series, it will be called webflix.
django-admin.py startproject webflix
cd webflix
django-admin.py startapp series
Once our first application has been created, we proceed to incorporate it into the installed Django modules as we will do with DRF. To do this, we edit our /tutorial/settings.py file and add the following
INSTALLED_APPS = (
...
'rest_framework',
'series',
)
Now we start to play with the serializers.
Serializers and Model Serializers
Before creating our first serializer, we need to create a model which we are going to use to work with in this tutorial. We will create one called Series, within our series app, which will represent all the information that a TV series has.
from django.db import models
class Serie(models.Model):
HORROR = 'horror'
COMEDY = 'comedy'
ACTION = 'action'
DRAMA = 'drama'
CATEGORIES_CHOICES = (
(HORROR, 'Horror'),
(COMEDY, 'Comedy'),
(ACTION, 'Action'),
(DRAMA, 'Drama'),
)
name = models.CharField(max_length=100)
release_date = models.DateField()
rating = models.IntegerField(default=0)
category = models.CharField(max_length=10, choices=CATEGORIES_CHOICES)
Then we need to create the Django migrations
./manage.py makemigrations series
./manage.py migrate
A vital part of the Django Rest Framework is the ability to be able to serialize and deserialize our instances or resources in some type of representation that is easier to handle and transmit. To learn a little more about this process, we are going to create a serializer that will help us transform and handle our string instances in JSON format. To do this, we are going to create a file inside our app series called serializers.py. Within it the declarations of our serializers related to our newly created app will recur.
from rest_framework import serializers
from .models import Serie
class SerieSerializer(serializers.Serializer):
pk = serializers.IntegerField(read_only=True)
name = serializers.CharField()
release_date = serializers.DateField()
rating = serializers.IntegerField()
category = serializers.ChoiceField(choices=Serie.CATEGORIES_CHOICES)
def create(self, validated_data):
"""
Create and return a new `Serie` instance, given the validated data.
"""
return Serie.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Serie` instance, given the validated data.
"""
instance.name = validated_data.get('name', instance.name)
instance.release_date = validated_data.get('release_date', instance.release_date)
instance.rating = validated_data.get('rating', instance.rating)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
Basically in the first part of the serializer we define the attributes that we want to represent, each attribute is accompanied by a type, much like the forms in Django. Serializers in DRF need two fundamental methods, create () and update (), both of which work to create and update the instance using the serializer. Now it's time to see how to initialize our serializer.
Working with Serializers
To start testing our serializer quickly, let's open our Django console
./manage.py shell
Now, we import our model, serializer, and other utilities to create multiple instances of the Series model
from series.models import Series
from series.serializers import SerieSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from datetime import datetime
release_date = datetime.strptime ('17 -04-2011 ','% d-% m-% Y '). date ()
series = Series (name = 'Game of Thrones', category = 'drama', release_date = release_date)
series.save ()
release_date = datetime.strptime ('24 -06-2015 ','% d-% m-% Y '). date ()
series = Series (name = 'Mr. Robot', category = 'drama', release_date = release_date)
series.save ()
Once we have created two instances, we can start playing with our serializers. We are going to serialize one of them.
serializer = SerieSerializer(serie)
serializer.data
>{'category': 'drama',
'name': u'Mr. Robot',
'pk': 2,
'rating': 0,
'release_date': '2015-06-24'}
In this step, we have transformed our instance into a native Python data type, in this case a dictionary (dict for future reference). To finish the serialization process we must transform our dict into JSON, for this, we will use the JSONRenderer class that DRF brings and its render () method.
content = JSONRenderer().render(serializer.data)
content
> '{"pk":2,"name":"Mr. Robot","release_date":"2015-06-24","rating":0,"category":"drama"}'
If we want to reverse the process, we must transform our JSON into a native Python data type
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
data
> {u'category': u'drama',
u'name': u'Mr. Robot',
u'pk': 2,
u'rating': 0,
u'release_date': u'2015-06-24'}
Next, we convert our native data type, in this case a dict, to an instance of the Series model with all its attributes set.
serializer = SerieSerializer(data=data)
serializer.is_valid()
> True
serializer.validated_data
> OrderedDict([(u'name', u'Mr. Robot'), (u'release_date', datetime.date(2015, 6, 24)), (u'rating', 0), (u'category', 'drama')])
serie = serializer.save()
serie
> <Serie: Serie object>
# Borramos la serie creada para continuar con nuestro ejemplo
serie.delete()
We just instantiated our model from a dict. We can notice a similarity between working with serializers and Django forms. The process to create an instance is similar: We initialize our serializer with the required data We validate that the data is correct If there is no error, we can inspect that data to manipulate it We execute the .save () method to create the instance. Not only can we serialize an instance, our serializer allows us to transform several instances or a queryset. To do this, we only need to pass the queryset and the flag many = True which indicates that the object to be serialized is a set of instances.
serializer = SerieSerializer(Serie.objects.all(), many=True)
serializer.data
>[
OrderedDict([('pk', 1), ('name', u'Game of Thrones'), ('release_date', '2011-04-17'), ('rating', 0), ('category', u'Drama')]), OrderedDict([('pk', 2), ('name', u'Mr. Robot'), ('release_date', '2015-06-24'), ('rating', 0), ('category', u'Drama')]),
OrderedDict([('pk', 3), ('name', u'Mr. Robot'), ('release_date', '2015-06-24'), ('rating', 0), ('category', 'drama')]), OrderedDict([('pk', 4), ('name', u'Mr. Robot'), ('release_date', '2015-06-24'), ('rating', 0), ('category', 'drama')])
]
Using Model Serializers
As we can see, our SerieSerializer class is replicating much of the information that is contained in the Serie model. DRF offers the possibility of creating a serializer based on a previously defined model to avoid duplication of information. Just like forms do in Django, defining a ModelForms based on an existing model. To do this, we must import the ModelSerializer class and redefine our serializer. We open the file series / serializer.py and modify it.
class SerieSerializer(serializers.ModelSerializer):
class Meta:
model = Serie
fields = ('id', 'name', 'release_date', 'rating', 'category')
Redefined our serializer, we return to the console and proceed to test it.
from series.serializers import SerieSerializer
serializer = SerieSerializer()
print(repr(serializer))
> SerieSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(max_length=100)
release_date = DateField()
rating = IntegerField(required=False)
category = ChoiceField(choices=(('horror', 'Horror'), ('comedy', 'Comedy'), ('action', 'Action'), ('drama', 'Drama')))
Like our old serializer, the new one has the same attributes. Furthermore, it already contains the .create () and .update () methods implemented. They are given by default.
Writing normal views in Django using a serializer
After defining our serializer, we are going to create our first API using functional views or simple views in Django. It should be noted that I am in favor of using Class-Based Views but for the moment we will do it this way for practical purposes. But first, we will create a subclass of HttpResponse to allow our views to return the content in JSON format. We open the series / views.py file and add our subclass.
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from series.models import Serie
from series.serializers import SerieSerializer
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
Now, we are going to create our first API services. We are going to allow the user to list the series and create new records for them.
@csrf_exempt
def serie_list(request):
"""
List all code serie, or create a new serie.
"""
if request.method == 'GET':
series = Serie.objects.all()
serializer = SerieSerializer(series, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SerieSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
Since we are allowing a user to create a new record of a series, using the POST method, we must tell Django that this view should not request the csrf token that is usually handled, because our service will be public and the user will not you necessarily own that token, this for our practical purposes. To do this, we use the @csrf_exempt decorator. Usually this is not what should be done, DRF offers mechanisms to better handle these cases. Now, we are going to allow the user to get, update or delete a series
@csrf_exempt
def serie_detail(request, pk):
"""
Retrieve, update or delete a serie.
"""
try:
serie = Serie.objects.get(pk=pk)
except Serie.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SerieSerializer(serie)
return JSONResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SerieSerializer(serie, data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data)
return JSONResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
serie.delete()
return HttpResponse(status=204)
Finally, we add our urls to expose the services. We create a series / urls.py file within our series app.
from django.conf.urls import url
from series import views
urlpatterns = [
url (r '^ series / $', views.serie_list),
url (r '^ series / (? P <pk> [0-9] +) / $', views.serie_detail),
]
We proceed to open our main urls file webflix / urls.py to import the urls established in our app series.
....
from django.conf.urls import url, include
...
urlpatterns = [
....
url(r'^', include('series.urls')),
....
]
It should be noted that due to the practicality of the example, there are cases, which are not being taken into account. For example, what happens if the user sends a malformed request, which can cause a 500 error. There are several ways to handle certain exceptions. They are not included in the scope of this tutorial.
Testing our API
First, we must raise our server
./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
August 03, 2016 - 08:23:45
Django version 1.9.7, using settings 'webflix.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Now, we open another terminal to test our API We can request the API services using curl or httpie. Httpie is a lightweight http server, very easy to use. We install Httpie
pip install httpie
Now, we are going to list all the series
$http http://127.0.0.1:8000/series/
HTTP/1.0 200 OK
Content-Type: application/json
Date: Wed, 03 Aug 2016 08:29:53 GMT
Server: WSGIServer/0.1 Python/2.7.10
X-Frame-Options: SAMEORIGIN
[
{
"category": "drama",
"id": 1,
"name": "Game of Thrones",
"rating": 0,
"release_date": "2011-04-17"
},
{
"category": "drama",
"id": 2,
"name": "Mr. Robot",
"rating": 0,
"release_date": "2015-06-24"
}
]
Or we can request a series by your id
$http http://127.0.0.1:8000/series/1/
HTTP/1.0 200 OK
Content-Type: application/json
Date: Wed, 03 Aug 2016 08:29:53 GMT
Server: WSGIServer/0.1 Python/2.7.10
X-Frame-Options: SAMEORIGIN
{
"category": "Drama",
"id": 1,
"name": "Game of Thrones",
"rating": 0,
"release_date": "2011-04-17"
}
To create a series, we POST to / series /
$http -j POST http://127.0.0.1:8000/series/ category=comedy name=Lost release_date=2004-09-22
HTTP/1.0 201 Created
Content-Type: application/json
Date: Wed, 03 Aug 2016 08:30:50 GMT
Server: WSGIServer/0.1 Python/2.7.10
X-Frame-Options: SAMEORIGIN
{
"category": "drama",
"id": 4,
"name": "Lost",
"rating": 0,
"release_date": "2004-09-22"
}
Obviously they will be wondering, what's wrong with him, why the LOST category is comedy, but don't worry, I know, it's drama. To correct my mistake, we are going to update the information of our series by making a PUT to the url / series / 4 / with the new category.
$http -j PUT http://127.0.0.1:8000/series/4/ category=drama name=Lost release_date=2004-09-22
HTTP/1.0 200 OK
Content-Type: application/json
Date: Wed, 03 Aug 2016 08:32:53 GMT
Server: WSGIServer/0.1 Python/2.7.10
X-Frame-Options: SAMEORIGIN
{
"category": "drama",
"id": 4,
"name": "Lost",
"rating": 0,
"release_date": "2004-09-22"
}
DRF also offers by default a web interface which allows to interact with the API. To do this, open a browser and visit the same url previously requested.
Next step
At the moment, we know how to create serializers and that they behave very similar to the Django forms. In addition, we managed to create a functional REST API that currently only serves data in JSON format. It is important to clarify that certain factors that must be handled when wanting to implement robust services are not being taken into account.
In the next tutorial we will talk a little more in depth about requests and responses. In addition, we will see certain methods to improve what has been previously achieved. For example, using Class Based Views with DRF.
To close, you cannot miss the cool gif.
0 Comments