Realpython.com-Django Rest Framework an Introduction
Realpython.com-Django Rest Framework an Introduction
realpython.com/django-rest-framework-quick-start
Let’s look at how to create a RESTFul API for our Django Talk Project using Django Rest
Framework (DRF), which is an application used for rapidly building RESTful APIs based on
Django models.
Put another way, we’ll be converting a non-RESTful application into a RESTful one with
DRF. We’ll be using DRF version 2.4.2 for this application.
1. DRF Setup
2. RESTful Structure
3. Model Serializer
4. DRF Web Browseable API
Free Bonus: Click here to download a copy of the "REST API Examples" Guide and get a
hands-on introduction to Python + REST API principles with actionable examples.
If you missed parts one and two of this tutorial series, be sure to check them out. Need the
code? Download it from the repo. For a more in-depth tutorial on Django Rest Framework,
check out the third Real Python course.
1/14
DRF setup
Install:
Update settings.py:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'talk',
'rest_framework'
)
Boom!
Remove ads
RESTful Structure
In a RESTful API, endpoints (URLs) define the structure of the API and how end users access
data from our application using the HTTP methods: GET, POST, PUT, DELETE. Endpoints
should be logically organized around collections and elements, both of which are resources.
In our case, we have one single resource, posts , so we will use the following URLS -
/posts/ and /posts/<id> for collections and elements, respectively.
/posts/ Show all posts Add new post Update all posts Delete all posts
Model Serializer
2/14
DRF’s Serializers convert model instances to Python dictionaires, which can then be
rendered in various API appropriate formats - like JSON or XML. Similar to the Django
ModelForm class, DRF comes with a concise format for its Serializers, the
ModelSerializer class. It’s simple to use: Just tell it which fields you want to use from the
model:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'author', 'text', 'created', 'updated')
Update Views
We need to refactor our current views to fit the RESTful paradigm. Comment out the current
views and add in:
3/14
from django.shortcuts import render
from django.http import HttpResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from talk.models import Post
from talk.serializers import PostSerializer
from talk.forms import PostForm
def home(request):
tmpl_vars = {'form': PostForm()}
return render(request, 'talk/index.html', tmpl_vars)
@api_view(['GET'])
def post_collection(request):
if request.method == 'GET':
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
@api_view(['GET'])
def post_element(request, pk):
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = PostSerializer(post)
return Response(serializer.data)
1. First, the @api_view decorator checks that the appropriate HTTP request is passed
into the view function. Right now, we’re only supporting GET requests.
2. Then, the view either grabs all the data, if it’s for the collection, or just a single post, if
it’s for an element.
3. Finally, the data is serialized to JSON and returned.
Be sure to read more about the @api_view from the official documentation.
Update URLs
4/14
# Talk urls
from django.conf.urls import patterns, url
urlpatterns = patterns(
'talk.views',
url(r'^$', 'home'),
# api
url(r'^api/v1/posts/$', 'post_collection'),
url(r'^api/v1/posts/(?P<pk>[0-9]+)$', 'post_element')
)
Test
So, With no extra work on our end we automatically get this nice, human-readable
output of our API. Nice! This is a huge win for DRF.
Before moving on you may have noticed that the author field is an id rather than the actual
username . We’ll address this shortly. For now, let’s wire up our new API so that it works
with our current application’s Templates.
Remove ads
GET
On the initial page load, we want to display all posts. To do that, add the following AJAX
request:
5/14
load_posts()
You’ve seen all this before. Notice how we’re handling a success: Since the API sends back a
number of objects, we need to iterate through them, appending each to the DOM. We also
changed json[i].postpk to json[i].id as we are serializing the post id .
Test this out. Fire up the server, log in, then check out the posts.
Besides the author being displayed as an id , take note of the datetime format. This is not
what we want, right? We want a readable datetime format. Let’s update that…
Datetime Format
We can use an awesome JavaScript library called MomentJS to easily format the date anyway
we want.
6/14
Then update the for loop in main.js:
That’s it. Refresh the browser. The datetime format should now look something like this -
08/22/2014, 6:48:29 pm . Be sure to check out the MomentJS documentation to view
more information on parsing and formatting a datetime string in JavaScript.
POST
POST requests are handled in similar fashion. Before messing with the serializer, let’s test it
first by just updating the views. Maybe we’ll get lucky and it will just work.
@api_view(['GET', 'POST'])
def post_collection(request):
if request.method == 'GET':
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
elif request.method == 'POST':
data = {'text': request.DATA.get('the_post'), 'author': request.user.pk}
serializer = PostSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
7/14
1. request.DATA extends Django’s HTTPRequest, returning the content from the
request body. Read more about it here.
2. If the deserialization process works, we return a response with a code of 201 (created).
3. On the other hand, if the deserialization process fails, we return a 400 response.
From:
To:
Test it out in the browser. It should work. Don’t forget to update the handling of the dates
correctly as well as changing json.postpk to json.id :
success : function(json) {
$('#post-text').val(''); // remove the value from the input
console.log(json); // log the returned json to the console
dateString = convert_to_readable_date(json.created)
$("#talk").prepend("<li id='post-"+json.id+"'><strong>"+json.text+"</strong> -
<em> "+
json.author+"</em> - <span> "+dateString+
"</span> - <a id='delete-post-"+json.id+"'>delete me</a></li>");
console.log("success"); // another sanity check
},
Remove ads
Author Format
Now’s a good time to pause and address the author id vs. username issue. We have a few
options:
1. Be really RESTFUL and make another call to get the user info, which is not good for
performance.
2. Utilize the SlugRelatedField relation.
8/14
from django.contrib.auth.models import User
from rest_framework import serializers
from talk.models import Post
class PostSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(
queryset=User.objects.all(), slug_field='username'
)
class Meta:
model = Post
fields = ('id', 'author', 'text', 'created', 'updated')
1. The SlugRelatedField allows us to change the target of the author field from id
to username .
2. Also, by default the target field - username - is both readable and writeable so out-of-
the-box this relation will work for both GET and POST requests.
Test again. You should now see the author’s username . Make sure both GET and POST
requests are working correctly.
Delete
Before changing or adding anything, test it out. Try the delete link. What happens? You
should get a 404 error. Any idea why that would be? Or where to go to find out what the issue
is? How about the delete_post function in our JavaScript file:
That URL does not exist. Before we update it, ask yourself - “Should we target the collection
or an individual element?”. If you’re unsure, scroll back up and look at the RESTful Structure
table. Unless we want to delete all posts, then we need to hit the element endpoint:
Test again. Now what happens? You should see a 405 error - 405: {"detail": "Method
'DELETE' not allowed."} - since the view is not setup to handle a DELETE request.
9/14
@api_view(['GET', 'DELETE'])
def post_element(request, pk):
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = PostSerializer(post)
return Response(serializer.data)
With the DELETE HTTP verb added, we can handle the request by removing the post with
the delete() method and returning a 204 response. Does it work? Only one way to find
out. This time when you test make sure that (a) the post is actually deleted and removed from
the DOM and (b) that a 204 status code is returned (you can confirm this in the Network tab
within Chrome Developer Tools).
This is all for now. Need an extra challenge? Add the ability to update posts with the PUT
request.
The actual REST part is simple: You just need to update the post_element() function to
handle PUT requests.
The client side is a bit more difficult as you need to update the actual HTML to display an
input box for the user to enter the new value in, which you’ll need to grab in the JavaScript
file so you can send it with the PUT request.
Are you going to allow any user to update any post, regardless of whether s/he originally
posted it?
If so, are you going to update the author name? Maybe add an edited_by field to the
database? Then display an edited by note on the DOM as well. If users can only update their
own posts, you need to make sure you are handling this correctly in the views and then
displaying an error message if the user is trying to edit a post that s/he did not originally
post.
10/14
Or perhaps you could just remove the edit link (and perhaps the delete link as well) for posts
that a certain user can’t edit. You could turn it into a permissions issue and only let certain
users, like moderators or admin, edit all posts while the remaining users can only update
their own posts.
So many questions.
If you do decide to try this, go with the lowest hanging fruit—simply allow any user to update
any post and only update the text in the database. Then test. Then add another iteration.
Then test, etc. Take notes and email us at [email protected] so we can add a
supplementary blog post!
Anyhow, next time you’ll get to see us tear apart the current JavaScript code as we add in
Angular! See you then.
🐍 Python Tricks 💌
Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam
ever. Unsubscribe any time. Curated by the Real Python team.
11/14
About The Team
Each tutorial at Real Python is created by a team of developers so that it meets our high
quality standards. The team members who worked on this tutorial are:
Commenting Tips: The most useful comments are those written with the goal of learning
from or helping out other students. Get tips for asking good questions and get answers to
common questions in our support portal.
12/14
Aldren Santos
13/14
Looking for a real-time conversation? Visit the Real Python Community Chat or join the next
“Office Hours” Live Q&A Session. Happy Pythoning!
Keep Learning
Related Tutorial Categories: api django web-dev
14/14