Bits of cleanup

This commit is contained in:
Tom Christie 2012-09-04 12:02:05 +01:00
parent 29dfbabaf5
commit 8457c87196
7 changed files with 104 additions and 43 deletions

View File

@ -11,7 +11,7 @@ import base64
__all__ = ( __all__ = (
'BaseAuthentication', 'BaseAuthentication',
'BasicAuthentication', 'BasicAuthentication',
'UserLoggedInAuthentication' 'SessionAuthentication'
) )
@ -68,7 +68,7 @@ class BasicAuthentication(BaseAuthentication):
return None return None
class UserLoggedInAuthentication(BaseAuthentication): class SessionAuthentication(BaseAuthentication):
""" """
Use Django's session framework for authentication. Use Django's session framework for authentication.
""" """

View File

@ -10,14 +10,13 @@ from djangorestframework.request import Request
def api_view(allowed_methods): 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']) @api_view(['GET', 'POST'])
def my_view(request): def my_view(request):
# request will be an instance of `Request` # request will be an instance of `Request`
# `Response` objects will have .request set automatically
# APIException instances will be handled # APIException instances will be handled
Note that request methods should be in uppercase.
""" """
allowed_methods = [method.upper() for method in allowed_methods] allowed_methods = [method.upper() for method in allowed_methods]
@ -25,17 +24,26 @@ def api_view(allowed_methods):
@wraps(func, assigned=available_attrs(func)) @wraps(func, assigned=available_attrs(func))
def inner(request, *args, **kwargs): def inner(request, *args, **kwargs):
try: try:
request = Request(request) request = Request(request)
if request.method not in allowed_methods: if request.method not in allowed_methods:
return exceptions.MethodNotAllowed(request.method) raise exceptions.MethodNotAllowed(request.method)
response = func(request, *args, **kwargs) response = func(request, *args, **kwargs)
if isinstance(response, Response):
response.request = request response.request = request
return response return response
except exceptions.APIException as exc: except exceptions.APIException as exc:
return Response({'detail': exc.detail}, status=exc.status_code) return Response({'detail': exc.detail}, status=exc.status_code)
except Http404 as exc: except Http404 as exc:
return Response({'detail': 'Not found'}, return Response({'detail': 'Not found'},
status=status.HTTP_404_NOT_FOUND) status=status.HTTP_404_NOT_FOUND)
except PermissionDenied as exc: except PermissionDenied as exc:
return Response({'detail': 'Permission denied'}, return Response({'detail': 'Permission denied'},
status=status.HTTP_403_FORBIDDEN) status=status.HTTP_403_FORBIDDEN)

View File

@ -2,10 +2,31 @@ from djangorestframework import status
from djangorestframework.response import Response 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): class CreateModelMixin(object):
""" """
Create a model instance. Create a model instance.
Should be mixed in with any `APIView` Should be mixed in with any `BaseView`.
""" """
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA) serializer = self.get_serializer(data=request.DATA)
@ -47,7 +68,7 @@ class UpdateModelMixin(object):
self.object = self.get_object() self.object = self.get_object()
serializer = self.get_serializer(data=request.DATA, instance=self.object) serializer = self.get_serializer(data=request.DATA, instance=self.object)
if serializer.is_valid(): if serializer.is_valid():
self.object = serializer.deserialized self.object = serializer.object
self.object.save() self.object.save()
return Response(serializer.data) return Response(serializer.data)
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)

View File

@ -6,7 +6,7 @@ from django.contrib.auth.models import User
from django.test import TestCase, Client from django.test import TestCase, Client
from djangorestframework import status from djangorestframework import status
from djangorestframework.authentication import UserLoggedInAuthentication from djangorestframework.authentication import SessionAuthentication
from djangorestframework.utils import RequestFactory from djangorestframework.utils import RequestFactory
from djangorestframework.parsers import ( from djangorestframework.parsers import (
FormParser, FormParser,
@ -208,7 +208,7 @@ class TestContentParsing(TestCase):
class MockView(APIView): class MockView(APIView):
authentication = (UserLoggedInAuthentication,) authentication = (SessionAuthentication,)
def post(self, request): def post(self, request):
if request.POST.get('example') is not None: 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): 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. doesn't log in.
""" """
content = {'example': 'example'} content = {'example': 'example'}

View File

@ -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 from django.conf.urls.defaults import patterns, url

View File

@ -53,7 +53,7 @@ def get_media_type_params(media_type):
def order_by_precedence(media_type_lst): 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: Precedence is determined by how specific a media type is:
3. 'type/subtype; param=val' 3. 'type/subtype; param=val'
@ -61,11 +61,11 @@ def order_by_precedence(media_type_lst):
1. 'type/*' 1. 'type/*'
0. '*/*' 0. '*/*'
""" """
ret = [[], [], [], []] ret = [set(), set(), set(), set()]
for media_type in media_type_lst: for media_type in media_type_lst:
precedence = _MediaType(media_type).precedence precedence = _MediaType(media_type).precedence
ret[3 - precedence].append(media_type) ret[3 - precedence].add(media_type)
return ret return [media_types for media_types in ret if media_types]
class _MediaType(object): class _MediaType(object):

View File

@ -80,7 +80,7 @@ class APIView(_View):
List of parser classes the view can parse the request with. List of parser classes the view can parse the request with.
""" """
authentication = (authentication.UserLoggedInAuthentication, authentication = (authentication.SessionAuthentication,
authentication.BasicAuthentication) authentication.BasicAuthentication)
""" """
List of all authenticating methods to attempt. List of all authenticating methods to attempt.
@ -217,11 +217,14 @@ class APIView(_View):
else in the view. else in the view.
Returns the final response object. Returns the final response object.
""" """
if isinstance(response, Response):
response.view = self response.view = self
response.request = request response.request = request
response.renderers = self.renderers response.renderers = self.renderers
for key, value in self.headers.items(): for key, value in self.headers.items():
response[key] = value response[key] = value
return response return response
def handle_exception(self, exc): def handle_exception(self, exc):
@ -269,43 +272,43 @@ class APIView(_View):
self.response = self.final(request, response, *args, **kwargs) self.response = self.final(request, response, *args, **kwargs)
return self.response return self.response
def options(self, request, *args, **kwargs):
content = { # Abstract view classes that do not provide any method handlers,
'name': self.get_name(), # but which provide required behaviour for concrete views to build on.
'description': self.get_description(),
'renders': self._rendered_media_types, class BaseView(APIView):
'parses': self._parsed_media_types, """
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)
} }
form = self.get_bound_form() return self.serializer_class(data, context=context)
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 ### class MultipleObjectBaseView(MultipleObjectMixin, BaseView):
class MultipleObjectBaseView(MultipleObjectMixin, APIView):
""" """
Base class for views onto a queryset. Base class for generic views onto a queryset.
""" """
pass 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 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, class ListAPIView(mixins.ListModelMixin,
mixins.MetadataMixin,
MultipleObjectBaseView): MultipleObjectBaseView):
""" """
Concrete view for listing a queryset. Concrete view for listing a queryset.
@ -313,9 +316,13 @@ class ListAPIView(mixins.ListModelMixin,
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.list(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, class RootAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin, mixins.CreateModelMixin,
mixins.MetadataMixin,
MultipleObjectBaseView): MultipleObjectBaseView):
""" """
Concrete view for listing a queryset or creating a model instance. Concrete view for listing a queryset or creating a model instance.
@ -326,8 +333,12 @@ class RootAPIView(mixins.ListModelMixin,
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
return self.create(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, class DetailAPIView(mixins.RetrieveModelMixin,
mixins.MetadataMixin,
SingleObjectBaseView): SingleObjectBaseView):
""" """
Concrete view for retrieving a model instance. Concrete view for retrieving a model instance.
@ -335,10 +346,14 @@ class DetailAPIView(mixins.RetrieveModelMixin,
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.retrieve(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, class InstanceAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin, mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
mixins.MetadataMixin,
SingleObjectBaseView): SingleObjectBaseView):
""" """
Concrete view for retrieving, updating or deleting a model instance. Concrete view for retrieving, updating or deleting a model instance.
@ -351,3 +366,6 @@ class InstanceAPIView(mixins.RetrieveModelMixin,
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) return self.destroy(request, *args, **kwargs)
def options(self, request, *args, **kwargs):
return self.metadata(request, *args, **kwargs)