Django REST Framework: Get Started Fast
Table of Contents
- TL;DR Django REST Framework
- Introduction to the Django REST Framework
- TL;DR: Let’s get started fast
- Conclusion
TL;DR Django REST Framework
If you’re not interested in the nuts and bolts of the Django REST Framework (DRF), just jump to the tutorial bit to know how to get your API off the ground in five minutes or less. Afterwards, come back and check out the details of how each part of the DRF tutorial is put together and what additional features and functionality you can get from DRF.
Introduction to the Django REST Framework
The Django REST Framework is like the Django for RESTful APIs. It provides so much out of the box for you that just by installing and hooking it up to Django, you get a ton of functionality without having to write much code at all. For big, complex projects, that’s a huge relief for anyone maintaining that code.
Django REST Framework comes with Serializers that can directly replace Django Forms for api-based validation and object creation, generic view classes to get your API endpoints up and running fast, and an automatically generated browsable API for any of the Django REST Framework views you use.
These are just a few of the great things that Django REST Framework comes with, and just like Django and any other Python package, you can take the parts you want and leave the rest alone allowing you to ease into something like converting your existing APIs to Django REST Framework without having to learn the entire framework.
Let’s briefly look at some of the things that make Django REST Framework so powerful.
Serializers
When I first started working with Django REST Framework, I already had my own APIs built using Django Generic Views and was struggling with serializing the complex data I had. Importing and using the Django REST Framework serializers took all that headache away. There are several different serializers you can use, and you can customize each different type. Out of the box, the serializers handle complex Django model instances with ease and the API for using the serializers is intuitive and well documented.
Django REST Framework serializers are so similar to Django forms, you shouldn’t have any trouble picking them up. Let’s look at a Django REST Framework `ModelSerializer` and compare that to a Django ModelForm:
"""
Forms for Character model
"""
from django import forms
from characters.models import Character
class CharacterCreateView(forms.ModelForm):
class Meta:
model = Character
fields = ('name', 'description', 'profession', 'mentor', 'team', 'type',)
class CharacterUpdateView(forms.ModelForm):
class Meta:
model = Character
fields = ('name', 'description', 'profession', 'mentor', 'team', 'type',)
"""
Serializers for Character model
"""
from rest_framework import serializers
from characters.models import Character
class CharacterSerializer(serializers.ModelSerializer):
mentor = serializers.StringRelatedField()
team = serializers.StringRelatedField()
random_line = serializers.SerializerMethodField()
@staticmethod
def get_random_line(obj):
return obj.get_line()
class Meta:
model = Character
fields = (
'name', 'description', 'profession', 'mentor', 'team', 'type', 'random_line',
)
You can see that the definition is almost identical. The differences in this simple example are the StringRelatedField
fields and get_random_line
method on the CharacterSerializer
. These extra pieces are used to add additional information to the serialized output.
The StringRelatedField
takes a related model and outputs its __str__
representation when serialized, and the get_random_line
calls a method on the model and adds that to the serialized output. DRF Serializers allow you to customize, add, and exclude any data you choose from your serialized output.
Just like Django ModelForms
, the ModelSerializer
also provides a create
and update
method, so you are able to create and update model instances through your serializer and API endpoints.
Another powerful feature of Django REST Framework Serializers is that aside from just form processing, they can be used to serialize data in _bulk_
. You can serialize entire QuerySets into JSON, without any modification.
Views
Django REST Framework provides generic class-based views that you can just use out of the box. These views will produce both the browsable API and the JSON API formats for you automatically.
The configuration for the Django REST Framework class-based views is almost identical to Django class-based views so you should be able to pick up the syntax immediately.
There are views provided for listing, creating, retrieving, destroying, and updating. These can all be used individually as class-based views and hooked into whatever URL route you want, or pieces of them can be “mixed in” together to include or exclude specific endpoint actions or functionality from your views.
For the majority of your needs though, Django REST Framework did us the favor of combining the endpoints that go together based on the REST spec, giving us the ListCreateAPIView
, RetrieveUpdateAPIView
, RetrieveDestroyAPIView
, and finally the RetrieveUpdateDestroyAPIView
.
To go even a step further, Django REST Framework provides ViewSets
which are single view classes that provide all of the view functionality for a given endpoint. That’s five endpoints, not to mention the built-in browsable API all for free. That’s brilliant.
So if you’re using Django generic class-based views, you’ll be able to create almost identical views using DRF class-based views to produce your API in the same manor.
The best part is that these views can all be based on Django Model classes, so that as your data model changes, your endpoints will stay up to date without you having to maintain them.
The Browsable API
I’ve mentioned the browsable API a few times already because it is such a gift. Having a browsable API can serve as both documentation as well as a troubleshooting and smoke-testing tool for your APIs. Not having to actually write that documentation saves a ton of time.
The browsable API is also customizable if you don’t like the default formatting or style of the produced pages. There are third party tools that get a lot of attention for doing what the browsable API does; they allow you to query your API endpoints and visualize the data in a clean, even beautiful way. You get that baked right in with the Django REST Framework!
URLs
The way DRF handles URLs is again modeled after Django itself. When you add a view to your project, you need to add a URL route for that view. DRF provides some great utilities to go along with their combined view classes, so the routes are automatically created. By using the provider Router classes, your views will be hooked up and function as expected with very little work on your part
from rest_framework import routers
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls
You can, of course, also hook URLs up to views the exact same way you connect Django views to URL routes:
from django.urls import path, include
urlpatterns = [
path('<int:pk>', views.CharacterDetailView.as_view(), name='get_update_delete'),
]
DRF Response and Requests
Much like Django, Django REST Framework, has its own special Response
and Request
classes. These are based on the Django HttpRequest
and TemplateResponse
classes respectively, and are redesigned to be easier to use when working with APIs and the nature of API data.
Django REST Framework Response
The DRF Response
class gives you a .data
attribute which is similar to request.POST
, but is available even when POST
isn’t the request method used, and additionally, .data
handles arbitrary data where POST
only handles form data submitted through a form in a browser.
Django REST Framework Request
The DRF Response
class is mostly the same as its Django counterpart, except that it renders the content it has based on the content-type
being used by the client. That means that if you use a browser, or the command line, or a programmatic interface to interact with the API, the response you get will automatically be what’s best for your client. The content-type
returned will match the content-type you sent your data in. However, you can also specify the response content-type
you want by specifying the endpoint extension when you make your call, ie /endpoint.json
TL;DR: Let’s get started fast
For this tutorial, I will be using Django 2.2 and Python 3.6.7. You can access the code from this tutorial on Kite’s Github repository.
You’re writing a Superhero fan fiction app and want to be able to move from the web to mobile. To do that you need an API. You need your API yesterday, so let’s make that happen as quickly as possible:
First, create your serializer for your model. This serves as both the input validation, and the output serialization for your API responses:
Use ModelSerializer to quickly serialize an existing model
Here’s our models.py with the models and relationships defined:
"""
Character models
"""
import random
from django.db import models
from django.urls import reverse_lazy
from .constants import CHARACTER_TYPES, EXTRA
class Character(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
profession = models.CharField(max_length=50) # Choices later
mentor = models.ForeignKey('Character', models.DO_NOTHING, related_name='proteges', null=True, blank=True)
team = models.ForeignKey('Team', models.DO_NOTHING, null=True, blank=True)
type = models.CharField(max_length=20, choices=CHARACTER_TYPES, default=EXTRA)
def __str__(self):
return '{name} ({team_name})'.format(name=self.name, team_name=self.team.name)
@staticmethod
def get_random_line():
try:
return random.choice(Line.objects.all())
except IndexError:
return 'Say what..?'
def get_random_line_modifier(self):
try:
return random.choice(self.line_modifiers.all())
except IndexError:
return ''
def get_line(self):
return '{} {}'.format(self.get_random_line(), self.get_random_line_modifier())
def get_absolute_url(self):
return reverse_lazy('characters:detail', kwargs={'pk': self.pk})
class Team(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
class LineModifier(models.Model):
character = models.ForeignKey('Character', models.DO_NOTHING, related_name='line_modifiers')
modifier = models.CharField(max_length=50)
def __str__(self):
return self.modifier
class Line(models.Model):
line_text = models.TextField()
def __str__(self):
return self.line_text
And the only serializer we need, we looked at earlier:
"""
Serializers for Character model
"""
from rest_framework import serializers
from characters.models import Character
class CharacterSerializer(serializers.ModelSerializer):
mentor = serializers.StringRelatedField()
team = serializers.StringRelatedField()
random_line = serializers.SerializerMethodField()
@staticmethod
def get_random_line(obj):
return obj.get_line()
class Meta:
model = Character
fields = (
'name', 'description', 'profession', 'mentor', 'team', 'type', 'random_line',
)
Use ModelViewSet
to get an API view done in seconds
For our app, we only need to expose the Characters model for now, so that your model app can CRUD (Create, Retrieve, Update, and Destroy) Characters. The other models will be exposed read-only via the Characters serializer, but we won’t allow people to add those models through the API.
So our using the ModelViewSet
looks like this:
"""
Views for the Character API
"""
from characters.models import Character
from characters.serializers import CharacterSerializer
from rest_framework import viewsets
class CharacterViewSet(viewsets.ModelViewSet):
queryset = Character.objects.all()
serializer_class = CharacterSerializer
Yep, that’s it. Django REST Framework is that powerful. That gives us List, Create, Retrieve, Update (both full and partial), and Destroy actions.
Lean on the DefaultRouter
for quick routes defined from your ViewSet
To hook all this together, we just need to add a route so that calls can be routed to our new ViewSet
.
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'characters', views.CharacterViewSet)
v3_patterns = [
path('', include(router.urls)),
]
By using the DRF DefaultRouter
class with a ViewSet
, all the routes are created automatically, HTTP verbs are handled appropriately, and even content-type switching works without any extra configuration. From the browsable API, we can confirm that the json looks the way we expected, GET
, PUT
, PATCH
, and DELETE
all exist and work as expected. It’s pretty magical how much utility we get from using the built-in DRF classes. DRF is so comprehensive, and to RESTful specification, you know your users are going to get what they expect.
Putting it all together
Here’s how things look once you’ve hooked the pieces together.
We have an endpoint to list and create Characters:
Then we have an endpoint to view the detail of a single Character, as well as update, and delete that character:
These endpoints all accept an OPTIONS call to get information about what can be submitted.
Almost all of what you’re looking at here was produced by Django REST Framework. The forms, nice formatting, and the navigation are all just part of DRF doing its thing. The only thing we did was hook up the route and add some data!
Conclusion
The Django REST Framework is pretty amazing. APIs are almost always required in modern day applications because of the rise of mobile. Anything you build will get requests for a mobile native app from day one.
Now, with DRF, you can provide that API to your data and start building a mobile app within minutes. The APIs will all be generated based on your existing data Model so that as your models evolve, so will your API.
What we’ve covered here today is only the tip of the iceberg when it comes to all the functionality that Django REST Framework provides.
DRF also provides Authentication, Endpoint Throttling, Versioning, Pagination, and Caching, just to name a few important pieces that you should be aware of when building for the web.
And just like Django, there are plenty of third-party, well-supported plugins to DRF that you can choose from to save you time and headache of reinventing the wheel.