Commit 8457c871 by Tom Christie

Bits of cleanup

parent 29dfbaba
......@@ -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.
"""
......
......@@ -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)
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)
......
......@@ -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)
......
......@@ -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'}
......
"""
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
......
......@@ -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):
......
......@@ -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.
"""
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,8 +333,12 @@ 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,
mixins.MetadataMixin,
SingleObjectBaseView):
"""
Concrete view for retrieving a model instance.
......@@ -335,10 +346,14 @@ class DetailAPIView(mixins.RetrieveModelMixin,
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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment