Django REST framework concepts and introduction

The Django REST framework is distributed as a standard Python package. So to get started you need to install the Django REST framework with the command: pip install djangorestframework.

Once you install the Django REST framework package, add it to the INSTALLED_APPS list variable in your Django project's settings.py file with the name rest_framework. Once this is done, you can start working with the Django REST framework. Next, let's walk through the core concepts and creation process of a REST service using the Django REST framework.

Serializers and views

Serializers are one of the main building blocks of the Django REST framework used to define the representation of data records, which are generally based on Django models. As described in the previous section on Introduction to REST services options for Django, Python records can have ambiguous data representations (e.g. a record with a datetime value can be represented as DD/MM/YYYY, DD-MM-YYYY or MM-YYYY) and a serializer removes any uncertainty about how to represent a record. Listing 12-3 illustrates a Django REST framework serializer using one its serializers package.

Listing 12-3. Serializer class based on Django REST framework

# coffeehouse.stores.serializers.py file
from rest_framework import serializers

class StoreSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=200)
    email = serializers.EmailField()

As you can see listing 12-3, a Django REST framework serializer is a standard Python class, which in this case, inherits its behavior from the Django REST framework's serializers.Serializer class. Next, inside the serializer class are a set of fields that use the data types from the serializers package of the same Django REST framework. Notice the similar structure between this Django REST framework serializer class and Django model classes or Django form classes (i.e. they inherit their behavior from a parent class and use different fields to represent different data types like character and email fields).

The example in listing 12-3 is one of the simplest Django REST framework serializer classes possible, because it only has two fields and inherits its behavior from the very basic serializers.Serializer class. But just as with Django models and forms, as you progress with the Django REST framework, you'll find yourself using more advanced data types, as well as creating serializers with more sophisticated base classes than serializers.Serializer. I'll describe a more advanced serializer shortly. Next let's explore a view in the context of the Django REST framework.

Serializer classes by themselves do nothing and must be integrated with views that do the bulk of the REST service logic (i.e. handle incoming requests, query a database for data) which then use serializers to transform data. In the first section in this chapter, you learned how a regular Django view method can be turned into REST services. While it's perfectly possible to use a Django REST framework serializer class -- like the one listing 12-3 -- in a regular Django view method, the Django REST framework also provides additional view syntax -- illustrated in listing 12-4 -- to make it easier to build REST services.

Listing 12-4. Django view method decorated with Django REST framework

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET','POST','DELETE'])
def rest_store(request):
    if request.method == 'GET':
        stores = Store.objects.all()
        serializer = StoreSerializer(stores, many=True)
        return Response(serializer.data)
    elif request.method == 'POST':
        ... #logic for HTTP POST  operation
    elif request.method == 'DELETE':
        ... #logic for HTTP DELETE operation

First notice the method in listing 12-4 is a regular Django view method, but it uses the @api_view decorator from the Django REST framework. The arguments to @api_view indicate which HTTP REST methods to support -- see Chapter 2 or Chapter 6 for details on the topic of HTTP methods in Django or wikipedia's REST entry[7], as HTTP methods is a generic REST concept rather than a Django/REST topic. Next, inside the view method, a series of conditions are executed to process the different HTTP request methods that form the REST service.

If a GET request is made on the view method, a query is made to get all Store model records. However, notice in this case of listing 12-4, it uses the StoreSerializer from listing 12-3 to transform the Django queryset. In addition, the return statement uses the Django REST framework Response method, instead of Django's standard HttpResponse or render methods.

More importantly, notice both the request and response logic in listing 12-4 lacks any kind of REST output format (e.g. JSON, XML). By leveraging the Django REST framework, you no longer need to deal with the low level details of detecting or handling output formats -- this is taken care of directly by the REST framework and based on how you make requests to the REST service, which I'll describe shortly.

Next, the rest_store() view method in listing 12-4 must be configured to become accessible at a url. A line like url(r'^rest/$',stores_views.rest_store,name="rest_index") added to the app's urls.py file solves this issue. Once you do this, if you access the URL you'll see a result like the one in figure 12-3.

Figure 12-3. Django REST framework main service response

Caution If you get the error 'TemplateDoesNotExist at /stores/rest/ rest_framework/api.html' instead of the page in figure 12-3, it means you haven't added the REST framework to your project's INSTALLED_APPS as described at the start of this section (e.g. INSTALLED_APPS = ['rest_framework']).

As you can see in figure 12-3, the REST framework service response is very informative and pretty compared to Django's basic HttpResponse response generated by the Django REST service created in the initial section of this chapter.

For example, you can see the different options offered by the REST service and invoke its various operations in a few clicks. Also notice the output from the REST service only has Store objects with two fields due to the StoreSerializer definition in listing 12-3. Next, let's modify the serializer class to output complete Store records. Listing 12-5 shows an updated serializer class based on listing 12-3.

Listing 12-5 Serializer class using Django model based on Django REST framework

from rest_framework import serializers
from coffeehouse.stores.models import Store

class StoreSerializer(serializers.ModelSerializer):
    class Meta:
        model = Store
        fields = '__all__'

In order to serialize full Store records based on a Django Store model, listing 12-5 makes use of the the Django REST framework ModelSerializer class to simplify the serializer syntax. More importantly, notice how the REST framework StoreSerializer class in listing 12-5 uses syntax that's identical to Django model forms which are also based on Django models.

In this case, the StoreSerializer class uses a Meta class with the model option set to Strore to specify the Django model to serialize and the fields option set to __all__ to indicate all model fields should be serialized -- note the fields option can equally declare a limited list of model field names, just like it's done in model forms.

With this more specialized parent serializer ModelSerializer class in listing 12-5 -- vs. the generic Serializer class in listing 12-3 -- it's this simple to serialize Django model classes for REST services using the Django REST framework.

But as powerful as these Django REST framework serialization features are and as helpful as the Django REST framework view syntax used in listing 12-4 is -- cutting down on low level logic and presenting a user-friendly interface -- there's still a lot of scaffolding code in the view method that can be further trimmed down.

To further simplify the construction of REST services, the Django REST framework can make use of class-based views.

Class-based views

Chapter 2 introduced the concept of Django class-based views and Chapter 8 expanded on the topic with class-based views that use Django models to perform CRUD operations. Since the principles of class-based views in Django have already been covered, l'll assume you have a minimum level of familiarity with the topic, if not, then go back to these other chapters to learn the basics.

Listing 12-6 illustrates a class-based view based on a REST framework class, which simplifies the earlier standard view method -- in listing 12-4 -- decorated with @api_view from the REST framework.

Listing 12-6 Django REST framework class-based views

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer
from rest_framework.views import APIView
from rest_framework.response import Response

class StoreList(APIView):
    def get(self, request, format=None):
        stores = Store.objects.all()
        serializer = StoreSerializer(stores, many=True)
        return Response(serializer.data)
    def post(self, request, format=None):
        ...
        #logic for HTTP POST operation
    def delete(self, request, format=None):
        ...
        #logic for HTTP DELETE operation

Notice in listing 12-6 how the class inherits its behavior from the Django REST framework APIView class. This allows the class to contain various methods representing each of the REST service's HTTP methods (i.e. GET,POST, DELETE), -- similar to how it's done with standard Django class-based views that handle multiple HTTP methods (e.g. form processing).

As you can see, listing 12-6 produces much more readable REST services logic, compared to the regular Django view method in listing 12-4 which requires to manually inspect a request and perform conditional statements. The logic inside the get method in listing 12-6 uses the same Django REST framework syntax used in listing 12-4, so there's nothing new.

In the same way you must hook up a regular Django view method to a url, you must also associate a Django REST framework class-based view to make it accessible on a certain url. Listing 12-7 illustrates a urls.py file with the syntax to access the REST service class-based view from listing 12-6.

Listing 12-7 Django URL definition linked to Django REST framework class based views

from django.conf.urls import url
from coffeehouse.store import stores_views

urlpatterns = [
    url(r'^$',stores_views.index,name="index"),
    url(r'^rest/$',stores_views.StoreList.as_view(),name="rest_index"),    ]

In listing 12-7 you can see the urls.py file declares the r'^rest/$' url pattern is mapped to the Django REST framework StoreList class using the as_view() method, which is a staple of all Django class-based views to link them to a url. In this manner, if an HTTP GET request is made on the /stores/rest/ url it's handled by the get() method of the class-based view and if an HTTP POST request is made on the same /stores/rest/ it's handled by the post() method of the class-based view.

It's worth mentioning that hitting a url backed by a REST framework class-based view like the one in listing 12-6, also produces the same interface shown in figure 12-3 which is produced by the standard view method from listing 12-4.

Now, as helpful as REST framework class-based views are to simplify REST service logic, class-based views still require you to write all of the logic behind each method. For example, in the get() method in listing 12-6 a query is made to get all Store records, then serialize the data and finally return a response. For the post() method in listing 12-6, you would similarly need to insert/update a Store record with the provided data and with the delete() method you would need to delete a Store record with the provided data.

Once you write a couple of class-based views with the Django REST framework, you'll realize there's a constant pattern behind each type of view method (e.g. read a record, serialize it and return a response). On top of this, you'll also come to realize what a close relationship there is between REST methods (e.g. GET,POST and DELETE) and the operations they execute on Django models (i.e. Create-Read-Update-Delete(CRUD) operations).

To avoid having to constantly write the same CRUD operations and boilerplate logic for different Django objects associated with REST services, the Django REST framework following Django's DRY(Don't Repeat Yourself) principle and Django's class-based model view principle, offers another construct: mixins.

Mixins and generic class-based views

A mixin is used to encapsulate and reuse the same logic and be able to use it in class-based view. For example, instead of writing the same logic from listing 12-6 -- get all records, serialize them and generate a response -- over and over for different REST services (e.g. Item, Drink or Store services) you can use the mixins.ListModelMixin class and quickly achieve the same result.

I won't go into greater detail about mixin classes here, mainly because mixin classes are not as widely used as other Django REST framework options, not to mention Django mixins were already described in Chapter 9 in the context of class-based views that use models.

For most Django framework REST services, you'll either end up using class-based views -- to get full control over the logic -- or a more succinct approach based on mixins called 'mixed-in generic class views'. Listing 12-8 illustrates an equivalent mixed-in generic class view based on the class-based view from listing 12-6.

Listing 12-8. Django mixed-in generic class views in Django REST framework

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer
from rest_framework import generics

class StoreList(generics.ListCreateAPIView):
    queryset = Store.objects.all()
    serializer_class = StoreSerializer

Notice listing 12-8 is even more succinct than prior iterations of the same REST service. In this case, the generic class name ListCreateAPIView is indicative of what the class produces -- A REST view to generate a list -- based on the queryset option that specifies to get all Store records & the serializer option that points to the StoreSerializer class in listing 12-5.

Just as before and even though you now have a REST service composed of a couple of lines, the Django REST framework can further extend Django's DRY principle with the use view sets and routers.

View sets and routers

The generic class view in listing 12-8 is pretty powerful for just three lines, but it's just one class to display a list of Store records. Let's assume you now need to create a REST service to a display a specific Store record, another REST service to update a Store record and yet another REST service to delete Store records. In this scenario, you would need to create three more generic class views and three more URL mappings to roll out this basic CRUD functionality. But instead of creating separate view classes for each case, you can instead rely on a Django REST framework view set.

A Django REST framework view set, as its name implies is a group of views. To create a Django REST framework view set all you need to do is create a class that inherits its behavior from one of the Django REST framework's classes intended for this purpose. Listing 12-9 illustrates a view set created with the ModelViewSet class.

Listing 12-9 Django viewset class in Django REST framework

from coffeehouse.stores.models import Store
from coffeehouse.stores.serializers import StoreSerializer
from rest_framework import viewsets

class StoreViewSet(viewsets.ModelViewSet):
    queryset = Store.objects.all()
    serializer_class = StoreSerializer

Listing 12-9 is as short as the REST service class in listing 12-8, but besides the class name change, it's the parent ModelViewSet inherited class which gives this REST service a whole new dimension. Using this class alone, a REST service is automatically hooked up to display a Store record list, as well as to create, read, update or delete individual Store records.

Because a view set generates multiple views, you're still left with the issue of configuring each view to a url, in which case the easiest path is to use a Django REST framework router. A router is to a view set what a url statement is to class-based view, a way to hookup an end point. Listing 12-10 illustrates the urls.py file set up with a Django REST framework router.

Listing 12-10 Django URL definition with Django REST framework router for view set

from django.conf.urls import include, url
from coffeehouse.stores import views as stores_views
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'stores', stores_views.StoreViewSet)
urlpatterns = [
    url(r'^rest/', include(router.urls,namespace="rest")),
    ]
Caution View set & router combinations automatically create sensitive REST end points (e.g. delete & update) which by default are accessible to anyone. See the next section on REST framework security to restrict these service end points.

The first step in listing 12-10 is to initiate a router with routers.DefaultRouter() and then register the different view sets with it. As you can see in listing 12-10, the router registration process uses the router.register method which accepts two arguments: the first argument indicates the REST url prefix -- in this case stores -- and a second argument to specify the view set -- in this case the StoreViewSet class from listing 12-9.

Next, you can see the router is assigned using Django's standard url and include methods. In this case, the router instance is assigned under the r'^rest/' url , which means the final root url of the Store view set becomes /rest/stores/ as shown in figure 12-4.

Figure 12-4. Django REST framework view set main page

As you can see in figure 12-4, the REST framework presents a default Api Root page. You can further navigate to other urls under the Api Root page (i.e. /stores/rest/) to perform other CRUD actions associated with the view set (e.g. an HTTP GET request on /stores/rest/stores/ to get a list of all Store records, an HTTP GET request on /stores/rest/stores/1/ to get the Store record with id=1 or an HTTP DELETE request on /stores/rest/stores/2/ to delete the Store record with id=3).

And with this description of view sets and routers, we conclude the coverage of basic concepts needed to set up REST services with the Django REST framework. Now that you're familiar with the basics, in the next section you'll learn how to secure REST services built with the Django REST framework.

  1. https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods