aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2012-09-04 12:02:05 +0100
committerTom Christie2012-09-04 12:02:05 +0100
commit8457c871963264c9f62552f30307e98221a1c25d (patch)
tree2048cbfc334aa5fca0d5c8d88fd20e50de667e41
parent29dfbabaf54d75801b404e253a2d7574fa415564 (diff)
downloaddjango-rest-framework-8457c871963264c9f62552f30307e98221a1c25d.tar.bz2
Bits of cleanup
-rw-r--r--djangorestframework/authentication.py4
-rw-r--r--djangorestframework/decorators.py18
-rw-r--r--djangorestframework/mixins.py25
-rw-r--r--djangorestframework/tests/request.py6
-rw-r--r--djangorestframework/urls.py14
-rw-r--r--djangorestframework/utils/mediatypes.py8
-rw-r--r--djangorestframework/views.py70
7 files changed, 103 insertions, 42 deletions
diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py
index 3ddd9e45..197aa424 100644
--- a/djangorestframework/authentication.py
+++ b/djangorestframework/authentication.py
@@ -11,7 +11,7 @@ import base64
__all__ = (
'BaseAuthentication',
'BasicAuthentication',
- 'UserLoggedInAuthentication'
+ 'SessionAuthentication'
)
@@ -68,7 +68,7 @@ class BasicAuthentication(BaseAuthentication):
return None
-class UserLoggedInAuthentication(BaseAuthentication):
+class SessionAuthentication(BaseAuthentication):
"""
Use Django's session framework for authentication.
"""
diff --git a/djangorestframework/decorators.py b/djangorestframework/decorators.py
index ff158367..314ea635 100644
--- a/djangorestframework/decorators.py
+++ b/djangorestframework/decorators.py
@@ -10,14 +10,13 @@ from djangorestframework.request import Request
def api_view(allowed_methods):
"""
- Decorator to make a view only accept particular request methods. Usage::
+ Decorator for function based views.
@api_view(['GET', 'POST'])
def my_view(request):
# request will be an instance of `Request`
+ # `Response` objects will have .request set automatically
# APIException instances will be handled
-
- Note that request methods should be in uppercase.
"""
allowed_methods = [method.upper() for method in allowed_methods]
@@ -25,17 +24,26 @@ def api_view(allowed_methods):
@wraps(func, assigned=available_attrs(func))
def inner(request, *args, **kwargs):
try:
+
request = Request(request)
+
if request.method not in allowed_methods:
- return exceptions.MethodNotAllowed(request.method)
+ raise exceptions.MethodNotAllowed(request.method)
+
response = func(request, *args, **kwargs)
- response.request = request
+
+ if isinstance(response, Response):
+ response.request = request
+
return response
+
except exceptions.APIException as exc:
return Response({'detail': exc.detail}, status=exc.status_code)
+
except Http404 as exc:
return Response({'detail': 'Not found'},
status=status.HTTP_404_NOT_FOUND)
+
except PermissionDenied as exc:
return Response({'detail': 'Permission denied'},
status=status.HTTP_403_FORBIDDEN)
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 2721f59e..7269f298 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -2,10 +2,31 @@ from djangorestframework import status
from djangorestframework.response import Response
+class MetadataMixin(object):
+ """
+ Should be mixed in with any `BaseView`.
+ """
+ def metadata(self, request, *args, **kwargs):
+ content = {
+ 'name': self.get_name(),
+ 'description': self.get_description(),
+ 'renders': self._rendered_media_types,
+ 'parses': self._parsed_media_types,
+ }
+ # TODO: Add 'fields', from serializer info.
+ # form = self.get_bound_form()
+ # if form is not None:
+ # field_name_types = {}
+ # for name, field in form.fields.iteritems():
+ # field_name_types[name] = field.__class__.__name__
+ # content['fields'] = field_name_types
+ raise Response(content, status=status.HTTP_200_OK)
+
+
class CreateModelMixin(object):
"""
Create a model instance.
- Should be mixed in with any `APIView`
+ Should be mixed in with any `BaseView`.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA)
@@ -47,7 +68,7 @@ class UpdateModelMixin(object):
self.object = self.get_object()
serializer = self.get_serializer(data=request.DATA, instance=self.object)
if serializer.is_valid():
- self.object = serializer.deserialized
+ self.object = serializer.object
self.object.save()
return Response(serializer.data)
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
diff --git a/djangorestframework/tests/request.py b/djangorestframework/tests/request.py
index 7ad99c07..2bb90c0a 100644
--- a/djangorestframework/tests/request.py
+++ b/djangorestframework/tests/request.py
@@ -6,7 +6,7 @@ from django.contrib.auth.models import User
from django.test import TestCase, Client
from djangorestframework import status
-from djangorestframework.authentication import UserLoggedInAuthentication
+from djangorestframework.authentication import SessionAuthentication
from djangorestframework.utils import RequestFactory
from djangorestframework.parsers import (
FormParser,
@@ -208,7 +208,7 @@ class TestContentParsing(TestCase):
class MockView(APIView):
- authentication = (UserLoggedInAuthentication,)
+ authentication = (SessionAuthentication,)
def post(self, request):
if request.POST.get('example') is not None:
@@ -233,7 +233,7 @@ class TestContentParsingWithAuthentication(TestCase):
def test_user_logged_in_authentication_has_POST_when_not_logged_in(self):
"""
- Ensures request.POST exists after UserLoggedInAuthentication when user
+ Ensures request.POST exists after SessionAuthentication when user
doesn't log in.
"""
content = {'example': 'example'}
diff --git a/djangorestframework/urls.py b/djangorestframework/urls.py
index 3fa813ea..e446c396 100644
--- a/djangorestframework/urls.py
+++ b/djangorestframework/urls.py
@@ -1,3 +1,17 @@
+"""
+Login and logout views for the browseable API.
+
+Add these to your root URLconf if you're using the browseable API and
+your API requires authentication.
+
+The urls must be namespaced as 'djangorestframework', and you should make sure
+your authentication settings include `SessionAuthentication`.
+
+ urlpatterns = patterns('',
+ ...
+ url(r'^auth', include('djangorestframework.urls', namespace='djangorestframework'))
+ )
+"""
from django.conf.urls.defaults import patterns, url
diff --git a/djangorestframework/utils/mediatypes.py b/djangorestframework/utils/mediatypes.py
index 48dca0f0..5eba7fb2 100644
--- a/djangorestframework/utils/mediatypes.py
+++ b/djangorestframework/utils/mediatypes.py
@@ -53,7 +53,7 @@ def get_media_type_params(media_type):
def order_by_precedence(media_type_lst):
"""
- Returns a list of lists of media type strings, ordered by precedence.
+ Returns a list of sets of media type strings, ordered by precedence.
Precedence is determined by how specific a media type is:
3. 'type/subtype; param=val'
@@ -61,11 +61,11 @@ def order_by_precedence(media_type_lst):
1. 'type/*'
0. '*/*'
"""
- ret = [[], [], [], []]
+ ret = [set(), set(), set(), set()]
for media_type in media_type_lst:
precedence = _MediaType(media_type).precedence
- ret[3 - precedence].append(media_type)
- return ret
+ ret[3 - precedence].add(media_type)
+ return [media_types for media_types in ret if media_types]
class _MediaType(object):
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index 1939eed2..3f0138d8 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -80,7 +80,7 @@ class APIView(_View):
List of parser classes the view can parse the request with.
"""
- authentication = (authentication.UserLoggedInAuthentication,
+ authentication = (authentication.SessionAuthentication,
authentication.BasicAuthentication)
"""
List of all authenticating methods to attempt.
@@ -217,11 +217,14 @@ class APIView(_View):
else in the view.
Returns the final response object.
"""
- response.view = self
- response.request = request
- response.renderers = self.renderers
+ if isinstance(response, Response):
+ response.view = self
+ response.request = request
+ response.renderers = self.renderers
+
for key, value in self.headers.items():
response[key] = value
+
return response
def handle_exception(self, exc):
@@ -269,43 +272,43 @@ class APIView(_View):
self.response = self.final(request, response, *args, **kwargs)
return self.response
- def options(self, request, *args, **kwargs):
- content = {
- 'name': self.get_name(),
- 'description': self.get_description(),
- 'renders': self._rendered_media_types,
- 'parses': self._parsed_media_types,
- }
- form = self.get_bound_form()
- if form is not None:
- field_name_types = {}
- for name, field in form.fields.iteritems():
- field_name_types[name] = field.__class__.__name__
- content['fields'] = field_name_types
- raise Response(content, status=status.HTTP_200_OK)
-# TODO: .get_serializer()
+# Abstract view classes that do not provide any method handlers,
+# but which provide required behaviour for concrete views to build on.
+class BaseView(APIView):
+ """
+ Base class for all generic views.
+ """
+ serializer_class = None
+
+ def get_serializer(self, data=None, files=None, instance=None):
+ context = {
+ 'request': self.request,
+ 'format': self.kwargs.get('format', None)
+ }
+ return self.serializer_class(data, context=context)
-### Abstract view classes, that do not provide any method handlers ###
-class MultipleObjectBaseView(MultipleObjectMixin, APIView):
+class MultipleObjectBaseView(MultipleObjectMixin, BaseView):
"""
- Base class for views onto a queryset.
+ Base class for generic views onto a queryset.
"""
pass
-class SingleObjectBaseView(SingleObjectMixin, APIView):
+class SingleObjectBaseView(SingleObjectMixin, BaseView):
"""
- Base class for views onto a model instance.
+ Base class for generic views onto a model instance.
"""
pass
-### Concrete view classes, that provide existing method handlers ###
+# Concrete view classes that provide method handlers
+# by composing the mixin classes with a base view.
class ListAPIView(mixins.ListModelMixin,
+ mixins.MetadataMixin,
MultipleObjectBaseView):
"""
Concrete view for listing a queryset.
@@ -313,9 +316,13 @@ class ListAPIView(mixins.ListModelMixin,
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
+ def options(self, request, *args, **kwargs):
+ return self.metadata(request, *args, **kwargs)
+
class RootAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
+ mixins.MetadataMixin,
MultipleObjectBaseView):
"""
Concrete view for listing a queryset or creating a model instance.
@@ -326,19 +333,27 @@ class RootAPIView(mixins.ListModelMixin,
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
+ def options(self, request, *args, **kwargs):
+ return self.metadata(request, *args, **kwargs)
+
class DetailAPIView(mixins.RetrieveModelMixin,
- SingleObjectBaseView):
+ mixins.MetadataMixin,
+ SingleObjectBaseView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
+ def options(self, request, *args, **kwargs):
+ return self.metadata(request, *args, **kwargs)
+
class InstanceAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
+ mixins.MetadataMixin,
SingleObjectBaseView):
"""
Concrete view for retrieving, updating or deleting a model instance.
@@ -351,3 +366,6 @@ class InstanceAPIView(mixins.RetrieveModelMixin,
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
+
+ def options(self, request, *args, **kwargs):
+ return self.metadata(request, *args, **kwargs)