diff --git a/api-guide/authentication.html b/api-guide/authentication.html new file mode 100644 index 000000000..3f1b9244f --- /dev/null +++ b/api-guide/authentication.html @@ -0,0 +1,223 @@ + +
+ +Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted.
+REST framework provides a number of authentication policies out of the box, and also allows you to implement custom policies.
+Authentication will run the first time either the request.user
or request.auth
properties are accessed, and determines how those properties are initialized.
The default authentication policy may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES
setting. For example.
API_SETTINGS = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+ 'djangorestframework.authentication.SessionAuthentication',
+ )
+}
+
+You can also set the authentication policy on a per-view basis, using the APIView
class based views.
class ExampleView(APIView):
+ authentication_classes = (SessionAuthentication,)
+
+ def get(self, request, format=None):
+ content = {
+ 'user': unicode(request.user), # `django.contrib.auth.User` instance.
+ 'auth': unicode(request.auth), # None
+ }
+ return Response(content)
+
+Or, if you're using the @api_view
decorator with function based views.
@api_view(allowed=('GET',), authentication_classes=(SessionAuthentication,))
+def example_view(request, format=None):
+ content = {
+ 'user': unicode(request.user), # `django.contrib.auth.User` instance.
+ 'auth': unicode(request.auth), # None
+ }
+ return Response(content)
+
+This policy uses HTTP Basic Authentication, signed against a user's username and password. User basic authentication is generally only appropriate for testing.
+Note: If you run UserBasicAuthentication
in production your API must be https
only, or it will be completely insecure. You should also ensure that your API clients will always re-request the username and password at login, and will never store those details to persistent storage.
If successfully authenticated, UserBasicAuthentication
provides the following credentials.
request.user
will be a django.contrib.auth.models.User
instance.request.auth
will be None
.This policy uses HTTP Basic Authentication, signed against a token key and secret. Token basic authentication is appropriate for client-server setups, such as native desktop and mobile clients.
+Note: If you run TokenBasicAuthentication
in production your API must be https
only, or it will be completely insecure.
If successfully authenticated, TokenBasicAuthentication
provides the following credentials.
request.user
will be a django.contrib.auth.models.User
instance.request.auth
will be a djangorestframework.models.BasicToken
instance.This policy uses the OAuth 2.0 protocol to authenticate requests. OAuth is appropriate for server-server setups, such as when you want to allow a third-party service to access your API on a user's behalf.
+If successfully authenticated, OAuthAuthentication
provides the following credentials.
request.user
will be a django.contrib.auth.models.User
instance.request.auth
will be a djangorestframework.models.OAuthToken
instance.This policy uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
+If successfully authenticated, SessionAuthentication
provides the following credentials.
request.user
will be a django.contrib.auth.models.User
instance.request.auth
will be None
.To implement a custom authentication policy, subclass BaseAuthentication
and override the authenticate(self, request)
method. The method should return a two-tuple of (user, auth)
if authentication succeeds, or None
otherwise.
Settings for REST framework are all namespaced in the API_SETTINGS
setting.
+For example your project's settings.py
file might look like this:
API_SETTINGS = {
+ 'DEFAULT_RENDERERS': (
+ 'djangorestframework.renderers.YAMLRenderer',
+ )
+ 'DEFAULT_PARSERS': (
+ 'djangorestframework.parsers.YAMLParser',
+ )
+}
+
+A list or tuple of renderer classes, that determines the default set of renderers that may be used when returning a Response
object.
Default:
+(
+ 'djangorestframework.renderers.JSONRenderer',
+ 'djangorestframework.renderers.DocumentingHTMLRenderer'
+ 'djangorestframework.renderers.TemplateHTMLRenderer'
+)
+
+A list or tuple of parser classes, that determines the default set of parsers used when accessing the request.DATA
property.
Default:
+(
+ 'djangorestframework.parsers.JSONParser',
+ 'djangorestframework.parsers.FormParser'
+)
+
+A list or tuple of authentication classes, that determines the default set of authenticators used when accessing the request.user
or request.auth
properties.
Default if DEBUG
is True
:
(
+ 'djangorestframework.authentication.SessionAuthentication',
+ 'djangorestframework.authentication.UserBasicAuthentication'
+)
+
+Default if DEBUG
is False
:
(
+ 'djangorestframework.authentication.SessionAuthentication',
+)
+
+Default: ()
Default: ()
Default: djangorestframework.serializers.ModelSerializer
Default: djangorestframework.pagination.PaginationSerializer
Default: format
Default: django.contrib.auth.models.AnonymousUser
Default: _method
Default: _content
Default: _content_type
Default: _accept
If you're intending to use the browserable API you'll want to add REST framework's login and logout views. Add the following to your root urls.py
file.
urlpatterns = patterns('',
...
- url(r'^auth', include('djangorestframework.urls', namespace='djangorestframework'))
+ url(r'^api-auth/', include('djangorestframework.urls', namespace='djangorestframework'))
)
General guides to using REST framework.
Describe api_settings
+Describe compat module
+Before we do anything else we'll create a new virtual environment, using virtualenv. This will make sure our package configuration is keep nicely isolated from any other projects we're working on.
mkdir ~/env
-virtualenv --no-site-packages ~/env/djangorestframework
-source ~/env/djangorestframework/bin/activate
+virtualenv --no-site-packages ~/env/tutorial
+source ~/env/tutorial/bin/activate
Now that we're inside a virtualenv environment, we can install our package requirements.
pip install django
pip install djangorestframework
-*Note: To exit the virtualenv environment at any time, just type deactivate
. For more information see the virtualenv documentation.*
Note: To exit the virtualenv environment at any time, just type deactivate
. For more information see the virtualenv documentation.
Okay, we're ready to get coding. To get started, let's create a new project to work with.
@@ -245,7 +247,7 @@ c3.save()We've now got a few comment instances to play with. Let's take a look at serializing one of those instances.
serializer = CommentSerializer(instance=c1)
serializer.data
-# {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
+# {'email': u'leila@example.com', 'content': u'nothing to say', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774, tzinfo=<UTC>)}
At this point we've translated the model instance into python native datatypes. To finalise the serialization process we render the data into json
.
stream = JSONRenderer().render(serializer.data)
@@ -260,7 +262,7 @@ stream
serializer.is_valid()
# True
serializer.object
-# <Comment object at 0x10633b2d0>
+# <Comment: Comment 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.
The wrappers also provide behaviour such as returning 405 Method Not Allowed
responses when appropriate, and handling any ParseError
exception that occurs when accessing request.DATA
with malformed input.
Okay, let's go ahead and start using these new components to write a few views.
-from djangorestframework.decorators import api_view
-from djangorestframework.status import *
+We don't need our JSONResponse
class anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly.
+from blog.models import Comment
+from blog.serializers import CommentSerializer
+from djangorestframework import status
+from djangorestframework.decorators import api_view
+from djangorestframework.response import Response
-@api_view(allow=['GET', 'POST'])
+@api_view(['GET', 'POST'])
def comment_root(request):
"""
List all comments, or create a new comment.
- """
+ """
if request.method == 'GET':
comments = Comment.objects.all()
serializer = CommentSerializer(instance=comments)
@@ -182,12 +188,12 @@ def comment_root(request):
if serializer.is_valid():
comment = serializer.object
comment.save()
- return Response(serializer.data, status=HTTP_201_CREATED)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
- return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST)
+ return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
-Our instance view is an improvement over the previous example. It's slightly more concise, and the code now feels very similar to if we were working with the Forms API.
-@api_view(allow=['GET', 'PUT', 'DELETE'])
+Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.
+@api_view(['GET', 'PUT', 'DELETE'])
def comment_instance(request, pk):
"""
Retrieve, update or delete a comment instance.
@@ -195,7 +201,7 @@ def comment_instance(request, pk):
try:
comment = Comment.objects.get(pk=pk)
except Comment.DoesNotExist:
- return Response(status=HTTP_404_NOT_FOUND)
+ return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = CommentSerializer(instance=comment)
@@ -208,16 +214,16 @@ def comment_instance(request, pk):
comment.save()
return Response(serializer.data)
else:
- return Response(serializer.error_data, status=HTTP_400_BAD_REQUEST)
+ return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
comment.delete()
- return Response(status=HTTP_204_NO_CONTENT)
+ return Response(status=status.HTTP_204_NO_CONTENT)
-This should all feel very familiar - it looks a lot like working with forms in regular Django views.
+This should all feel very familiar - there's not a lot different to working with regular Django views.
Notice that we're 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 we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us.
Adding optional format suffixes to our URLs
-To take advantage of that, let's add support for format suffixes to our API endpoints, so that we can use URLs that explicitly refer to a given format. That means our API will be able to handle URLs such as http://example.com/api/items/4.json.
+To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as http://example.com/api/items/4.json.
Start by adding a format
keyword argument to both of the views, like so.
def comment_root(request, format=None):
@@ -225,7 +231,8 @@ def comment_instance(request, pk):
def comment_instance(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 djangorestframework.urlpatterns import format_suffix_patterns
+from django.conf.urls import patterns, url
+from djangorestframework.urlpatterns import format_suffix_patterns
urlpatterns = patterns('blogpost.views',
url(r'^$', 'comment_root'),
diff --git a/tutorial/3-class-based-views.html b/tutorial/3-class-based-views.html
index e5c774d82..b48dcade6 100644
--- a/tutorial/3-class-based-views.html
+++ b/tutorial/3-class-based-views.html
@@ -94,7 +94,8 @@ margin-top: 5px;
Throttling
Exceptions
Status codes
- Returning URLs
+ Returning URLs
+ Settings
@@ -102,6 +103,7 @@ margin-top: 5px;
@@ -144,9 +146,9 @@ from blog.serializers import ComentSerializer
from django.http import Http404
from djangorestframework.views import APIView
from djangorestframework.response import Response
-from djangorestframework.status import *
+from djangorestframework.status import status
-class CommentRoot(views.APIView):
+class CommentRoot(APIView):
"""
List all comments, or create a new comment.
"""
@@ -161,19 +163,20 @@ class CommentRoot(views.APIView):
comment = serializer.object
comment.save()
return Response(serializer.serialized, status=HTTP_201_CREATED)
- else:
- return Response(serializer.serialized_errors, status=HTTP_400_BAD_REQUEST)
+ return Response(serializer.serialized_errors, status=HTTP_400_BAD_REQUEST)
+
+ comment_root = CommentRoot.as_view()
So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view.
-class CommentInstance(views.APIView):
+class CommentInstance(APIView):
"""
Retrieve, update or delete a comment instance.
"""
def get_object(self, pk):
try:
- return Poll.objects.get(pk=pk)
- except Poll.DoesNotExist:
+ return Comment.objects.get(pk=pk)
+ except Comment.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
@@ -188,54 +191,53 @@ class CommentRoot(views.APIView):
comment = serializer.deserialized
comment.save()
return Response(serializer.data)
- else:
- return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
+ return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
comment = self.get_object(pk)
comment.delete()
- return Response(status=HTTP_204_NO_CONTENT)
-
-That's looking good. Again, it's still pretty similar to the function based view right now.
-Since we're now working with class based views, rather than function based views, we'll also need to update our urlconf slightly.
-from blogpost import views
-from djangorestframework.urlpatterns import format_suffix_patterns
+ return Response(status=status.HTTP_204_NO_CONTENT)
-urlpatterns = patterns('',
- url(r'^$', views.CommentRoot.as_view()),
- url(r'^(?P<id>[0-9]+)$', views.CommentInstance.as_view())
-)
-
-urlpatterns = format_suffix_patterns(urlpatterns)
+ comment_instance = CommentInstance.as_view()
-Okay, we're done. If you run the development server everything should be working just as before.
+That's looking good. Again, it's still pretty similar to the function based view right now.
+Okay, we're done. If you run the development server everything should be working just as before.
Using mixins
One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour.
The create/retrieve/update/delete operations that we've been using so far is going to be pretty simliar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes.
We can compose those mixin classes, to recreate our existing API behaviour with less code.
from blog.models import Comment
from blog.serializers import CommentSerializer
-from djangorestframework import mixins, views
+from djangorestframework import mixins
+from djangorestframework import views
-class CommentRoot(mixins.ListModelQuerysetMixin,
- mixins.CreateModelInstanceMixin,
- views.BaseRootAPIView):
+class CommentRoot(mixins.ListModelMixin,
+ mixins.CreateModelMixin,
+ views.MultipleObjectBaseView):
model = Comment
serializer_class = CommentSerializer
- get = list
- post = create
+ def get(self, request, *args, **kwargs):
+ return self.list(request, *args, **kwargs)
-class CommentInstance(mixins.RetrieveModelInstanceMixin,
- mixins.UpdateModelInstanceMixin,
- mixins.DestroyModelInstanceMixin,
- views.BaseInstanceAPIView):
+ def post(self, request, *args, **kwargs):
+ return self.create(request, *args, **kwargs)
+
+class CommentInstance(mixins.RetrieveModelMixin,
+ mixins.UpdateModelMixin,
+ mixins.DestroyModelMixin,
+ views.SingleObjectBaseView):
model = Comment
serializer_class = CommentSerializer
- get = retrieve
- put = update
- delete = destroy
+ 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)
Reusing generic class based views
That's a lot less code than before, but we can go one step further still. REST framework also provides a set of already mixed-in views.
@@ -243,11 +245,11 @@ class CommentInstance(mixins.RetrieveModelInstanceMixin,
from blog.serializers import CommentSerializer
from djangorestframework import views
-class CommentRoot(views.RootAPIView):
+class CommentRoot(views.RootModelView):
model = Comment
serializer_class = CommentSerializer
-class CommentInstance(views.InstanceAPIView):
+class CommentInstance(views.InstanceModelView):
model = Comment
serializer_class = CommentSerializer
diff --git a/tutorial/4-authentication-permissions-and-throttling.html b/tutorial/4-authentication-permissions-and-throttling.html
index 1e666d71b..022237d11 100644
--- a/tutorial/4-authentication-permissions-and-throttling.html
+++ b/tutorial/4-authentication-permissions-and-throttling.html
@@ -94,7 +94,8 @@ margin-top: 5px;
Throttling
Exceptions
Status codes
- Returning URLs
+ Returning URLs
+ Settings
@@ -102,6 +103,7 @@ margin-top: 5px;
diff --git a/tutorial/5-relationships-and-hyperlinked-apis.html b/tutorial/5-relationships-and-hyperlinked-apis.html
index 2338a7575..997947639 100644
--- a/tutorial/5-relationships-and-hyperlinked-apis.html
+++ b/tutorial/5-relationships-and-hyperlinked-apis.html
@@ -94,7 +94,8 @@ margin-top: 5px;
Throttling
Exceptions
Status codes
- Returning URLs
+ Returning URLs
+ Settings
@@ -102,6 +103,7 @@ margin-top: 5px;
diff --git a/tutorial/6-resource-orientated-projects.html b/tutorial/6-resource-orientated-projects.html
index ddb5ce42f..4b3ccc989 100644
--- a/tutorial/6-resource-orientated-projects.html
+++ b/tutorial/6-resource-orientated-projects.html
@@ -94,7 +94,8 @@ margin-top: 5px;
Throttling
Exceptions
Status codes
- Returning URLs
+ Returning URLs
+ Settings
@@ -102,6 +103,7 @@ margin-top: 5px;