Django Rest Framework
Django Rest Framework
Documentation
Release 2.3
Tom Christie
Contents
Requirements
Installation
Example
Quickstart
Tutorial
5.1 Quickstart
5.2 Tutorial 1:
5.3 Tutorial 2:
5.4 Tutorial 3:
5.5 Tutorial 4:
5.6 Tutorial 5:
5.7 Tutorial 6:
. . . . . . . . . . . . . . . . . . .
Serialization . . . . . . . . . . . .
Requests and Responses . . . . . .
Class Based Views . . . . . . . . .
Authentication & Permissions . . .
Relationships & Hyperlinked APIs
ViewSets & Routers . . . . . . . .
API Guide
Topics
7.1 Documenting your API . . . . . . . .
7.2 Working with AJAX, CSRF & CORS
7.3 Browser enhancements . . . . . . . .
7.4 The Browsable API . . . . . . . . .
7.5 REST, Hypermedia & HATEOAS . .
7.6 Contributing to REST framework . .
7.7 Issues . . . . . . . . . . . . . . . . .
7.8 Development . . . . . . . . . . . . .
7.9 Documentation . . . . . . . . . . . .
7.10 Third party packages . . . . . . . . .
7.11 Django REST framework 2 . . . . .
7.12 REST framework 2.2 announcement .
7.13 REST framework 2.3 announcement .
7.14 API Changes . . . . . . . . . . . . .
7.15 Other notes . . . . . . . . . . . . . .
7.16 Release Notes . . . . . . . . . . . .
7.17 Credits . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
14
22
25
28
32
35
39
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
41
45
46
48
52
54
54
55
56
58
58
60
65
67
70
71
85
93
Development
95
10 Support
97
11 Security
99
12 License
101
ii
Attention: This a non-official Sphinx version of the Django REST Frameworks documentation, mantained by
Martn Gaitn
As the official documentation is written using Markdown and custom scripts, it cant be rendered using Sphinx nor
take advantage of Read the Docss downloads in many formats.
Django REST framework is a powerful and flexible toolkit that makes it easy to build Web APIs.
Some reasons you might want to use REST framework:
The Web browseable API is a huge usability win for your developers.
Authentication policies including OAuth1a and OAuth 2 out of the box.
Serialization that supports both ORM and non-ORM data sources.
Customizable all the way down - just use regular function-based views if you dont need the more powerful
features
Extensive documentation and great community support.
Used and trusted by large companies such as Mozilla and Eventbrite.
Contents
Contents
CHAPTER 1
Requirements
Chapter 1. Requirements
CHAPTER 2
Installation
If youre intending to use the browsable API youll probably also want to add REST frameworks login and logout
views. Add the following to your root urls.py file.
urlpatterns = patterns(,
...
url(r^api-auth/, include(rest_framework.urls, namespace=rest_framework))
)
Note that the URL path can be whatever you want, but you must include rest_framework.urls with the
rest_framework namespace.
Chapter 2. Installation
CHAPTER 3
Example
Lets take a look at a quick example of using REST framework to build a simple model-backed API.
Well create a read-write API for accessing users and groups.
Any global settings for a REST framework API are kept in a single configuration dictionary named
REST_FRAMEWORK. Start off by adding the following to your settings.py module:
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the serializer_class attribute is not set on a view.
DEFAULT_MODEL_SERIALIZER_CLASS:
rest_framework.serializers.HyperlinkedModelSerializer,
# Use Djangos standard django.contrib.auth permissions,
# or allow read-only access for unauthenticated users.
DEFAULT_PERMISSION_CLASSES: [
rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly
]
}
Dont forget to make sure youve also added rest_framework to your INSTALLED_APPS.
Were ready to create our API now. Heres our projects root urls.py module:
from django.conf.urls import url, patterns, include
from django.contrib.auth.models import User, Group
from rest_framework import viewsets, routers
# ViewSets define the view behavior.
class UserViewSet(viewsets.ModelViewSet):
model = User
class GroupViewSet(viewsets.ModelViewSet):
model = Group
urlpatterns = patterns(,
url(r^, include(router.urls)),
url(r^api-auth/, include(rest_framework.urls, namespace=rest_framework))
)
Chapter 3. Example
CHAPTER 4
Quickstart
Cant wait to get started? The quickstart guide is the fastest way to get up and running, and building APIs with REST
framework.
10
Chapter 4. Quickstart
CHAPTER 5
Tutorial
The tutorial will walk you through the building blocks that make up REST framework. Itll take a little while to get
through, but itll give you a comprehensive understanding of how everything fits together, and is highly recommended
reading.
5.1 Quickstart
Were going to create a simple API to allow admin users to view and edit the users and groups in the system.
Next youll need to get a database set up and synced. If you just want to use SQLite for now, then youll want to edit
your tutorial/settings.py module to include something like this:
DATABASES = {
default: {
ENGINE: django.db.backends.sqlite3,
NAME: database.sql,
USER: ,
PASSWORD: ,
HOST: ,
PORT:
}
}
11
Once youve set up a database and got everything synced and ready to go, open up the apps directory and well get
coding...
5.1.2 Serializers
First up were going to define some serializers in quickstart/serializers.py that well use for our data
representations.
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = (url, username, email, groups)
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = (url, name)
Notice that were using hyperlinked relations in this case, with HyperlinkedModelSerializer. You can also
use primary key and various other relationships, but hyperlinking is good RESTful design.
5.1.3 Views
Right, wed better write some views then. Open quickstart/views.py and get typing.
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from quickstart.serializers import UserSerializer, GroupSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
Rather than write multiple views were grouping together all the common behavior into classes called ViewSets.
12
Chapter 5. Tutorial
We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely
organized as well as being very concise.
Notice that our viewset classes here are a little different from those in the frontpage example, as they include
queryset and serializer_class attributes, instead of a model attribute.
For trivial cases you can simply set a model attribute on the ViewSet class and the serializer and queryset will be
automatically generated for you. Setting the queryset and/or serializer_class attributes gives you more
explicit control of the API behaviour, and is the recommended style for most applications.
5.1.4 URLs
Okay, now lets wire up the API URLs. On to tutorial/urls.py...
from django.conf.urls import patterns, url, include
from rest_framework import routers
from quickstart import views
router = routers.DefaultRouter()
router.register(rusers, views.UserViewSet)
router.register(rgroups, views.GroupViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browseable API.
urlpatterns = patterns(,
url(r^, include(router.urls)),
url(r^api-auth/, include(rest_framework.urls, namespace=rest_framework))
)
Because were using viewsets instead of views, we can automatically generate the URL conf for our API, by simply
registering the viewsets with a router class.
Again, if we need more control over the API URLs we can simply drop down to using regular class based views, and
writing the URL conf explicitly.
Finally, were including default login and logout views for use with the browsable API. Thats optional, but useful if
your API requires authentication and you want to use the browsable API.
5.1.5 Settings
Wed also like to set a few global settings. Wed like to turn on pagination, and we want our API to only be accessible
to admin users. The settings module will be in tutorial/settings.py
INSTALLED_APPS = (
...
rest_framework,
)
REST_FRAMEWORK = {
DEFAULT_PERMISSION_CLASSES: (rest_framework.permissions.IsAdminUser,),
PAGINATE_BY: 10
}
5.1. Quickstart
13
We can now access our API, both from the command-line, using tools like curl...
bash: curl -H Accept: application/json; indent=4 -u admin:password http://127.0.0.1:8000/users/
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"email": "admin@example.com",
"groups": [],
"url": "http://127.0.0.1:8000/users/1/",
"username": "admin"
},
{
"email": "tom@example.com",
"groups": [
],
"url": "http://127.0.0.1:8000/users/2/",
"username": "tom"
}
]
}
Note: The code for this tutorial is available in the tomchristie/rest-framework-tutorial repository on GitHub. The
completed implementation is also online as a sandbox version for testing, available here.
14
Chapter 5. Tutorial
15
Now that were inside a virtualenv environment, we can install our package requirements.
pip install django
pip install djangorestframework
pip install pygments # Well be using this for the code highlighting
Note: To exit the virtualenv environment at any time, just type deactivate. For more information see the virtualenv
documentation.
Once thats done we can create an app that well use to create a simple Web API.
python manage.py startapp snippets
The simplest way to get up and running will probably be to use an sqlite3 database for the tutorial. Edit
the tutorial/settings.py file, and set the default database "ENGINE" to "sqlite3", and "NAME" to
"tmp.db".
DATABASES = {
default: {
ENGINE: django.db.backends.sqlite3,
NAME: tmp.db,
USER: ,
PASSWORD: ,
HOST: ,
PORT: ,
}
}
Well also need to add our new snippets app and the rest_framework app to INSTALLED_APPS.
INSTALLED_APPS = (
...
rest_framework,
snippets,
)
We also need to wire up the root urlconf, in the tutorial/urls.py file, to include our snippet apps URLs.
urlpatterns = patterns(,
url(r^, include(snippets.urls)),
)
Chapter 5. Tutorial
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default=)
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES,
default=python,
max_length=100)
style = models.CharField(choices=STYLE_CHOICES,
default=friendly,
max_length=100)
class Meta:
ordering = (created,)
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: Field is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default=python)
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default=friendly)
17
The first part of serializer class defines the fields that get serialized/deserialized. The restore_object method
defines how fully fledged instances get created when deserializing data.
Notice that we can also use various attributes that would typically be used on form fields, such as
widget=widgets.Textarea. These can be used to control how the serializer should render when displayed
as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as well see
later in the tutorial.
We can actually also save ourselves some time by using the ModelSerializer class, as well see later, but for now
well keep our serializer definition explicit.
Okay, once weve got a few imports out of the way, lets create a couple of code snippets to work with.
from
from
from
from
Weve now got a few snippet instances to play with. Lets take a look at serializing one of those instances.
serializer = SnippetSerializer(snippet)
serializer.data
# {pk: 2, title: u, code: uprint "hello, world"\n, linenos: False, language: upython,
18
Chapter 5. Tutorial
At this point weve translated the model instance into Python native datatypes. To finalize the serialization process we
render the data into json.
content = JSONRenderer().render(serializer.data)
content
# {"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "pytho
...then we restore those native datatypes into to a fully populated object instance.
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.object
# <Snippet: Snippet object>
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start
writing views that use our serializer.
We can also serialize querysets instead of model instances. To do so we simply add a many=True flag to the serializer
arguments.
class.
Open
the
file
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = (id, title, code, linenos, language, style)
19
from
from
from
from
from
from
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs[content_type] = application/json
super(JSONResponse, self).__init__(content, **kwargs)
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == GET:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JSONResponse(serializer.data)
elif request.method == POST:
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
Note that because we want to be able to POST to this view from clients that wont have a CSRF token we need to
mark the view as csrf_exempt. This isnt something that youd normally want to do, and REST framework views
actually use more sensible behavior than this, but itll do for our purposes right now.
Well also need a view which corresponds to an individual snippet, and can be used to retrieve, update or delete the
snippet.
@csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == GET:
serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data)
elif request.method == PUT:
data = JSONParser().parse(request)
20
Chapter 5. Tutorial
Finally we need to wire these views up. Create the snippets/urls.py file:
from django.conf.urls import patterns, url
urlpatterns = patterns(snippets.views,
url(r^snippets/$, snippet_list),
url(r^snippets/(?P<pk>[0-9]+)/$, snippet_detail),
)
Its worth noting that there are a couple of edge cases were not dealing with properly at the moment. If we send
malformed json, or if a request is made with a method that the view doesnt handle, then well end up with a 500
server error response. Still, thisll do for now.
[{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "
{"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "s
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
21
22
Chapter 5. Tutorial
@api_view([GET, POST])
def snippet_list(request):
"""
List all snippets, or create a new snippet.
"""
if request.method == GET:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == POST:
serializer = SnippetSerializer(data=request.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)
Our instance view is an improvement over the previous example. Its a little more concise, and the code now feels very
similar to if we were working with the Forms API. Were also using named status codes, which makes the response
meanings more obvious.
Here is the view for an individual snippet, in the views.py module.
@api_view([GET, PUT, DELETE])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a snippet instance.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == GET:
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == PUT:
serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == DELETE:
23
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
This should all feel very familiar - it is not a lot different from working with regular Django views.
Notice that were no longer explicitly tying our requests or responses to a given content type. request.DATA can
handle incoming json requests, but it can also handle yaml and other formats. Similarly were returning response
objects with data, but allowing REST framework to render the response into the correct content type for us.
and
def snippet_detail(request, pk, format=None):
Now update the urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing
URLs.
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = patterns(snippets.views,
url(r^snippets/$, snippet_list),
url(r^snippets/(?P<pk>[0-9]+)$, snippet_detail),
)
urlpatterns = format_suffix_patterns(urlpatterns)
We dont necessarily need to add these extra url patterns in, but it gives us a simple, clean way of referring to a specific
format.
[{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "
We can control the format of the response that we get back, either by using the Accept header:
curl http://127.0.0.1:8000/snippets/ -H Accept: application/json
curl http://127.0.0.1:8000/snippets/ -H Accept: text/html
# Request JSON
# Request HTML
24
Chapter 5. Tutorial
curl http://127.0.0.1:8000/snippets/.json
curl http://127.0.0.1:8000/snippets/.api
# JSON suffix
# Browsable API suffix
Similarly, we can control the format of the request that we send, using the Content-Type header.
# POST using form data
curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
{"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendl
{"id": 4, "title": "", "code": "print 456", "linenos": true, "language": "python", "style": "friendly
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
25
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.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)
So far, so good. It looks pretty similar to the previous case, but weve got better separation between the different HTTP
methods. Well also need to update the instance view in views.py.
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Thats looking good. Again, its still pretty similar to the function based view right now.
Well also need to refactor our urls.py slightly now were using class based views.
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = patterns(,
url(r^snippets/$, views.SnippetList.as_view()),
url(r^snippets/(?P<pk>[0-9]+)/$, views.SnippetDetail.as_view()),
)
urlpatterns = format_suffix_patterns(urlpatterns)
26
Chapter 5. Tutorial
Okay, were done. If you run the development server everything should be working just as before.
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Well take a moment to examine exactly whats happening here. Were building our view using GenericAPIView,
and adding in ListModelMixin and CreateModelMixin.
The base class provides the core functionality, and the mixin classes provide the .list() and .create() actions.
Were then explicitly binding the get and post methods to the appropriate actions. Simple enough stuff so far.
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
Pretty similar. Again were using the GenericAPIView class to provide the core functionality, and adding in mixins
to provide the .retrieve(), .update() and .destroy() actions.
27
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
Wow, thats pretty concise. Weve gotten a huge amount for free, and our code looks like good, clean, idiomatic
Django.
Next well move onto part 4 of the tutorial, where well take a look at how we can deal with authentication and
permissions for our API.
Wed also need to make sure that when the model is saved, that we populate the highlighted field, using the pygments
code highlighting library.
Well need some extra imports:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
28
Chapter 5. Tutorial
When thats all done well need to update our database tables. Normally wed create a database migration in order to
do that, but for the purposes of this tutorial, lets just delete the database and start again.
rm tmp.db
python ./manage.py syncdb
You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with
the createsuperuser command.
python ./manage.py createsuperuser
Because snippets is a reverse relationship on the User model, it will not be included by default when using the
ModelSerializer class, so we needed to add an explicit field for it.
Well also add a couple of views to views.py. Wed like to just use read-only views for the user representations, so
well use the ListAPIView and RetrieveAPIView generic class based views.
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
29
Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the
patterns in urls.py.
url(r^users/$, views.UserList.as_view()),
url(r^users/(?P<pk>[0-9]+)/$, views.UserDetail.as_view()),
Note: Make sure you also add owner, to the list of fields in the inner Meta class.
This field is doing something quite interesting. The source argument controls which attribute is used to populate
a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in
which case it will traverse the given attributes, in a similar way as it is used with Djangos template language.
The field weve added is the untyped Field class, in contrast to the other typed fields, such as CharField,
BooleanField etc... The untyped Field is always read-only, and will be used for serialized representations,
but will not be used for updating model instances when they are deserialized.
Then, add the following property to both the SnippetList and SnippetDetail view classes.
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
30
Chapter 5. Tutorial
And, at the end of the file, add a pattern to include the login and logout views for the browsable API.
urlpatterns += patterns(,
url(r^api-auth/, include(rest_framework.urls,
namespace=rest_framework)),
)
The r^api-auth/ part of pattern can actually be whatever URL you want to use. The only restriction is that the
included urls must use the rest_framework namespace.
Now if you open up the browser again and refresh the page youll see a Login link in the top right of the page. If you
log in as one of the users you created earlier, youll be able to create code snippets again.
Once youve created a few code snippets, navigate to the /users/ endpoint, and notice that the representation includes
a list of the snippet pks that are associated with each user, in each users snippets field.
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so well always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
Now we can add that custom permission to our snippet instance endpoint, by editing the permission_classes
property on the SnippetDetail class:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
31
Now, if you open a browser again, you find that the DELETE and PUT actions only appear on a snippet instance
endpoint if youre logged in as the same user that created the code snippet.
We can make a successful request by including the username and password of one of the users we created earlier.
curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password
{"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python"
5.5.9 Summary
Weve now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for
the code snippets that they have created.
In part 5 of the tutorial well look at how we can tie everything together by creating an HTML endpoint for our
highlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the
system.
32
Chapter 5. Tutorial
@api_view((GET,))
def api_root(request, format=None):
return Response({
users: reverse(user-list, request=request, format=format),
snippets: reverse(snippet-list, request=request, format=format)
})
Notice that were using REST frameworks reverse function in order to return fully-qualified URLs.
As usual we need to add the new views that weve created in to our URLconf. Well add a url pattern for our new API
root:
url(r^$, api_root),
33
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name=snippet-detail)
class Meta:
model = User
fields = (url, username, snippets)
Notice that weve also added a new highlight field. This field is of the same type as the url field, except that
it points to the snippet-highlight url pattern, instead of the snippet-detail url pattern.
Because weve included format suffixed URLs such as .json, we also need to indicate on the highlight field
that any format suffixed hyperlinks it returns should use the .html suffix.
34
Chapter 5. Tutorial
name=snippet-list),
url(r^snippets/(?P<pk>[0-9]+)/$,
views.SnippetDetail.as_view(),
name=snippet-detail),
url(r^snippets/(?P<pk>[0-9]+)/highlight/$,
views.SnippetHighlight.as_view(),
name=snippet-highlight),
url(r^users/$,
views.UserList.as_view(),
name=user-list),
url(r^users/(?P<pk>[0-9]+)/$,
views.UserDetail.as_view(),
name=user-detail)
))
# Login and logout views for the browsable API
urlpatterns += patterns(,
url(r^api-auth/, include(rest_framework.urls,
namespace=rest_framework)),
)
Note that settings in REST framework are all namespaced into a single dictionary setting, named
REST_FRAMEWORK, which helps keep them well separated from your other project settings.
We could also customize the pagination style if we needed too, but in this case well just stick with the default.
35
ViewSet classes are almost the same thing as View classes, except that they provide operations such as read, or
update, and not method handlers such as get or put.
A ViewSet class is only bound to a set of method handlers at the last moment, when it is instantiated into a set of
views, typically by using a Router class which handles the complexities of defining the URL conf for you.
Here weve used ReadOnlyModelViewSet class to automatically provide the default read-only operations.
Were still setting the queryset and serializer_class attributes exactly as we did when we were using regular
views, but we no longer need to provide the same information to two separate classes.
Next were going to replace the SnippetList, SnippetDetail and SnippetHighlight view classes. We
can remove the three views, and again replace them with a single class.
from rest_framework.decorators import link
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides list, create, retrieve,
update and destroy actions.
Additionally we also provide an extra highlight action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@link(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def pre_save(self, obj):
obj.owner = self.request.user
This time weve used the ModelViewSet class in order to get the complete set of default read and write operations.
Notice that weve also used the @link decorator to create a custom action, named highlight. This decorator can
be used to add any custom endpoints that dont fit into the standard create/update/delete style.
Custom actions which use the @link decorator will respond to GET requests. We could have instead used the
@action decorator if we wanted an action that responded to POST requests.
36
Chapter 5. Tutorial
Notice how were creating multiple views from each ViewSet class, by binding the http methods to the required
action for each view.
Now that weve bound our resources into concrete views, that we can register the views with the URL conf as usual.
urlpatterns = format_suffix_patterns(patterns(snippets.views,
url(r^$, api_root),
url(r^snippets/$, snippet_list, name=snippet-list),
url(r^snippets/(?P<pk>[0-9]+)/$, snippet_detail, name=snippet-detail),
url(r^snippets/(?P<pk>[0-9]+)/highlight/$, snippet_highlight, name=snippet-highlight),
url(r^users/$, user_list, name=user-list),
url(r^users/(?P<pk>[0-9]+)/$, user_detail, name=user-detail)
))
37
router.register(rsnippets, views.SnippetViewSet)
router.register(rusers, views.UserViewSet)
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browseable API.
urlpatterns = patterns(,
url(r^, include(router.urls)),
url(r^api-auth/, include(rest_framework.urls, namespace=rest_framework))
)
Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL
prefix for the views, and the viewset itself.
The DefaultRouter class were using also automatically creates the API root view for us, so we can now delete
the api_root method from our views module.
38
Chapter 5. Tutorial
CHAPTER 6
API Guide
The API guide is your complete reference manual to all the functionality provided by REST framework.
39
40
CHAPTER 7
Topics
41
Chapter 7. Topics
43
44
Chapter 7. Topics
If the python markdown library is installed, then markdown syntax may be used in the docstring, and will be converted
to HTML in the browsable API. For example:
class AccountListView(views.APIView):
"""
Returns a list of all **active** accounts in the system.
For more details on how accounts are activated please [see here][ref].
[ref]: http://example.com/activating-accounts
"""
Note that one constraint of using viewsets is that any documentation be used for all generated views, so for example,
you cannot have differing documentation for the generated list view and detail view.
The OPTIONS method
REST framework APIs also support programmatically accessible descriptions, using the OPTIONS HTTP method. A
view will respond to an OPTIONS request with metadata including the name, description, and the various media types
it accepts and responds with.
When using the generic views, any OPTIONS requests will additionally respond with metadata regarding any POST
or PUT actions available, describing which fields are on the serializer.
You can modify the response behavior to OPTIONS requests by overriding the metadata view method. For example:
def metadata(self, request):
"""
Dont include the view description in OPTIONS responses.
"""
data = super(ExampleView, self).metadata(request)
data.pop(description)
return data
45
7.2.3 CORS
Cross-Origin Resource Sharing is a mechanism for allowing clients to interact with APIs that are hosted on a different
domain. CORS works by requiring the server to include a specific set of headers that allow a browser to determine if
and when cross-domain requests should be allowed.
The best way to deal with CORS in REST framework is to add the required response headers in middleware. This
ensures that CORS is supported transparently, without having to change any behavior in your views.
Otto Yiu maintains the django-cors-headers package, which is known to work correctly with REST framework APIs.
46
Chapter 7. Topics
47
7.4.1 URLs
If you include fully-qualified URLs in your resource output, they will be urlized and made clickable for easy browsing
by humans. The rest_framework package includes a reverse <../api-guide/reverse.md>_ helper for this
purpose.
7.4.2 Formats
By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The
format can be specified using ?format= in the request, so you can look at the raw JSON response in a browser by
adding ?format=json to the URL. There are helpful extensions for viewing JSON in Firefox and Chrome.
7.4.3 Customizing
The browsable API is built with Twitters Bootstrap (v 2.1.1), making it easy to customize the look-and-feel.
To customize the default style, create a template called rest_framework/api.html that extends from
rest_framework/base.html. For example:
templates/rest_framework/api.html
{% extends "rest_framework/base.html" %}
...
48
Chapter 7. Topics
A suitable replacement theme can be generated using Bootstraps Customize Tool. There are also pre-made themes
available at Bootswatch. To use any of the Bootswatch themes, simply download the themes bootstrap.min.css
file, add it to your project, and replace the default one as described above.
You can also change the navbar variant, which by default is navbar-inverse, using the
bootstrap_navbar_variant block. The empty {% block bootstrap_navbar_variant %}{%
endblock %} will use the original Bootstrap navbar style.
Full example:
{% extends "rest_framework/base.html" %}
{% block bootstrap_theme %}
<link rel="stylesheet" href="http://bootswatch.com/flatly/bootstrap.min.css" type="text/css">
{% endblock %}
{% block bootstrap_navbar_variant %}{% endblock %}
For more specific CSS tweaks than simply overriding the default bootstrap theme you can override the style block.
49
Blocks
All of the blocks available in the browsable API base template that can be used in your api.html.
bodyclass - Class attribute for the <body> tag, empty by default.
bootstrap_theme - CSS for the Bootstrap theme.
bootstrap_navbar_variant - CSS class for the navbar.
branding - Branding section of the navbar, see Bootstrap components.
breadcrumbs - Links showing resource nesting, allowing the user to go back up the resources. Its recommended to preserve these, but they can be overridden using the breadcrumbs block.
footer - Any copyright notices or similar footer materials can go here (by default right-aligned).
script - JavaScript files for the page.
style - CSS stylesheets for the page.
title - Title of the page.
userlinks - This is a list of links on the right of the header, by default containing login/logout links. To add
links instead of replace, use {{ block.super }} to preserve the authentication links.
50
Chapter 7. Topics
Components
The browsable API makes use of the Bootstrap tooltips component. Any element with the js-tooltip class and a
title attribute has that title content will display a tooltip on hover events.
Login Template
To add branding and customize the look-and-feel of the login template, create a template called login.html and
add it to your project, eg: templates/rest_framework/login.html. The template should extend from
rest_framework/login_base.html.
You can add your site name or branding by including the branding block:
{% block branding %}
<h3 style="margin: 0 0 20px;">My Site Name</h3>
{% endblock %}
You can also customize the style by adding the bootstrap_theme or style block similar to api.html.
Advanced Customization
Context
51
For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your
site, you can simply choose not to have api.html extend base.html. Then the page content and capabilities are
entirely up to you.
Autocompletion
When a ChoiceField has too many items, rendering the widget containing all the options can become very slow,
and cause the browsable API rendering to perform poorly. One solution is to replace the selector by an autocomplete
widget, that only loads and renders a subset of the available options as needed.
There are a variety of packages for autocomplete widgets, such as django-autocomplete-light. To setup
django-autocomplete-light, follow the installation documentation, add the the following to the api.html
template:
{%
{{
{%
{%
block script %}
block.super }}
include autocomplete_light/static.html %}
endblock %}
You can now add the autocomplete_light.ChoiceWidget widget to the serializer field.
import autocomplete_light
class BookSerializer(serializers.ModelSerializer):
author = serializers.ChoiceField(
widget=autocomplete_light.ChoiceWidget(AuthorAutocomplete)
)
class Meta:
model = Book
Chapter 7. Topics
53
7.6.1 Community
The most important thing you can do to help push the REST framework project forward is to be actively involved
wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we
dont believe that needs to be the case.
If you use REST framework, wed love you to be vocal about your experiences with it - you might consider writing a
blog post about using REST framework, or publishing a tutorial about building a project with a particular Javascript
framework. Experiences from beginners can be particularly helpful because youll be in the best position to assess
which bits of REST framework are more difficult to understand and work with.
Other really great ways you can help move the community forward include helping answer questions on the discussion group, or setting up an email alert on StackOverflow so that you get notified of any new questions with the
django-rest-framework tag.
When answering questions make sure to help future contributors find their way around by hyperlinking wherever
possible to related threads and tickets, and include backlinks from those items if relevant.
7.7 Issues
Its really helpful if you can make sure to address issues on the correct channel. Usage questions should be directed to
the discussion group. Feature requests, bug reports and other issues should be raised on the GitHub issue tracker.
Some tips on good issue reporting:
54
Chapter 7. Topics
When describing issues try to phrase your ticket in terms of the behavior you think needs changing rather than
the code you think need changing.
Search the issue list first for related items, and make sure youre running the latest version of REST framework
before reporting an issue.
If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if
there is a valid issue, and make sure that it gets fixed more quickly if there is one.
Feature requests will often be closed with a recommendation that they be implemented outside of the core REST
framework library. Keeping new feature requests implemented as third party libraries allows us to keep down
the maintainence overhead of REST framework, so that the focus can be on continued stability, bugfixes, and
great documentation.
Closing an issue doesnt necessarily mean the end of a discussion. If you believe your issue has been closed
incorrectly, explain why and well consider if it needs to be reopened.
7.8 Development
To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git
Changes should broadly follow the PEP 8 style conventions, and we recommend you setup your editor to automatically
indicated non-conforming styles.
7.8.1 Testing
To run the tests, clone the repository, and then:
# Setup the virtual environment
virtualenv env
source env/bin/activate
pip install -r requirements.txt
pip install -r optionals.txt
# Run the tests
rest_framework/runtests/runtests.py
7.8. Development
55
You can also use the excellent [tox][tox] testing tool to run the tests against all supported versions of Python and
Django. Install tox globally, and then simply run:
tox
7.9 Documentation
The documentation for REST framework is built from the Markdown source files in the docs directory.
There are many great markdown editors that make working with the documentation really easy. The Mou editor for
Mac is one such editor that comes highly recommended.
56
Chapter 7. Topics
This will build the html output into the html directory.
You can build the documentation and open a preview in a browser window by using the -p flag.
./mkdocs.py -p
2. Links
Links should always use the reference style, with the referenced hyperlinks kept at the end of the document.
Here is a link to [some other thing][other-thing].
More text...
[other-thing]: http://example.com/other/thing
This style helps keep the documentation source consistent and readable.
If you are hyperlinking to another REST framework document, you should use a relative link, and link to the .md
suffix. For example:
7.9. Documentation
57
[authentication]: ../api-guide/authentication.md
Linking in this style means youll be able to click the hyperlink in your markdown editor to open the referenced
document. When the documentation is built, these links will be converted into regular links to HTML pages.
3. Notes
If you want to draw attention to a note or warning, use a pair of enclosing lines, like so:
--**Note:** A useful documentation note.
---
REST framework 2 is an almost complete reworking of the original framework, which comprehensively addresses
some of the original design issues.
58
Chapter 7. Topics
Because the latest version should be considered a re-release, rather than an incremental improvement, weve skipped
a version, and called this release Django REST framework 2.0.
This article is intended to give you a flavor of what REST framework 2 is, and why you might want to give it a try.
7.11.2 Serialization
REST framework 2 includes a totally re-worked serialization engine, that was initially intended as a replacement for
Djangos existing inflexible fixture serialization, and which meets the following design goals:
A declarative serialization API, that mirrors Djangos Forms/ModelForms API.
Structural concerns are decoupled from encoding concerns.
Able to support rendering and parsing to many formats, including both machine-readable representations and
HTML forms.
Validation that can be mapped to obvious and comprehensive error responses.
Serializers that support both nested, flat, and partially-nested representations.
Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations.
Mapping between the internal state of the system and external representations of that state is the core concern of building Web APIs. Designing serializers that allow the developer to do so in a flexible and obvious way is a deceptively
difficult design task, and with the new serialization API we think weve pretty much nailed it.
59
The Request/Response approach leads to a much cleaner API, less logic in the view itself, and a simple, obvious
request-response cycle.
REST framework 2 also allows you to work with both function-based and class-based views. For simple API views
all you need is a single @api_view decorator, and youre good to go.
7.11.7 Documentation
As you can see the documentation for REST framework has been radically improved. It gets a completely new style,
using markdown for the documentation source, and a bootstrap-based theme for the styling.
Were really pleased with how the docs style looks - its simple and clean, is easy to navigate around, and we think it
reads great.
7.11.8 Summary
In short, weve engineered the hell outta this thing, and were incredibly proud of the result.
If youre interested please take a browse around the documentation. The tutorial is a great place to get started.
Theres also a live sandbox version of the tutorial API available for testing.
60
Chapter 7. Topics
61
7.12.3 Community
As of the 2.2 merge, weve also hit an impressive milestone. The number of committers listed in the credits, is now at
over one hundred individuals. Each name on that list represents at least one merged pull request, however large or
small.
Our mailing list and #restframework IRC channel are also very active, and weve got a really impressive rate of
development both on REST framework itself, and on third party packages such as the great django-rest-frameworkdocs package from Marc Gibbons.
62
Chapter 7. Topics
class UserSerializer(serializers.HyperlinkedModelSerializer):
questions = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
fields = (username, questions)
The new syntax is cleaner and more obvious, and the change will also make the documentation cleaner, simplify the
internal API, and make writing custom relational fields easier.
The change also applies to serializers. If you have a nested serializer, you should start using many=True for tomany relationships. For example, a serializer representation of an Album that can contain many Tracks might look
something like this:
class TrackSerializer(serializer.ModelSerializer):
class Meta:
model = Track
fields = (name, duration)
class AlbumSerializer(serializer.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = (album_name, artist, tracks)
Additionally, the change also applies when serializing or deserializing data. For example to serialize a queryset of
models you should now use the many=True flag.
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
This more explicit behavior on serializing and deserializing data makes integration with non-ORM backends such
as MongoDB easier, as instances to be serialized can include the __iter__ method, without incorrectly triggering
list-based serialization, or requiring workarounds.
The implicit to-many behavior on serializers, and the ManyRelatedField style classes will continue to function,
but will raise a PendingDeprecationWarning, which can be made visible using the -Wd flag.
Note: If you need to forcibly turn off the implicit many=True for __iter__ objects behavior, you can now
do so by specifying many=False. This will become the default (instead of the current default of None) once the
deprecation of the implicit behavior is finalised in version 2.4.
Cleaner optional relationships
Serializer relationships for nullable Foreign Keys will change from using the current null=True flag, to instead
using required=False.
For example, is a user account has an optional foreign key to a company, that you want to express using a hyperlink,
you might use the following field in a Serializer class:
current_company = serializers.HyperlinkedRelatedField(required=False)
This is in line both with the rest of the serializer fields API, and with Djangos Form and ModelForm API.
Using required throughout the serializers API means you wont need to consider if a particular field should take
blank or null arguments instead of required, and also means there will be more consistent behavior for how
fields are treated when they are not present in the incoming data.
63
The null=True argument will continue to function, and will imply required=False, but will raise a
PendingDeprecationWarning.
Cleaner CharField syntax
The CharField API previously took an optional blank=True argument, which was intended to differentiate
between null CharField input, and blank CharField input.
In keeping with Djangos CharField API, REST frameworks CharField will only ever return the empty string,
for missing or None inputs. The blank flag will no longer be in use, and you should instead just use the
required=<bool> flag. For example:
extra_details = CharField(required=False)
The blank keyword argument will continue to function, but will raise a PendingDeprecationWarning.
Simpler object-level permissions
Custom permissions classes previously used the signature .has_permission(self, request, view,
obj=None). This method would be called twice, firstly for the global permissions check, with the obj parameter set to None, and again for the object-level permissions check when appropriate, with the obj parameter set to the
relevant model instance.
The global permissions check and object-level permissions check are now separated into two separate methods, which
gives a cleaner, more obvious API.
Global permission checks now use the .has_permission(self, request, view) signature.
Object-level permission checks use a new method .has_object_permission(self, request,
view, obj).
For example, the following custom permission class:
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to view or edit it.
Model instances are expected to include an owner attribute.
"""
def has_permission(self, request, view, obj=None):
if obj is None:
# Ignore global permissions check
return True
return obj.owner == request.user
Now becomes:
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to view or edit it.
Model instances are expected to include an owner attribute.
"""
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
64
Chapter 7. Topics
If youre overriding the BasePermission class, the old-style signature will continue to function, and will correctly
handle both global and object-level permissions checks, but its use will raise a PendingDeprecationWarning.
Note also that the usage of the internal APIs for permission checking on the View class has been cleaned up slightly,
and is now documented and subject to the deprecation policy in all future versions.
More explicit hyperlink relations behavior
When using a serializer with a HyperlinkedRelatedField or HyperlinkedIdentityField, the hyperlinks would previously use absolute URLs if the serializer context included a request key, and fall back to using
relative URLs otherwise. This could lead to non-obvious behavior, as it might not be clear why some serializers
generated absolute URLs, and others do not.
From version 2.2 onwards, serializers with hyperlinked relationships always require a request key to be
supplied in the context dictionary. The implicit behavior will continue to function, but its use will raise a
PendingDeprecationWarning.
65
The best place to get started with ViewSets and Routers is to take a look at the newest section in the tutorial, which
demonstrates their usage.
As of 2.3, you can simply include the field name, and the appropriate serializer field will automatically be used for the
relationship.
class BlogSerializer(serializers.ModelSerializer):
"""
Dont need to specify the comments field explicitly anymore.
"""
class Meta:
model = Blog
fields = (id, title, created, comments)
Similarly, you can now easily include the primary key in hyperlinked relationships, simply by adding the field name
to the metadata.
class BlogSerializer(serializers.HyperlinkedModelSerializer):
"""
This is a hyperlinked serializer, which default to using
a field named url as the primary identifier.
Note that we can now easily also add in the id field.
"""
class Meta:
66
Chapter 7. Topics
model = Blog
fields = (url, id, title, created, comments)
67
There may be cases when removing these bits of API might mean you need to write a little more code if your view has
highly customized behavior, but generally we believe that providing a coarser-grained API will make the views easier
to work with, and is the right trade-off to make for the vast majority of cases.
Note that the listed attributes and methods have never been a documented part of the REST framework API, and as
such are not covered by the deprecation policy.
Simplified methods
The get_object and get_paginate_by methods no longer take an optional queryset argument. This makes
overridden these methods more obvious, and a little more simple.
Using an optional queryset with
PendingDeprecationWarning.
these
methods
continues
to
be
supported,
but
will
raise
The paginate_queryset method no longer takes a page_size argument, or returns a four-tuple of pagination
information. Instead it simply takes a queryset argument, and either returns a page object with an appropriate page
size, or returns None, if pagination is not configured for the view.
Using the page_size argument is still supported and will trigger the old-style return type, but will raise a
PendingDeprecationWarning.
Deprecated attributes
The following attributes are used to control queryset lookup, and have all been moved into a pending deprecation state.
pk_url_kwarg = pk
slug_url_kwarg = slug
slug_field = slug
Their usage is replaced with a single attribute:
lookup_field = pk
This attribute is used both as the regex keyword argument in the URL conf, and as the model field to filter against when
looking up a model instance. To use non-pk based lookup, simply set the lookup_field argument to an alternative
field, and ensure that the keyword argument in the url conf matches the field name.
For example, a view with username based lookup might look like this:
class UserDetail(generics.RetrieveAPIView):
lookup_field = username
queryset = User.objects.all()
serializer_class = UserSerializer
Usage of the old-style attributes continues to be supported, but will raise a PendingDeprecationWarning.
The allow_empty attribute is also deprecated. To use allow_empty=False style behavior you should explicitly
override get_queryset and raise an Http404 on empty querysets.
For example:
68
Chapter 7. Topics
class DisallowEmptyQuerysetMixin(object):
def get_queryset(self):
queryset = super(DisallowEmptyQuerysetMixin, self).get_queryset()
if not queryset.exists():
raise Http404
return queryset
In our opinion removing lesser-used attributes like allow_empty helps us move towards simpler generic view
implementations, making them more obvious to use and override, and re-enforcing the preferred style of developers
writing their own base classes and mixins for custom behavior rather than relying on the configurability of the generic
views.
Usage of the old-style attributes continues to be supported, but will raise a PendingDeprecationWarning.
7.14.3 FileUploadParser
2.3 adds a FileUploadParser parser class, that supports raw file uploads, in addition to the existing multipart
upload support.
7.14.4 DecimalField
2.3 introduces a DecimalField serializer field, which returns Decimal instances.
For most cases APIs using model fields will behave as previously, however if you are using a custom renderer, not
provided by REST framework, then you may now need to add support for rendering Decimal instances to your
renderer implementation.
69
Using an explicit queryset and serializer_class attributes makes the functioning of the view more clear
than using the shortcut model attribute.
It also makes the usage of the get_queryset() or get_serializer_class() methods more obvious.
class AccountListView(generics.RetrieveAPIView):
serializer_class = MyModelSerializer
def get_queryset(self):
"""
Determine the queryset dynamically, depending on the
user making the request.
Note that overriding this method follows on more obviously now
that an explicit queryset attribute is the usual view style.
"""
return self.user.accounts
70
Chapter 7. Topics
7.16.1 Versioning
Minor version numbers (0.0.x) are used for changes that are API compatible. You should be able to upgrade between
minor point releases without any other code changes.
Medium version numbers (0.x.0) may include API changes, in line with the deprecation policy. You should read the
release notes carefully before upgrading between medium point releases.
Major version numbers (x.0.0) are reserved for substantial project milestones. No major point releases are currently
planned.
7.16.3 Upgrading
To upgrade Django REST framework to the latest version, use pip:
pip install -U djangorestframework
You can determine your currently installed version using pip freeze:
pip freeze | grep djangorestframework
71
72
Chapter 7. Topics
[*] Note that the change in page=0 behaviour fixes what is considered to be a bug in how clients can effect the
pagination size. However if you were relying on this behavior you will need to add the following mixin to your list
views in order to preserve the existing behavior.
class DisablePaginationMixin(object):
def get_paginate_by(self, queryset=None):
if self.request.QUERY_PARAMS[self.paginate_by_param] == 0:
return None
return super(DisablePaginationMixin, self).get_paginate_by(queryset)
2.3.7
Date: 16th August 2013
Added APITestClient, APIRequestFactory and APITestCase etc...
Refactor SessionAuthentication to allow esier override for CSRF exemption.
73
Remove Hold down Control message from help_text widget messaging when not appropriate.
Added admin configuration for auth tokens.
Bugfix: AnonRateThrottle fixed to not throttle authenticated users.
Bugfix: Dont set X-Throttle-Wait-Seconds when throttle does not have wait value.
Bugfix: Fixed PATCH button title in browsable API.
Bugfix: Fix issue with OAuth2 provider naive datetimes.
2.3.6
Date: 27th June 2013
Added trailing_slash option to routers.
Include support for HttpStreamingResponse.
Support wider range of default serializer validation when used with custom model fields.
UTF-8 Support for browsable API descriptions.
OAuth2 provider uses timezone aware datetimes when supported.
Bugfix: Return error correctly when OAuth non-existent consumer occurs.
Bugfix: Allow FileUploadParser to correctly filename if provided as URL kwarg.
Bugfix: Fix ScopedRateThrottle.
2.3.5
Date: 3rd June 2013
Added get_url hook to HyperlinkedIdentityField.
Serializer field default argument may be a callable.
@action decorator now accepts a methods argument.
Bugfix: request.user should be still be accessible in renderer context if authentication fails.
Bugfix: The lookup_field option on HyperlinkedIdentityField should apply by default to the url
field on the serializer.
Bugfix:
HyperlinkedIdentityField should continue
slug_url_kwarg, slug_field, in a pending deprecation state.
to
support
pk_url_kwarg,
Bugfix: Ensure we always return 404 instead of 500 if a lookup field cannot be converted to the correct lookup
type. (Eg non-numeric AutoInteger pk lookup)
2.3.4
Date: 24th May 2013
Serializer fields now support label and help_text.
Added UnicodeJSONRenderer.
OPTIONS requests now return metadata about fields for POST and PUT requests.
Bugfix: charset now properly included in Content-Type of responses.
74
Chapter 7. Topics
75
DecimalField support.
Made Login template easier to restyle.
Bugfix: Fix issue with depth>1 on ModelSerializer.
Note: See the 2.3 announcement for full details.
76
Chapter 7. Topics
77
2.2.0
Date: 13th Feb 2013
Python 3 support.
Added a post_save() hook to the generic views.
Allow serializers to handle dicts as well as objects.
Deprecate ManyRelatedField() syntax in favor of RelatedField(many=True)
Deprecate null=True on relations in favor of required=False.
Deprecate blank=True on CharFields, just use required=False.
Deprecate optional obj argument in permissions checks in favor of has_object_permission.
Deprecate implicit hyperlinked relations behavior.
Bugfix: Fix broken DjangoModelPermissions.
Bugfix: Allow serializer output to be cached.
Bugfix: Fix styling on browsable API login.
Bugfix: Fix issue with deserializing empty to-many relations.
Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom
.restore_object() method.
Note: See the 2.2 announcement for full details.
78
Chapter 7. Topics
2.1.16
Date: 14th Jan 2013
Deprecate django.utils.simplejson in favor of Python 2.6s built-in json module.
Bugfix: auto_now, auto_now_add and other editable=False fields now default to read-only.
Bugfix: PK fields now only default to read-only if they are an AutoField or if editable=False.
Bugfix: Validation errors instead of exceptions when serializers receive incorrect types.
Bugfix: Validation errors instead of exceptions when related fields receive incorrect types.
Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one
Note: Prior to 2.1.16, The Decimals would render in JSON using floating point if simplejson was installed, but
otherwise render using string notation. Now that use of simplejson has been deprecated, Decimals will consistently
render using string notation. See #582 for more details.
2.1.15
Date: 3rd Jan 2013
Added PATCH support.
Added RetrieveUpdateAPIView.
Remove unused internal save_m2m flag on ModelSerializer.save().
Tweak behavior of hyperlinked fields with an explicit format suffix.
Relation changes are now persisted in .save() instead of in .restore_object().
Bugfix: Fix issue with FileField raising exception instead of validation error when files=None.
Bugfix: Partial updates should not set default values if field is not included.
2.1.14
Date: 31st Dec 2012
Bugfix: ModelSerializers now include reverse FK fields on creation.
Bugfix: Model fields with blank=True are now required=False by default.
Bugfix: Nested serializers now support nullable relationships.
Note: From 2.1.14 onwards, relational fields move out of the fields.py module and into the new relations.py
module, in order to separate them from regular data type fields, such as CharField and IntegerField.
This change will not affect user code, so long as its following the recommended import style
of from rest_framework import serializers and referring to fields using the style
serializers.PrimaryKeyRelatedField.
2.1.13
Date: 28th Dec 2012
Support configurable STATICFILES_STORAGE storage.
Bugfix: Related fields now respect the required flag, and may be required=False.
79
2.1.12
Date: 21st Dec 2012
Bugfix: Fix bug that could occur using ChoiceField.
Bugfix: Fix exception in browsable API on DELETE.
Bugfix: Fix issue where pk was was being set to a string if set by URL kwarg.
2.1.11
Date: 17th Dec 2012
Bugfix: Fix issue with M2M fields in browsable API.
2.1.10
Date: 17th Dec 2012
Bugfix: Ensure read-only fields dont have model validation applied.
Bugfix: Fix hyperlinked fields in paginated results.
2.1.9
Date: 11th Dec 2012
Bugfix: Fix broken nested serialization.
Bugfix: Fix Meta.fields only working as tuple not as list.
Bugfix: Edge case if unnecessarily specifying required=False on read only field.
2.1.8
Date: 8th Dec 2012
Fix for creating nullable Foreign Keys with as well as None.
Added null=<bool> related field option.
2.1.7
Date: 7th Dec 2012
Serializers now properly support nullable Foreign Keys.
Serializer validation now includes model field validation, such as uniqueness constraints.
Support true and false string values for BooleanField.
Added pickle support for serialized data.
Support source=dotted.notation style for nested serializers.
Make Request.user settable.
Bugfix: Fix RegexField to work with BrowsableAPIRenderer.
80
Chapter 7. Topics
2.1.6
Date: 23rd Nov 2012
Bugfix: Unfix DjangoModelPermissions. (I am a doofus.)
2.1.5
Date: 23rd Nov 2012
Bugfix: Fix DjangoModelPermissions.
2.1.4
Date: 22nd Nov 2012
Support for partial updates with serializers.
Added RegexField.
Added SerializerMethodField.
Serializer performance improvements.
Added obtain_token_view to get tokens when using TokenAuthentication.
Bugfix: Django 1.5 configurable user support for TokenAuthentication.
2.1.3
Date: 16th Nov 2012
Added FileField and ImageField. For use with MultiPartParser.
Added URLField and SlugField.
Support for read_only_fields on ModelSerializer classes.
Support for clients overriding the pagination page sizes. Use the PAGINATE_BY_PARAM setting or set the
paginate_by_param attribute on a generic view.
201 Responses now return a Location header.
Bugfix: Serializer fields now respect max_length.
2.1.2
Date: 9th Nov 2012
Filtering support.
Bugfix: Support creation of objects with reverse M2M relations.
81
2.1.1
Date: 7th Nov 2012
Support use of HTML exception templates. Eg. 403.html
Hyperlinked fields take optional slug_field, slug_url_kwarg and pk_url_kwarg arguments.
Bugfix: Deal with optional trailing slashes properly when generating breadcrumbs.
Bugfix: Make textareas same width as other fields in browsable API.
Private API change: .get_serializer now uses same instance and data ordering as serializer initialization.
2.1.0
Date: 5th Nov 2012
Serializer instance and data keyword args have their position swapped.
queryset argument is now optional on writable model fields.
Hyperlinked related fields optionally take slug_field and slug_url_kwarg arguments.
Support Djangos cache framework.
Minor field improvements. (Dont stringify dicts, more robust many-pk fields.)
Bugfix: Support choice field in Browsable API.
Bugfix: Related fields with read_only=True do not require a queryset argument.
API-incompatible changes: Please read this thread regarding the instance and data keyword args before updating to 2.1.0.
82
Chapter 7. Topics
2.0.0
Date: 30th Oct 2012
Fix all of the things. (Well, almost.)
For more information please see the 2.0 announcement.
83
0.3.2
Bugfixes:
Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115)
serialize_model method in serializer.py may cause wrong value (#73)
Fix Error when clicking OPTIONS button (#146)
And many other fixes
Remove short status codes
Zen of Python: There should be one and preferably only one obvious way to do it.
get_name, get_description become methods on the view - makes them overridable.
Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering
0.3.1
[not documented]
0.3.0
JSONP Support
Bugfixes, including support for latest markdown release
84
Chapter 7. Topics
0.2.1
Couple of simple bugfixes over 0.2.0
0.2.0
Big refactoring changes since 0.1.0, ask on the discussion group if anything isnt clear. The public API has been
massively cleaned up. Expect it to be fairly stable from here on in.
Resource becomes decoupled into View and Resource, your views should now inherit from View, not
Resource.
The handler functions on views .get() .put() .post() etc, no longer have the content and auth
args. Use self.CONTENT inside a view to access the deserialized, validated content. Use self.user inside
a view to access the authenticated user.
allowed_methods and anon_allowed_methods are now defunct. if a method is defined, its available.
The permissions attribute on a View is now used to provide generic permissions checking. Use permission classes such as FullAnonAccess, IsAuthenticated or IsUserOrIsAnonReadOnly to set the
permissions.
The authenticators class becomes authentication. Class names change to Authentication.
The emitters class becomes renderers. Class names change to Renderers.
ResponseException becomes ErrorResponse.
The mixin classes have been nicely refactored, the basic mixins are now RequestMixin, ResponseMixin,
AuthMixin, and ResourceMixin You can reuse these mixin classes individually without using the View
class.
7.17 Credits
The following people have helped make REST framework great.
Tom Christie - tomchristie
Marko Tibold - markotibold
Paul Miller - paulmillr
Sbastien Piquemal - sebpiq
Carmen Wick - cwick
7.17. Credits
85
Sebastian Zurek
- sebzur
Benoit C - dzen
Chris Pickett - bunchesofdonald
Ben Timby - btimby
Michele Lazzeri - michelelazzeri-nextage
Camille Harang - mammique
Paul Oswald - poswald
Sean C. Farley - scfarley
Daniel Izquierdo - izquierdo
Can Yavuz - tschan
Shawn Lewis - shawnlewis
Alec Perkins - alecperkins
Michael Barrett - phobologic
Mathieu Dhondt - laundromat
Johan Charpentier - cyberj
86
Chapter 7. Topics
7.17. Credits
87
88
Chapter 7. Topics
7.17. Credits
89
90
Chapter 7. Topics
7.17.2 Contact
For usage questions please see the REST framework discussion group.
You can also contact @_tomchristie directly on twitter.
7.17. Credits
91
92
Chapter 7. Topics
CHAPTER 8
genindex
modindex
search
93
94
CHAPTER 9
Development
If you want to work on REST framework itself, clone the repository, then...
Run the tests:
./rest_framework/runtests/runtests.py
To run the tests against all supported configurations, first install the tox testing tool globally, using pip install
tox, then simply run tox:
tox
95
96
Chapter 9. Development
CHAPTER 10
Support
For support please see the REST framework discussion group, try the #restframework channel on
irc.freenode.net, search the IRC archives, or raise a question on Stack Overflow, making sure to include
the django-rest-framework tag.
Paid support is available from DabApps, and can include work on REST framework core, or support with building
your REST framework API. Please contact DabApps if youd like to discuss commercial support options.
For updates on REST framework development, you may also want to follow the author on Twitter.
Follow @_tomchristie
97
98
CHAPTER 11
Security
If you believe youve found something in Django REST framework which has security implications, please do not
raise the issue in a public forum.
Send a description of the issue via email to rest-framework-security@googlegroups.com. The project maintainers will
then work with you to resolve any issues where required, prior to any public disclosure.
99
100
CHAPTER 12
License
101