Allow views to return HttpResponses. Add initial() hook method

This commit is contained in:
Tom Christie 2011-05-24 10:27:24 +01:00
parent 698df527a3
commit 370274f564
16 changed files with 70 additions and 57 deletions

View File

@ -216,7 +216,7 @@ class RequestMixin(object):
class ResponseMixin(object): class ResponseMixin(object):
""" """
Adds behavior for pluggable `Renderers` to a :class:`views.BaseView` or Django :class:`View` class. Adds behavior for pluggable `Renderers` to a :class:`views.View` class.
Default behavior is to use standard HTTP Accept header content negotiation. Default behavior is to use standard HTTP Accept header content negotiation.
Also supports overriding the content type by specifying an ``_accept=`` parameter in the URL. Also supports overriding the content type by specifying an ``_accept=`` parameter in the URL.

View File

@ -1,6 +1,6 @@
from django.test import TestCase from django.test import TestCase
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.views import BaseView from djangorestframework.views import View
# See: http://www.useragentstring.com/ # See: http://www.useragentstring.com/
@ -19,7 +19,7 @@ class UserAgentMungingTest(TestCase):
def setUp(self): def setUp(self):
class MockView(BaseView): class MockView(View):
permissions = () permissions = ()
def get(self, request): def get(self, request):

View File

@ -6,13 +6,13 @@ from django.test import Client, TestCase
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework import permissions from djangorestframework import permissions
import base64 import base64
class MockView(BaseView): class MockView(View):
permissions = ( permissions.IsAuthenticated, ) permissions = ( permissions.IsAuthenticated, )
def post(self, request): def post(self, request):
return {'a':1, 'b':2, 'c':3} return {'a':1, 'b':2, 'c':3}

View File

@ -1,21 +1,21 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django.test import TestCase from django.test import TestCase
from djangorestframework.utils.breadcrumbs import get_breadcrumbs from djangorestframework.utils.breadcrumbs import get_breadcrumbs
from djangorestframework.views import BaseView from djangorestframework.views import View
class Root(BaseView): class Root(View):
pass pass
class ResourceRoot(BaseView): class ResourceRoot(View):
pass pass
class ResourceInstance(BaseView): class ResourceInstance(View):
pass pass
class NestedResourceRoot(BaseView): class NestedResourceRoot(View):
pass pass
class NestedResourceInstance(BaseView): class NestedResourceInstance(View):
pass pass
urlpatterns = patterns('', urlpatterns = patterns('',

View File

@ -1,5 +1,5 @@
from django.test import TestCase from django.test import TestCase
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.compat import apply_markdown from djangorestframework.compat import apply_markdown
from djangorestframework.utils.description import get_name, get_description from djangorestframework.utils.description import get_name, get_description
@ -35,7 +35,7 @@ MARKED_DOWN = """<h2>an example docstring</h2>
class TestViewNamesAndDescriptions(TestCase): class TestViewNamesAndDescriptions(TestCase):
def test_resource_name_uses_classname_by_default(self): def test_resource_name_uses_classname_by_default(self):
"""Ensure Resource names are based on the classname by default.""" """Ensure Resource names are based on the classname by default."""
class MockView(BaseView): class MockView(View):
pass pass
self.assertEquals(get_name(MockView()), 'Mock') self.assertEquals(get_name(MockView()), 'Mock')
@ -43,13 +43,13 @@ class TestViewNamesAndDescriptions(TestCase):
#def test_resource_name_can_be_set_explicitly(self): #def test_resource_name_can_be_set_explicitly(self):
# """Ensure Resource names can be set using the 'name' class attribute.""" # """Ensure Resource names can be set using the 'name' class attribute."""
# example = 'Some Other Name' # example = 'Some Other Name'
# class MockView(BaseView): # class MockView(View):
# name = example # name = example
# self.assertEquals(get_name(MockView()), example) # self.assertEquals(get_name(MockView()), example)
def test_resource_description_uses_docstring_by_default(self): def test_resource_description_uses_docstring_by_default(self):
"""Ensure Resource names are based on the docstring by default.""" """Ensure Resource names are based on the docstring by default."""
class MockView(BaseView): class MockView(View):
"""an example docstring """an example docstring
==================== ====================
@ -71,7 +71,7 @@ class TestViewNamesAndDescriptions(TestCase):
#def test_resource_description_can_be_set_explicitly(self): #def test_resource_description_can_be_set_explicitly(self):
# """Ensure Resource descriptions can be set using the 'description' class attribute.""" # """Ensure Resource descriptions can be set using the 'description' class attribute."""
# example = 'Some other description' # example = 'Some other description'
# class MockView(BaseView): # class MockView(View):
# """docstring""" # """docstring"""
# description = example # description = example
# self.assertEquals(get_description(MockView()), example) # self.assertEquals(get_description(MockView()), example)
@ -79,13 +79,13 @@ class TestViewNamesAndDescriptions(TestCase):
#def test_resource_description_does_not_require_docstring(self): #def test_resource_description_does_not_require_docstring(self):
# """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute.""" # """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute."""
# example = 'Some other description' # example = 'Some other description'
# class MockView(BaseView): # class MockView(View):
# description = example # description = example
# self.assertEquals(get_description(MockView()), example) # self.assertEquals(get_description(MockView()), example)
def test_resource_description_can_be_empty(self): def test_resource_description_can_be_empty(self):
"""Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string""" """Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string"""
class MockView(BaseView): class MockView(View):
pass pass
self.assertEquals(get_description(MockView()), '') self.assertEquals(get_description(MockView()), '')

View File

@ -1,7 +1,7 @@
from django.test import TestCase from django.test import TestCase
from django import forms from django import forms
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.resources import FormResource from djangorestframework.resources import FormResource
import StringIO import StringIO
@ -19,7 +19,7 @@ class UploadFilesTests(TestCase):
class MockResource(FormResource): class MockResource(FormResource):
form = FileForm form = FileForm
class MockView(BaseView): class MockView(View):
permissions = () permissions = ()
resource = MockResource resource = MockResource

View File

@ -2,11 +2,11 @@
# .. # ..
# >>> from djangorestframework.parsers import FormParser # >>> from djangorestframework.parsers import FormParser
# >>> from djangorestframework.compat import RequestFactory # >>> from djangorestframework.compat import RequestFactory
# >>> from djangorestframework.views import BaseView # >>> from djangorestframework.views import View
# >>> from StringIO import StringIO # >>> from StringIO import StringIO
# >>> from urllib import urlencode # >>> from urllib import urlencode
# >>> req = RequestFactory().get('/') # >>> req = RequestFactory().get('/')
# >>> some_view = BaseView() # >>> some_view = View()
# >>> some_view.request = req # Make as if this request had been dispatched # >>> some_view.request = req # Make as if this request had been dispatched
# #
# FormParser # FormParser
@ -85,7 +85,7 @@
# from django.test import TestCase # from django.test import TestCase
# from djangorestframework.compat import RequestFactory # from djangorestframework.compat import RequestFactory
# from djangorestframework.parsers import MultiPartParser # from djangorestframework.parsers import MultiPartParser
# from djangorestframework.views import BaseView # from djangorestframework.views import View
# from StringIO import StringIO # from StringIO import StringIO
# #
# def encode_multipart_formdata(fields, files): # def encode_multipart_formdata(fields, files):
@ -125,7 +125,7 @@
# def test_multipartparser(self): # def test_multipartparser(self):
# """Ensure that MultiPartParser can parse multipart/form-data that contains a mix of several files and parameters.""" # """Ensure that MultiPartParser can parse multipart/form-data that contains a mix of several files and parameters."""
# post_req = RequestFactory().post('/', self.body, content_type=self.content_type) # post_req = RequestFactory().post('/', self.body, content_type=self.content_type)
# view = BaseView() # view = View()
# view.request = post_req # view.request = post_req
# (data, files) = MultiPartParser(view).parse(StringIO(self.body)) # (data, files) = MultiPartParser(view).parse(StringIO(self.body))
# self.assertEqual(data['key1'], 'val1') # self.assertEqual(data['key1'], 'val1')

View File

@ -1,7 +1,7 @@
from django.conf.urls.defaults import patterns, url from django.conf.urls.defaults import patterns, url
from django import http from django import http
from django.test import TestCase from django.test import TestCase
from djangorestframework.compat import View from djangorestframework.compat import View as DjangoView
from djangorestframework.renderers import BaseRenderer, JSONRenderer from djangorestframework.renderers import BaseRenderer, JSONRenderer
from djangorestframework.mixins import ResponseMixin from djangorestframework.mixins import ResponseMixin
from djangorestframework.response import Response from djangorestframework.response import Response
@ -13,7 +13,7 @@ DUMMYCONTENT = 'dummycontent'
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
class MockView(ResponseMixin, View): class MockView(ResponseMixin, DjangoView):
def get(self, request): def get(self, request):
response = Response(DUMMYSTATUS, DUMMYCONTENT) response = Response(DUMMYSTATUS, DUMMYCONTENT)
return self.render(response) return self.render(response)

View File

@ -3,10 +3,10 @@ from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.views import BaseView from djangorestframework.views import View
class MockView(BaseView): class MockView(View):
"""Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified""" """Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified"""
permissions = () permissions = ()

View File

@ -3,11 +3,11 @@ from django.test import TestCase
from django.utils import simplejson as json from django.utils import simplejson as json
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.permissions import PerUserThrottling from djangorestframework.permissions import PerUserThrottling
class MockView(BaseView): class MockView(View):
permissions = ( PerUserThrottling, ) permissions = ( PerUserThrottling, )
throttle = (3, 1) # 3 requests per second throttle = (3, 1) # 3 requests per second

View File

@ -4,7 +4,7 @@ from django.test import TestCase
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.resources import Resource, FormResource, ModelResource from djangorestframework.resources import Resource, FormResource, ModelResource
from djangorestframework.response import ErrorResponse from djangorestframework.response import ErrorResponse
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.resources import Resource from djangorestframework.resources import Resource
@ -18,7 +18,7 @@ class TestDisabledValidations(TestCase):
class DisabledFormResource(FormResource): class DisabledFormResource(FormResource):
form = None form = None
class MockView(BaseView): class MockView(View):
resource = DisabledFormResource resource = DisabledFormResource
view = MockView() view = MockView()
@ -31,7 +31,7 @@ class TestDisabledValidations(TestCase):
class DisabledFormResource(FormResource): class DisabledFormResource(FormResource):
form = None form = None
class MockView(BaseView): class MockView(View):
resource = DisabledFormResource resource = DisabledFormResource
view = MockView() view = MockView()
@ -43,7 +43,7 @@ class TestDisabledValidations(TestCase):
"""If the view's form is None and does not have a Resource with a model set then """If the view's form is None and does not have a Resource with a model set then
ModelFormValidator(view).validate_request(content, None) should just return the content unmodified.""" ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
class DisabledModelFormView(BaseView): class DisabledModelFormView(View):
resource = ModelResource resource = ModelResource
view = DisabledModelFormView() view = DisabledModelFormView()
@ -52,7 +52,7 @@ class TestDisabledValidations(TestCase):
def test_disabled_model_form_validator_get_bound_form_returns_none(self): def test_disabled_model_form_validator_get_bound_form_returns_none(self):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None.""" """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
class DisabledModelFormView(BaseView): class DisabledModelFormView(View):
resource = ModelResource resource = ModelResource
view = DisabledModelFormView() view = DisabledModelFormView()
@ -77,7 +77,7 @@ class TestNonFieldErrors(TestCase):
class MockResource(FormResource): class MockResource(FormResource):
form = MockForm form = MockForm
class MockView(BaseView): class MockView(View):
pass pass
view = MockView() view = MockView()
@ -104,10 +104,10 @@ class TestFormValidation(TestCase):
class MockModelResource(ModelResource): class MockModelResource(ModelResource):
form = MockForm form = MockForm
class MockFormView(BaseView): class MockFormView(View):
resource = MockFormResource resource = MockFormResource
class MockModelFormView(BaseView): class MockModelFormView(View):
resource = MockModelResource resource = MockModelResource
self.MockFormResource = MockFormResource self.MockFormResource = MockFormResource
@ -277,7 +277,7 @@ class TestModelFormValidator(TestCase):
class MockResource(ModelResource): class MockResource(ModelResource):
model = MockModel model = MockModel
class MockView(BaseView): class MockView(View):
resource = MockResource resource = MockResource
self.validator = MockResource(MockView) self.validator = MockResource(MockView)

View File

@ -6,16 +6,17 @@ By setting or modifying class attributes on your view, you change it's predefine
""" """
from django.core.urlresolvers import set_script_prefix from django.core.urlresolvers import set_script_prefix
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View from djangorestframework.compat import View as DjangoView
from djangorestframework.response import Response, ErrorResponse from djangorestframework.response import Response, ErrorResponse
from djangorestframework.mixins import * from djangorestframework.mixins import *
from djangorestframework import resources, renderers, parsers, authentication, permissions, status from djangorestframework import resources, renderers, parsers, authentication, permissions, status
__all__ = ( __all__ = (
'BaseView', 'View',
'ModelView', 'ModelView',
'InstanceModelView', 'InstanceModelView',
'ListModelView', 'ListModelView',
@ -24,7 +25,7 @@ __all__ = (
class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View): class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
""" """
Handles incoming requests and maps them to REST operations. Handles incoming requests and maps them to REST operations.
Performs request deserialization, response serialization, authentication and input validation. Performs request deserialization, response serialization, authentication and input validation.
@ -65,7 +66,7 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
as an attribute on the callable function. This allows us to discover as an attribute on the callable function. This allows us to discover
information about the view when we do URL reverse lookups. information about the view when we do URL reverse lookups.
""" """
view = super(BaseView, cls).as_view(**initkwargs) view = super(View, cls).as_view(**initkwargs)
view.cls_instance = cls(**initkwargs) view.cls_instance = cls(**initkwargs)
return view return view
@ -86,6 +87,14 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
{'detail': 'Method \'%s\' not allowed on this resource.' % self.method}) {'detail': 'Method \'%s\' not allowed on this resource.' % self.method})
def initial(self, request, *args, **kargs):
"""
Hook for any code that needs to run prior to anything else.
Required if you want to do things like set `request.upload_handlers` before
the authentication and dispatch handling is run.
"""
pass
# Note: session based authentication is explicitly CSRF validated, # Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt. # all other authentication is CSRF exempt.
@csrf_exempt @csrf_exempt
@ -99,6 +108,8 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
set_script_prefix(prefix) set_script_prefix(prefix)
try: try:
self.initial(request, *args, **kwargs)
# Authenticate and check request has the relevant permissions # Authenticate and check request has the relevant permissions
self._check_permissions() self._check_permissions()
@ -110,8 +121,10 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
response_obj = handler(request, *args, **kwargs) response_obj = handler(request, *args, **kwargs)
# Allow return value to be either Response, or an object, or None # Allow return value to be either HttpResponse, Response, or an object, or None
if isinstance(response_obj, Response): if isinstance(response_obj, HttpResponse):
return response_obj
elif isinstance(response_obj, Response):
response = response_obj response = response_obj
elif response_obj is not None: elif response_obj is not None:
response = Response(status.HTTP_200_OK, response_obj) response = Response(status.HTTP_200_OK, response_obj)
@ -135,7 +148,7 @@ class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
class ModelView(BaseView): class ModelView(View):
"""A RESTful view that maps to a model in the database.""" """A RESTful view that maps to a model in the database."""
resource = resources.ModelResource resource = resources.ModelResource

View File

@ -1,7 +1,7 @@
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework import status from djangorestframework import status
@ -25,7 +25,7 @@ def remove_oldest_files(dir, max_files):
[os.remove(path) for path in ctime_sorted_paths[max_files:]] [os.remove(path) for path in ctime_sorted_paths[max_files:]]
class ObjectStoreRoot(BaseView): class ObjectStoreRoot(View):
""" """
Root of the Object Store API. Root of the Object Store API.
Allows the client to get a complete list of all the stored objects, or to create a new stored object. Allows the client to get a complete list of all the stored objects, or to create a new stored object.
@ -51,7 +51,7 @@ class ObjectStoreRoot(BaseView):
return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': reverse('stored-object', kwargs={'key':key})}) return Response(status.HTTP_201_CREATED, self.CONTENT, {'Location': reverse('stored-object', kwargs={'key':key})})
class StoredObject(BaseView): class StoredObject(View):
""" """
Represents a stored object. Represents a stored object.
The object may be any picklable content. The object may be any picklable content.

View File

@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
from djangorestframework.resources import FormResource from djangorestframework.resources import FormResource
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework.renderers import BaseRenderer from djangorestframework.renderers import BaseRenderer
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework import status from djangorestframework import status
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
@ -53,7 +53,7 @@ class PygmentsFormResource(FormResource):
form = PygmentsForm form = PygmentsForm
class PygmentsRoot(BaseView): class PygmentsRoot(View):
""" """
This example demonstrates a simple RESTful Web API aound the awesome pygments library. This example demonstrates a simple RESTful Web API aound the awesome pygments library.
This top level resource is used to create highlighted code snippets, and to list all the existing code snippets. This top level resource is used to create highlighted code snippets, and to list all the existing code snippets.
@ -88,7 +88,7 @@ class PygmentsRoot(BaseView):
return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])}) return Response(status.HTTP_201_CREATED, headers={'Location': reverse('pygments-instance', args=[unique_id])})
class PygmentsInstance(BaseView): class PygmentsInstance(View):
""" """
Simply return the stored highlighted HTML file with the correct mime type. Simply return the stored highlighted HTML file with the correct mime type.
This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class. This Resource only renders HTML and uses a standard HTML renderer rather than the renderers.DocumentingHTMLRenderer class.

View File

@ -1,6 +1,6 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from djangorestframework.views import BaseView from djangorestframework.views import View
from djangorestframework.resources import FormResource from djangorestframework.resources import FormResource
from djangorestframework.response import Response from djangorestframework.response import Response
from djangorestframework import status from djangorestframework import status
@ -14,7 +14,7 @@ class MyFormValidation(FormResource):
form = MyForm form = MyForm
class ExampleResource(BaseView): class ExampleResource(View):
""" """
A basic read-only resource that points to 3 other resources. A basic read-only resource that points to 3 other resources.
""" """
@ -23,7 +23,7 @@ class ExampleResource(BaseView):
return {"Some other resources": [reverse('another-example-resource', kwargs={'num':num}) for num in range(3)]} return {"Some other resources": [reverse('another-example-resource', kwargs={'num':num}) for num in range(3)]}
class AnotherExampleResource(BaseView): class AnotherExampleResource(View):
""" """
A basic GET-able/POST-able resource. A basic GET-able/POST-able resource.
""" """

View File

@ -1,10 +1,10 @@
"""The root view for the examples provided with Django REST framework""" """The root view for the examples provided with Django REST framework"""
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from djangorestframework.views import BaseView from djangorestframework.views import View
class Sandbox(BaseView): class Sandbox(View):
"""This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org). """This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org).
These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework. These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework.