fir3net
PPS-Firenetbanner-780.5x190-30-03-17

How to Build a RESTful API using the Django REST Framework

Contents[Hide]

The Django REST Framework (DRF) allows you create REST based APIs quickly and simply, providing a range of features such authentication, error handling, serialization and full CRUD  (Create Replace Update Delete) based operations to your database.

Within this article will look at how to create a very basic API., The API will take in a JSON input, validate and return a response back to the client.

Installing DRF

The first thing you will need to do is install DRF.

pip install Django==1.8 
pip install djangorestframework==3.3.2

Once done, lets create a Django project and app.

django-admin startproject myproject
cd myproject/
python manage.py startapp myapp

Settings.py

Within your settings file add DRF and your new Django app.

INSTALLED_APPS = [
+ 'rest_framework',
+ 'myapp',
...

Serializers.py

Serializers allow you to build a logical representation of your data, in turn allowing you to,  

  • Convert incoming data from JSON to Python data structures and vice-versa.
  • Perform data validation, this is extremely similar to how the Forms and ModelForm classes work within Django. 

Below shows you an example. We simply create a serializer, defining a key named 'email' and a value that must contain a dictionary.

from rest_framework import serializers
class MyAPISerializer(serializers.Serializer):
email = serializers.DictField()

Once your data has been passed to your serializer serializer.is_valid()can be called. Errors can then be returned by callingserializer.errors. Below shows can example,

serializer = MyAPISerializer(data={'wrong_key': 'bad_value'})
serializer.is_valid()
# False
serializer.errors
# {'email': [u'This field is required.']}

Views.py

Lets next look at views.py. First we apply a decorator to only allow GET and POST requests. Our data is then passed to our serializer we check if the data is valid, if it is a HTTP 200 response is returned back to the client. If the data is not valid the error is returned.

from rest_framework.decorators import api_view
@api_view(['GET','POST'])
def myapi(request):
serializer = MyAPISerializer(data=request.data)
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Exceptions

Django DRF contains a number of built in exceptions, such as ParseError, ValidationError and PermissionError. When these exceptions are raised, for example due to malformed input, a JSON response with the appropriate details and response code is returned.

Additionally, these exceptions can be manually raised. It is also worth noting, at the point of raising a custom exceptions you can also amend the response details returned.

raise NotAuthenticated(detail='User not authenticated')

Custom exceptions can also be created by subclassing APIException. Below shows an example,

from rest_framework.exceptions import APIException
class FluxError(APIException):
status_code = 503
default_detail = 'Flux Error. Please check flux capacitor'

Examples

Based on the previous Django configuration (views, serializers and views) and our new knowledge of the DRF exceptions, lets see some examples. Within our examples we will send the correct input to our API , the incorrect input and then view the returned responses. First all lets run Djangos built in webserver.

python manage.py runserver 0.0.0.0:8002 &

Correct Input

Next we use httpie to send in a HTTP POST request. Here we provide the required data, this was previously set withinserializer.py.

echo '{"email":{"john":"This email address is being protected from spambots. You need JavaScript enabled to view it."}}' | http http://127.0.0.1:8002/myapi

[10/Mar/2016 23:17:24]"POST /myapi HTTP/1.1" 200 33 HTTP/1.0 200 OK Allow: POST, OPTIONS, GET Content-Type: application/json Date: Thu, 10 Mar 2016 23:17:24 GMT Server: WSGIServer/0.1 Python/2.7.5 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "email": { "john": "This email address is being protected from spambots. You need JavaScript enabled to view it." } }

Malformed Input

Now, if we send in some malformed JSON the DRF raises an exception, this generates a HTTP response with the appropriate error.
Below the quotes are removed from 'email'.

echo '{email:{"john":"This email address is being protected from spambots. You need JavaScript enabled to view it."}}' | http http://127.0.0.1:8002/myapi

[11/Mar/2016 20:20:56]"POST /myapi HTTP/1.1" 400 107
HTTP/1.0 400 BAD REQUEST
Allow: POST, OPTIONS, GET
Content-Type: application/json
Date: Fri, 11 Mar 2016 20:20:56 GMT
Server: WSGIServer/0.1 Python/2.7.5
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN {
"detail": "JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)"
}

Lets change the data that is sent in. We change the input so that the JSON is different to what is defined within the serializer.py.

echo '{"bananas":{"john":"This email address is being protected from spambots. You need JavaScript enabled to view it."}}' | http http://127.0.0.1:8002/myapi

[11/Mar/2016 20:18:35]"POST /myapi HTTP/1.1" 400 37
HTTP/1.0 400 BAD REQUEST
Allow: POST, OPTIONS, GET
Content-Type: application/json
Date: Fri, 11 Mar 2016 20:18:35 GMT
Server: WSGIServer/0.1 Python/2.7.5
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN {
"email": [
"This field is required."
]
}

Not Found Catchall

Now everything above is all well and good, but what happens when the client sends in a request for a URI that is not configured within urls.py ?
Though a 404 is returned (shown below) the response is not presented as JSON due to the request not making it through to DRF, this is because  the error is raised within urls.py.

echo '{"email":{"john":"This email address is being protected from spambots. You need JavaScript enabled to view it."}}' | http http://127.0.0.1:8002/wronguri | more

[19/Mar/2016 21:49:39]"POST /wronguri HTTP/1.1" 404 2036 <!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Page not found at /wronguri</title>
<meta name="robots" content="NONE,NOARCHIVE">

To resolve this a catchall rule is placed within your urls.py and assigned to a view that will return the necessary response.
Below is an example of the configuration,

// views.py
from rest_framework.exceptions import APIException,NotFound
from rest_framework.decorators import api_view

@api_view(['GET'])
def not_found(request):
    raise NotFound('Resource Not Found')

// urls.py (placed at the bottom)
+ url(r'^.*$', 'myproject.views.not_found'),

Lets retest. Now we see that the response is returned as JSON.

echo '{"email":{"john":"This email address is being protected from spambots. You need JavaScript enabled to view it."}}' | http http://127.0.0.1:8002/wronguri

[19/Mar/2016 22:10:44]"POST / HTTP/1.1" 404 31
HTTP/1.0 404 NOT FOUND
Allow: POST, OPTIONS, GET
Content-Type: application/json
Date: Sat, 19 Mar 2016 22:10:44 GMT
Server: WSGIServer/0.1 Python/2.7.5
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN
{
"detail": "Resource Not Found"
}

Tags: Python, Django, API, RESTful, DRF

About the Author

RDonato

R Donato

Rick Donato is the Founder and Chief Editor of Fir3net.com. He currently works as a Principal Network Security Engineer and has a keen interest in automation and the cloud.

You can find Rick on Twitter @f3lix001