Representational state transfer (REST) services or simply RESTful services have become one of the most popular techniques in web development since they first appeared in the year 2000. While there's a long background story on REST services[1] which I won't get into here, the appeal and explosive growth of REST services in web development is simply due to how they solve a common problem in an easy and reusable manner.
In this chapter, you'll learn about the options available to create REST services with Django, including: plain Python/Django REST services and framework specific REST services. In addition, you'll learn how to create REST services with plain Python/Django packages and when they're preferable over using framework specific REST services packages. In addition, you'll also learn how to create and use some of the most important features of REST services created with the Django REST framework, as well as incorporate essential security into Django REST framework services.
REST services in Django
A REST service provides access to data through an end point or Internet URL making no assumption about who uses it (e.g. an IoT device, a mobile phone or a desktop browser), this means there's no device or environment requirements to access REST services, all you need is Internet access.
On top of this, because REST
services operate on Internet URLs, they provide a very intuitive
access scheme. For example, the REST service URL
/products/
can mean 'get all product data', while the
URL /products/10/20/
can mean get all data for
products with a price between 10 and 20 and the URL
/products/drinks/
get all product data in the drinks
category.
The power of this last approach is there's no steep learning curve (e.g. language syntax, complex business logic) to access REST service variations that solve elaborate data queries. Because of these features and versatility, REST services work as a reusable data backbone across a wide array of areas and applications. For example, practically the entire web API (Application Programming Interface) world operates around REST web services, because by definition an API must provide a device/environment neutral way for customers to access their functionality. In fact, there's a high-chance most web sites you visit make us of REST services in one way or another, a practice that allows web site operators to reuse the same data and then format it for users on desktop browsers, IoT devices, mobile applications, RSS feeds or any other target like Accelerated Mobile Pages(AMP)[2].
With this brief overview of REST services, let's start with the simplest option available to create REST services in Django.
Standard view method designed as REST service
You can actually create a REST service in Django with just the Django package and Python's core packages, with no need to install any third party package. Listing 12-1 illustrates a standard Django url and view method designed to function as a REST service.
Listing 12-1. Standard view method designed as REST service
# urls.py (In stores app) from coffeehouse.stores import views as stores_views urlpatterns = [ url(r'^rest/$',stores_views.rest_store,name="rest_index"), ] # views.py (In stores app) from django.http import HttpResponse from coffeehouse.stores.models import Store import json def rest_store(request): store_list = Store.objects.all() store_names = [{"name":store.name} for store in store_list] return HttpResponse(json.dumps(store_names), content_type='application/json') # Sample output # [{"name": "Corporate"}, {"name": "Downtown"}, {"name": "Uptown"}, {"name": "Midtown"}]
The url statement in listing 12-1
defines an access pattern on the stores
app under the
rest
directory (i.e. final url
/stores/rest/
). This last url statement is hooked up
the rest_store
view method -- also in listing 12-1 --
which makes a query to get all Store
records from the
database, then creates a list comprehension to generate a list of
store names from the resulting query and finally uses Django's
HttpResponse
to return a JSON response with the store
names using Python's json
package. The design in
listing 12-1 is one of the simplest REST services you can create in
Django and has some important points:
- Serialization.- Notice the
Store
query is not used directly in the response, but rather a list comprehension is created first. This is done to serialize the data into an appropriate format. Due to the way Django queries are built, results may not be naturally serializable, so a list comprehension is used to ensure the data is a standard Python dictionary that can be converted to JSON with the json package. I'll provide more details on why serialization is an important piece of working with REST services in the following sidebar. - Response handling.- Notice the use of Django's low-level
HttpResponse
method vs. Django's more commonrender()
method to generate a response. While it's possible to use Django'srender()
method and further pass the data to a template to format the response, for most REST services this is unnecessary as responses tend to be raw data (e.g. JSON, XML) and can be generated without a backing template. In addition, notice in listing 12-1 theHttpResponse
uses thecontent_type
argument which adds the HTTP Content-Typeapplication/json
header telling the requesting party the response is JSON. The HTTP Content-Type response value is important because it avoids consumers having to guess how to process a REST service response (e.g. another Content-Type option can beapplication/xml
for REST services with XML responses). HTTP response handling for Django view methods is discussed in greater detail in Chapter 2. - (No) Query parameters.- For simplicity, the view method in listing 12-1 has no parameters, so it provides a very rigid output (i.e. all stores in a JSON format)
One of the most common errors you're likely to encounter when you create REST services in Django is the 'Is not JSON/XML serializable' error. This means Django isn't capable of serializing (i.e. representing/converting) the source data into the selected format -- JSON or XML. This error happens when the source data is made up of objects or data types that can produce undetermined or ambiguous serializing results.
For example, if you have a
Store
model class with relationships, how should Django serialize these relationships ? Similarly, if you have a Pythondatetime
instance how should Django serialize the value, as DD/MM/YYYY, DD-MM-YYYY or something else ? In every case, Django never attempts to guess the serialization representation, so unless the source data is naturally serializable -- as in listing 12-1 -- you must explicitly specify a serialization scheme -- as I'll explain next -- or you'll get the 'Is not JSON/XML serializable' error.
Now that you have a brief understanding of a simple REST service in Django, let's rework the REST service in listing 12-2 to make use of parameters so it can return different data results and data formats.
Listing 12-2. Standard view method as REST service with parameters and different output formats
# urls.py (In stores app) from coffeehouse.stores import views as stores_views urlpatterns = [ url(r'^rest/$',stores_views.rest_store,name="rest_index"), url(r'^(?P<store_id>\d+)/rest/$',stores_views.rest_store,name="rest_detail"), ] # views.py (In stores app) from django.http import HttpResponse from coffeehouse.stores.models import Store from django.core import serializers def rest_store(request,store_id=None): store_list = Store.objects.all() if store_id: store_list = store_list.filter(id=store_id) if 'type' in request.GET and request.GET['type'] == 'xml': serialized_stores = serializers.serialize('xml',store_list) return HttpResponse(serialized_stores, content_type='application/xml') else: serialized_stores = serializers.serialize('json',store_list) return HttpResponse(serialized_stores, content_type='application/json')
The first thing that's different
in listing 12-2 is an additional url statement that accepts
requests with a store_id
parameter (e.g.
/stores/1/rest/
, /stores/2/rest/
). In
this case, the new url is processed by the same
rest_store()
view method to process the stores index
from listing 12-1. (i.e. /stores/rest/
)
Next, the view method in listing
12-2 is a modified version of the view method in listing 12-1 which
accepts a store_id
parameter. This modification allows
the REST service to process a url request for all stores
(e.g./stores/rest/
) or individual stores
(e.g./stores/1/rest/
for store number 1
).
If you're unfamiliar on how to set up Django urls with parameters,
see Chapter 2 which describes url parameters.
Once inside the view method, a
query is made to get all Store
records from a
database. In case the method receives a store_id
value, an additional filter is applied on the query to limit the
results to the given store_id
. This last query logic
is perfectly efficient due to the way Django queries work -- if
you're unfamiliar with the behavior in Django queries, see Chapter
8 'Understanding a QuerySet' section.
Next, an inspection is made on
the request
to see if it has the type
parameter with an xml
value. If a request matches this
last rule (e.g. /stores/rest/?type=xml
) then an XML
response is generated for the REST service, if it doesn't match
this last rule, then a JSON response -- the default -- is generated
for the REST service.
While the example in listing 12-2
also uses HttpResponse
and the
content_type
argument just like listing 12-1, notice
the data is prepared with Django's
django.core.serializers
package which is designed to
make data serialization easier -- unlike the REST service in
listing 12-1 which required pre-processing the query data with a
listing comprehension and then using Python's json
package to complete the process.
The use of
django.core.serializers
is very simple in listing
12-2. The serialize()
method is used which expects the
serialization type as its first argument (e.g. 'xml'
or 'json'
) and the data to serialize as the second
argument (e.g. the store_list
reference which
represents the query). There's much more functionality behind the
django.core.serializers package
(e.g. filters) that I
won't explore here to keep things simple, but figures 12-1 and 12-2
show two sample results from the REST service in listing 12-2.
Figure 12-1. JSON output from Django REST serviceXML output from Django REST service
Figure 12-2. XML output from Django REST service
As you can see in figures 12-1 and 12-2, Django is capable of serializing a query and outputting its result as a REST service response in either JSON or XML, with just the few lines in listing 12-2. It's likely these few lines from listing 12-2 can take you a long way toward building your own REST services in Django, but if you've never done REST services or plan to use REST services as the centerpiece of an application or web site, you should pause to analyze your requirements.
Because REST services solve a common problem in an easy and reusable manner they tend to suffer from scope creep, that characteristic where changes appear to be never ending and functionality is always 'not quite finished'. After seeing this initial Django REST service example, ask yourself the following questions:
- Do you need to support REST services for more than a couple of Django model types ? (e.g. Store,Drink,Employee)
- Do you need to customize the JSON/XML response to something that doesn't map directly to Django model records ? (e.g. use a custom schema, filter certain attributes)
- Do you need to provide a friendly interface for users, describing what a REST service does and what parameters it accepts ?
- Do you need to support some kind of authentication mechanism so the REST service isn't publicly available ?
- Do you need REST services to support more than just displaying data or read operations ? (e.g. update and delete operations)
If you said yes to most of the previous questions, then your REST services undertaking is beyond basic. While you could continue building on the example in listing 12-2 -- using a standard Django view method and additional Python packages -- to support all of the previous scenarios, supporting these more elaborate REST services features in Django is a path many have gone down before, to the point there are dedicated frameworks for just this purpose.
Django REST framework[3]
The Django REST framework is now in its 3rd version. Compared to writing your own REST services from plain Python/Django packages as I just described, the Django REST framework offers the following advantages:
- A web browsable interface.- Provides a user friendly descriptive page for all REST services (e.g. input parameters, options). Similar to how the Django admin provides a near effortless interface to view a Django project's database, the Django REST framework offers a near effortless interface for end users to discover a Django project's REST services.
- Integrated authentication mechanisms.- To restrict access to REST services and save you time integrating authentication logic, the Django REST framework is tightly integrated with authentication mechanism like OAuth2, HTTP Signature, HTTP digest authentication, JSON Web Token Authentication and HAWK (HTTP Holder-Of-Key Authentication Scheme), among others.
- More flexible and sophisticated serializers.- To avoid having to reinvent the wheel and constantly deal with 'Is not serializable' errors, the Django REST framework has its own serializers designed to work with complex data relationships.
These are just some of the core benefits of using the Django REST framework. As you can realize, if you plan to create more than a couple of REST services, investing the time to learn the Django REST framework is well worth the time vs. dealing with the scaffolding code necessary to deploy REST services using plain Python/Django packages.
Django Tastypie framework[4]
The Django Tastypie framework emerged as an alternative to the Django REST framework. Although the Django Tastypie framework is in its 0.14 version, don't let the pre-1.0 release number fool you, the Django Tastypie framework has been in development since 2010. Although both the Django Tastypie framework and Django REST framework can potentially produce the same results, the Django Tastypie framework has these differences:
- Tastypie provides more default behaviors, making it simpler to configure and setup REST services than with the Django REST framework.
- Tastypie is still the second most used Django REST package[5] -- albeit it gets half the downloads of the Django REST framework -- so it's still an attractive option for many Django projects.
- Tastypie was originally developed by the same creators of Django haystack, which is still the most popular Django search package[6] -- so Tastypie operates on some very solid Python/Django fundamentals.
Even though the Django Tastypie framework is not as mainstream as the Django REST framework, if you feel overwhelmed creating REST services with the latter, you can always try out the former to arrive at a faster REST solution than building your REST services from scratch with plain Python/Django packages.