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'sINSTALLED_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.