In the previous chapter, you built a student registration system and enrollment in courses. You created views to display course contents and learned how to use Django's cache framework. In this chapter, you will learn how to do the following:
You might want to create an interface for other services to interact with your web application. By building an API, you can allow third parties to consume information and operate with your application programmatically.
There are several ways you can structure your API, but following REST principles is encouraged. The REST architecture comes from Representational State Transfer. RESTful APIs are resource-based. Your models represent resources and HTTP methods such as GET, POST, PUT, or DELETE are used to retrieve, create, update, or delete objects. HTTP response codes are also used in this context. Different HTTP response codes are returned to indicate the result of the HTTP request, e.g. 2XX
response codes for success, 4XX
for errors, and so on.
The most common formats to exchange data in RESTful APIs are JSON and XML. We will build a REST API with JSON serialization for our project. Our API will provide the following functionality:
We can build an API from scratch with Django by creating custom views. However, there are several third-party modules that simplify creating an API for your project, the most popular among them being Django Rest Framework.
Django Rest Framework allows you to easily build REST API's for your project. You can find all information about REST Framework at http://www.django-rest-framework.org.
Open the shell and install the framework with the following command:
pip install djangorestframework==3.2.3
Edit the settings.py
file of the educa
project and add rest_framework
to the INSTALLED_APPS
setting to activate the application, as follows:
INSTALLED_APPS = ( # ... 'rest_framework', )
Then, add the following code to the settings.py
file:
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ] }
You can provide a specific configuration for your API using the REST_FRAMEWORK
setting. REST Framework offers a wide range of settings to configure default behaviors. The DEFAULT_PERMISSION_CLASSES
setting specifies the default permissions to read, create, update, or delete objects. We set the DjangoModelPermissionsOrAnonReadOnly
as the only default permission class. This class relies on Django's permissions system to allow users to create, update, or delete objects, while providing read-only access for anonymous users. You will learn more about permissions later.
For a complete list of available settings for REST framework, you can visit http://www.django-rest-framework.org/api-guide/settings/.
After setting up REST Framework, we need to specify how our data will be serialized. Output data has to be serialized into a specific format, and input data will be de-serialized for processing. The framework provides the following classes to build serializers for single objects:
Serializer
: Provides serialization for normal Python class instancesModelSerializer
: Provides serialization for model instancesHyperlinkedModelSerializer
: The same as ModelSerializer
, but represents object relationships with links rather than primary keysLet's build our first serializer. Create the following file structure inside the courses
application directory:
api/ __init__.py serializers.py
We will build all the API functionality inside the api
directory to keep everything well organized. Edit the serializers.py
file and add the following code:
from rest_framework import serializers from ..models import Subject class SubjectSerializer(serializers.ModelSerializer): class Meta: model = Subject fields = ('id', 'title', 'slug')
This is the serializer for the Subject
model. Serializers are defined in a similar fashion to Django's Form
and ModelForm
classes. The Meta
class allows you to specify the model to serialize and the fields to be included for serialization. All model fields will be included if you don't set a fields
attribute.
Let's try our serializer. Open the command line and start the Django shell with the command python manage.py shell
. Run the following code:
from courses.models import Subject from courses.api.serializers import SubjectSerializer subject = Subject.objects.latest('id') serializer = SubjectSerializer(subject) serializer.data
In this example, we get a Subject
object, create an instance of SubjectSerializer
, and access the serialized data. You will get the following output:
{'slug': 'music', 'id': 4, 'title': 'Music'}
As you can see, the model data is translated into Python native data types.
The serialized data has to be rendered in a specific format before you return it in an HTTP response. Likewise, when you get an HTTP request, you have to parse the incoming data and deserialize it before you can operate with it. REST Framework includes renderers and parsers to handle that.
Let's see how to parse incoming data. Given a JSON string input, you can use the JSONParser
class provided by REST framework to convert it to a Python object. Execute the following code in the Python shell:
from io import BytesIO from rest_framework.parsers import JSONParser data = b'{"id":4,"title":"Music","slug":"music"}' JSONParser().parse(BytesIO(data))
You will get the following output:
{'id': 4, 'title': 'Music', 'slug': 'music'}
REST Framework also includes Renderer
classes that allow you to format API responses. The framework determines which renderer to use through content negotiation. It inspects the request's Accept
header to determine the expected content type for the response. Optionally, the renderer is determined by the format suffix of the URL. For example, accessing will trigger the JSONRenderer
in order to return a JSON response.
Go back to the shell and execute the following code to render the serializer
object from the previous serializer example:
from rest_framework.renderers import JSONRenderer JSONRenderer().render(serializer.data)
You will see the following output:
b'{"id":4,"title":"Music","slug":"music"}'
We use the JSONRenderer
to render the serialized data into JSON. By default, REST Framework uses two different renderers: JSONRenderer
and BrowsableAPIRenderer
. The latter provides a web interface to easily browse your API. You can change the default renderer classes with the DEFAULT_RENDERER_CLASSES
option of the REST_FRAMEWORK
setting.
You can find more information about renderers and parsers at http://www.django-rest-framework.org/api-guide/renderers/ and http://www.django-rest-framework.org/api-guide/parsers/ respectively.
REST Framework comes with a set of generic views and mixins that you can use to build your API views. These provide functionality to retrieve, create, update, or delete model objects. You can see all generic mixins and views provided by REST Framework at http://www.django-rest-framework.org/api-guide/generic-views/.
Let's create list and detail views to retrieve Subject
objects. Create a new file inside the courses/api/
directory and name it views.py
. Add the following code to it:
from rest_framework import generics from ..models import Subject from .serializers import SubjectSerializer class SubjectListView(generics.ListAPIView): queryset = Subject.objects.all() serializer_class = SubjectSerializer class SubjectDetailView(generics.RetrieveAPIView): queryset = Subject.objects.all() serializer_class = SubjectSerializer
In this code, we are using the generic ListAPIView
and RetrieveAPIView
views of REST Framework. We include a pk
URL parameter for the detail view to retrieve the object for the given primary key. Both views have the following attributes:
queryset
: The base QuerySet to use to retrieve objectsserializer_class
: The class to serialize objectsLet's add URL patterns for our views. Create a new file inside the courses/api/
directory and name it urls.py
and make it look as follows:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^subjects/$', views.SubjectListView.as_view(), name='subject_list'), url(r'^subjects/(?P<pk>d+)/$', views.SubjectDetailView.as_view(), name='subject_detail'), ]
Edit the main
urls.py
file of the educa
project and include the API patterns as follows:
urlpatterns = [ # ... url(r'^api/', include('courses.api.urls', namespace='api')), ]
We use the api
namespace for our API URLs. Ensure your server is running with the command python manage.py runserver
. Open the shell and retrieve the URL http://127.0.0.1:8000/api/subjects/
with cURL as follows:
$ curl http://127.0.0.1:8000/api/subjects/
You will get a response similar to the following one:
[{"id":2,"title":"Mathematics","slug":"mathematics"},{"id":4,"title":"Music","slug":"music"},{"id":3,"title":"Physics","slug":"physics"},{"id":1,"title":"Programming","slug":"programming"}]
The HTTP response contains a list of Subject
objects in JSON format. If your operating system doesn't come with cURL installed, you can download it from http://curl.haxx.se/dlwiz/. Instead of cURL, you can also use any other tool to send custom HTTP requests such as a browser extension such as Postman, which you can get at https://www.getpostman.com.
Open http://127.0.0.1:8000/api/subjects/
in your browser. You will see REST Framework's browsable API as follows:
This HTML interface is provided by the BrowsableAPIRenderer
renderer. It displays the result headers and content and allows you to perform requests. You can also access the API detail view for a
Subject
object by including its id
in the URL. Open http://127.0.0.1:8000/api/subjects/1/
in your browser. You will see a single Subject
object rendered in JSON format.
We are going to create a serializer for the Course
model. Edit the api/serializers.py
file and add the following code to it:
from ..models import Course class CourseSerializer(serializers.ModelSerializer): class Meta: model = Course fields = ('id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules')
Let's take a look at how a Course
object is serialized. Open the shell, run python manage.py shell
, and run the following code:
from rest_framework.renderers import JSONRenderer from courses.models import Course from courses.api.serializers import CourseSerializer course = Course.objects.latest('id') serializer = CourseSerializer(course) JSONRenderer().render(serializer.data)
You will get a JSON object with the fields we included in CourseSerializer
. You can see that the related objects of the modules
manager are serialized as a list of primary keys, like this:
"modules": [17, 18, 19, 20, 21, 22]
We want to include more information about each module, so we need to serialize Module
objects and nest them. Modify the previous code of the api/serializers.py
file to make it look as follows:
from rest_framework import serializers from ..models import Course, Module class ModuleSerializer(serializers.ModelSerializer): class Meta: model = Module fields = ('order', 'title', 'description') class CourseSerializer(serializers.ModelSerializer): modules = ModuleSerializer(many=True, read_only=True) class Meta: model = Course fields = ('id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules')
We define a ModuleSerializer
to provide serialization for the Module
model. Then we add a modules
attribute to CourseSerializer
to nest the ModuleSerializer
serializer. We set many=True
to indicate that we are serializing multiple objects. The read_only
parameter indicates that this field is read-only and should not be included in any input to create or update objects.
Open the shell and create an instance of CourseSerializer
again. Render the serializer's data
attribute with JSONRenderer
. This time, the listed modules are being serialized with the nested ModuleSerializer
serializer, like this:
"modules": [ { "order": 0, "title": "Django overview", "description": "A brief overview about the Web Framework." }, { "order": 1, "title": "Installing Django", "description": "How to install Django." }, ... ]
You can read more about serializers at http://www.django-rest-framework.org/api-guide/serializers/.
REST Framework provides an APIView
class, which builds API functionality on top of Django's View
class. The APIView
class differs from View
in using REST Framework's custom Request
and Response
objects and handling APIException
exceptions to return the appropriate HTTP responses. It also has a built-in authentication and authorization system to manage access to views.
We are going to create a view for users to enroll in courses. Edit the api/views.py
file and add the following code to it:
from django.shortcuts import get_object_or_404 from rest_framework.views import APIView from rest_framework.response import Response from ..models import Course class CourseEnrollView(APIView): def post(self, request, pk, format=None): course = get_object_or_404(Course, pk=pk) course.students.add(request.user) return Response({'enrolled': True})
The CourseEnrollView
view handles user enrollment in courses. The preceding code is as follows:
APIView
.post()
method for POST actions. No other HTTP method will be allowed for this view.pk
URL parameter containing the ID of a course. We retrieve the course by the given pk
parameter and raise a 404 exception if it's not found.students
many-to-many relationship of the Course
object and return a successful response.Edit the api/urls.py
file and add the following URL pattern for the CourseEnrollView
view:
url(r'^courses/(?P<pk>d+)/enroll/$', views.CourseEnrollView.as_view(), name='course_enroll'),
Theoretically, we could now perform a POST request to enroll the current user in a course. However, we need to be able to identify the user and prevent unauthenticated users from accessing this view. Let's see how API authentication and permissions work.
REST Framework provides authentication classes to identify the user performing the request. If authentication is successful, the framework sets the authenticated User
object in request.user
. If no user is authenticated, an instance of Django's AnonymousUser
is set instead.
REST Framework provides the following authentication backends:
BasicAuthentication
: HTTP Basic Authentication. The user and password are sent by the client in the Authorization
HTTP header encoded with Base64. You can learn more about it at https://en.wikipedia.org/wiki/Basic_access_authentication.TokenAuthentication
: Token-based authentication. A Token
model is used to store user tokens. Users include the token in the Authorization
HTTP header for authentication.SessionAuthentication
: Uses Django's session backend for authentication. This backend is useful to perform authenticated AJAX requests to the API from your website's frontend.You can build a custom authentication backend by subclassing the BaseAuthentication
class provided by REST Framework and overriding the
authenticate()
method.
You can set authentication on a per-view basis, or set it globally with the DEFAULT_AUTHENTICATION_CLASSES
setting.
You can find all the information about authentication at http://www.django-rest-framework.org/api-guide/authentication/.
Let's add BasicAuthentication
to our view. Edit the api/views.py
file of the courses
application and add an authentication_classes
attribute to CourseEnrollView
as follows:
from rest_framework.authentication import BasicAuthentication class CourseEnrollView(APIView): authentication_classes = (BasicAuthentication,) # ...
Users will be identified by the credentials set in the Authorization
header of the HTTP request.
REST Framework includes a permission system to restrict access to views. Some of the built-in permissions of REST Framework are:
AllowAny
: Unrestricted access, regardless of if a user is authenticated or not.IsAuthenticated
: Allows access to authenticated users only.IsAuthenticatedOrReadOnly
: Complete access to authenticated users. Anonymous users are only allowed to execute read methods such as GET, HEAD, or OPTIONS.DjangoModelPermissions
: Permissions tied to django.contrib.auth
. The view requires a queryset
attribute. Only authenticated users with model permissions assigned are granted permission.DjangoObjectPermissions
: Django permissions on a per-object basis.If users are denied permission, they will usually get one of the following HTTP error codes:
HTTP 401
: UnauthorizedHTTP 403
: Permission deniedYou can read more information about permissions at http://www.django-rest-framework.org/api-guide/permissions/.
Edit the api/views.py
file of the courses
application and add a permission_classes
attribute to CourseEnrollView
as follows:
from rest_framework.authentication import BasicAuthentication from rest_framework.permissions import IsAuthenticated class CourseEnrollView(APIView): authentication_classes = (BasicAuthentication,) permission_classes = (IsAuthenticated,) # ...
We include the IsAuthenticated
permission. This will prevent anonymous users from accessing the view. Now, we can perform a POST request to our new API method.
Make sure the development server is running. Open the shell and run the following command:
curl -i –X POST http://127.0.0.1:8000/api/courses/1/enroll/
You will get the following response:
HTTP/1.0 401 UNAUTHORIZED ... {"detail": "Authentication credentials were not provided."}
We get a 401
HTTP code as expected, since we are not authenticated. Let's use basic authentication with one of our users. Run the following command:
curl -i -X POST -u student:password http://127.0.0.1:8000/api/courses/1/enroll/
Replace student:password
with the credentials of an existing user. You will get the following response:
HTTP/1.0 200 OK ... {"enrolled": true}
You can access the administration site and check that the user is now enrolled in the course.
ViewSets
allow you to define the interactions of your API and let REST Framework build the URLs dynamically with a Router
object. By using view sets, you can avoid repeating logic for multiple views. View sets include actions for the typical create, retrieve, update, delete operations, which are list()
, create()
, retrieve()
, update()
, partial_update()
, and destroy()
.
Let's create a view set for the Course
model. Edit the api/views.py
file and add the following code to it:
from rest_framework import viewsets from .serializers import CourseSerializer class CourseViewSet(viewsets.ReadOnlyModelViewSet): queryset = Course.objects.all() serializer_class = CourseSerializer
We subclass ReadOnlyModelViewSet
, which provides the read-only actions list()
and retrieve()
to both list objects or retrieve a single object. Edit the api/urls.py
file and create a router for our view set as follows:
from django.conf.urls import url, include from rest_framework import routers from . import views router = routers.DefaultRouter() router.register('courses', views.CourseViewSet) urlpatterns = [ # ... url(r'^', include(router.urls)), ]
We create a DefaultRouter
object and register our view set with the courses
prefix. The router takes charge of generating URLs automatically for our view set.
Open http://127.0.0.1:8000/api/
in your browser. You will see that the router lists all view sets in its base URL, as shown in the following screenshot:
You can access http://127.0.0.1:8000/api/courses/
to retrieve the list of courses.
You can learn more about view sets at http://www.django-rest-framework.org/api-guide/viewsets/. You can also find more information about routers at http://www.django-rest-framework.org/api-guide/routers/.
You can add extra actions to view sets. Let's change our previous CourseEnrollView
view into a custom view set action. Edit the api/views.py
file and modify the CourseViewSet
class to look as follows:
from rest_framework.decorators import detail_route class CourseViewSet(viewsets.ReadOnlyModelViewSet): queryset = Course.objects.all() serializer_class = CourseSerializer @detail_route(methods=['post'], authentication_classes=[BasicAuthentication], permission_classes=[IsAuthenticated]) def enroll(self, request, *args, **kwargs): course = self.get_object() course.students.add(request.user) return Response({'enrolled': True})
We add a custom enroll()
method that represents an additional action for this view set. The preceding code is as follows:
detail_route
decorator of the framework to specify that this is an action to be performed on a single object.self.get_object()
to retrieve the Course
object.students
many-to-many relationship and return a custom success response.Edit the api/urls.py
file and remove the following URL, since we don't need it anymore:
url(r'^courses/(?P<pk>[d]+)/enroll/$', views.CourseEnrollView.as_view(), name='course_enroll'),
Then edit the api/views.py
file and remove the CourseEnrollView
class.
The URL to enroll in courses is now automatically generated by the router. The URL remains the same, since it's built dynamically using our action name enroll
.
We want students to be able to access the contents of the courses they are enrolled in. Only students enrolled in a course should be able to access its contents. The best way to do this is with a custom permission class. Django provides a BasePermission
class that allows you to define the following methods:
has_permission()
: View-level permission checkhas_object_permission()
: Instance-level permission checkThese methods should return True
to grant access or False
otherwise. Create a new file inside the courses/api/
directory and name it permissions.py
. Add the following code to it:
from rest_framework.permissions import BasePermission class IsEnrolled(BasePermission): def has_object_permission(self, request, view, obj): return obj.students.filter(id=request.user.id).exists()
We subclass the
BasePermission
class and override the has_object_permission()
. We check that the user performing the request is present in the students
relationship of the Course
object. We are going to use the IsEnrolled
permission next.
We need to serialize course contents. The Content
model includes a generic foreign key that allows us to associate objects of different content models. Yet, we have added a common render()
method for all content models in the previous chapter. We can use this method to provide rendered contents to our API.
Edit the api/serializers.py
file of the courses
application and add the following code to it:
from ..models import Content class ItemRelatedField(serializers.RelatedField): def to_representation(self, value): return value.render() class ContentSerializer(serializers.ModelSerializer): item = ItemRelatedField(read_only=True) class Meta: model = Content fields = ('order', 'item')
In this code, we define a custom field by subclassing the RelatedField
serializer field provided by REST Framework and overriding the to_representation()
method. We define the ContentSerializer
serializer for the Content
model and use the custom field for the item
generic foreign key.
We need an alternate serializer for the Module
model that includes its contents, and an extended Course
serializer as well. Edit the api/serializers.py
file and add the following code to it:
class ModuleWithContentsSerializer(serializers.ModelSerializer): contents = ContentSerializer(many=True) class Meta: model = Module fields = ('order', 'title', 'description', 'contents') class CourseWithContentsSerializer(serializers.ModelSerializer): modules = ModuleWithContentsSerializer(many=True) class Meta: model = Course fields = ('id', 'subject', 'title', 'slug', 'overview', 'created', 'owner', 'modules')
Let's create a view that mimics the behavior of the retrieve()
action but includes the course contents. Edit the api/views.py
file and add the following method to the CourseViewSet
class:
from .permissions import IsEnrolled from .serializers import CourseWithContentsSerializer class CourseViewSet(viewsets.ReadOnlyModelViewSet): # ... @detail_route(methods=['get'], serializer_class=CourseWithContentsSerializer, authentication_classes=[BasicAuthentication], permission_classes=[IsAuthenticated, IsEnrolled]) def contents(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs)
The description of this method is as follows:
detail_route
decorator to specify that this action is performed on a single object.CourseWithContentsSerializer
serializer class that includes rendered course contents.IsAuthenticated
and our custom IsEnrolled
permissions. By doing so, we make sure that only users enrolled in the course are able to access its contents.retrieve()
action to return the course object.Open http://127.0.0.1:8000/api/courses/1/contents/
in your browser. If you access the view with the right credentials, you will see that each module of the course includes the rendered HTML for course contents, like this:
{ "order": 0, "title": "Installing Django", "description": "", "contents": [ { "order": 0, "item": "<p>Take a look at the following video for installing Django:</p> " }, { "order": 1, "item": " <iframe width="480" height="360" src="http://www.youtube.com/embed/bgV39DlmZ2U?wmode=opaque" frameborder="0" allowfullscreen></iframe> " } ] }
You have built a simple API that allows other services to access the course application programmatically. REST Framework also allows you to manage creating and editing objects with the ModelViewSet
view set. We have covered the main aspects of Django Rest Framework, but you will find further information about its features in its extensive documentation at http://www.django-rest-framework.org/.