mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 16:24:18 +03:00
cleaned a bit Response/ResponseMixin code, added some documentation + renamed ErrorResponse to ImmediateResponse
This commit is contained in:
parent
a0dc0b10e5
commit
ca96b4523b
|
@ -11,7 +11,7 @@ from urlobject import URLObject
|
|||
from djangorestframework import status
|
||||
from djangorestframework.renderers import BaseRenderer
|
||||
from djangorestframework.resources import Resource, FormResource, ModelResource
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.request import request_class_factory
|
||||
from djangorestframework.utils import as_tuple, allowed_methods
|
||||
|
||||
|
@ -80,28 +80,37 @@ class RequestMixin(object):
|
|||
|
||||
class ResponseMixin(object):
|
||||
"""
|
||||
Adds behavior for pluggable `Renderers` to a :class:`views.View` class.
|
||||
Adds behavior for pluggable `renderers` to a :class:`views.View` class.
|
||||
|
||||
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.
|
||||
Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
|
||||
"""
|
||||
|
||||
renderers = ()
|
||||
renderer_classes = ()
|
||||
"""
|
||||
The set of response renderers that the view can handle.
|
||||
|
||||
Should be a tuple/list of classes as described in the :mod:`renderers` module.
|
||||
"""
|
||||
|
||||
response_class = Response
|
||||
def get_renderers(self):
|
||||
"""
|
||||
Instantiates and returns the list of renderers that will be used to render
|
||||
the response.
|
||||
"""
|
||||
if not hasattr(self, '_renderers'):
|
||||
self._renderers = [r(self) for r in self.renderer_classes]
|
||||
return self._renderers
|
||||
|
||||
def prepare_response(self, response):
|
||||
"""
|
||||
Prepares response for the response cycle. Sets some headers, sets renderers, ...
|
||||
Prepares the response for the response cycle. This has no effect if the
|
||||
response is not an instance of :class:`response.Response`.
|
||||
"""
|
||||
if hasattr(response, 'request') and response.request is None:
|
||||
response.request = self.request
|
||||
|
||||
# Always add these headers.
|
||||
response['Allow'] = ', '.join(allowed_methods(self))
|
||||
# sample to allow caching using Vary http header
|
||||
|
@ -109,10 +118,9 @@ class ResponseMixin(object):
|
|||
# merge with headers possibly set at some point in the view
|
||||
for name, value in self.headers.items():
|
||||
response[name] = value
|
||||
|
||||
# set the views renderers on the response
|
||||
response.renderers = self.renderers
|
||||
# TODO: must disappear
|
||||
response.view = self
|
||||
response.renderers = self.get_renderers()
|
||||
self.response = response
|
||||
return response
|
||||
|
||||
|
@ -121,21 +129,21 @@ class ResponseMixin(object):
|
|||
"""
|
||||
Return an list of all the media types that this view can render.
|
||||
"""
|
||||
return [renderer.media_type for renderer in self.renderers]
|
||||
return [renderer.media_type for renderer in self.get_renderers()]
|
||||
|
||||
@property
|
||||
def _rendered_formats(self):
|
||||
"""
|
||||
Return a list of all the formats that this view can render.
|
||||
"""
|
||||
return [renderer.format for renderer in self.renderers]
|
||||
return [renderer.format for renderer in self.get_renderers()]
|
||||
|
||||
@property
|
||||
def _default_renderer(self):
|
||||
"""
|
||||
Return the view's default renderer class.
|
||||
"""
|
||||
return self.renderers[0]
|
||||
return self.get_renderers()[0]
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
|
@ -195,7 +203,7 @@ class AuthMixin(object):
|
|||
# TODO: wrap this behavior around dispatch()
|
||||
def _check_permissions(self):
|
||||
"""
|
||||
Check user permissions and either raise an ``ErrorResponse`` or return.
|
||||
Check user permissions and either raise an ``ImmediateResponse`` or return.
|
||||
"""
|
||||
user = self.user
|
||||
for permission_cls in self.permissions:
|
||||
|
@ -223,7 +231,7 @@ class ResourceMixin(object):
|
|||
"""
|
||||
Returns the cleaned, validated request content.
|
||||
|
||||
May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
|
||||
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request).
|
||||
"""
|
||||
if not hasattr(self, '_content'):
|
||||
self._content = self.validate_request(self.request.DATA, self.request.FILES)
|
||||
|
@ -234,7 +242,7 @@ class ResourceMixin(object):
|
|||
"""
|
||||
Returns the cleaned, validated query parameters.
|
||||
|
||||
May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
|
||||
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request).
|
||||
"""
|
||||
return self.validate_request(self.request.GET)
|
||||
|
||||
|
@ -253,7 +261,7 @@ class ResourceMixin(object):
|
|||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given the request *data* and optional *files*, return the cleaned, validated content.
|
||||
May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
|
||||
"""
|
||||
return self._resource.validate_request(data, files)
|
||||
|
||||
|
@ -384,7 +392,7 @@ class ReadModelMixin(ModelMixin):
|
|||
try:
|
||||
self.model_instance = self.get_instance(**query_kwargs)
|
||||
except model.DoesNotExist:
|
||||
raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return self.model_instance
|
||||
|
||||
|
@ -463,7 +471,7 @@ class DeleteModelMixin(ModelMixin):
|
|||
try:
|
||||
instance = self.get_instance(**query_kwargs)
|
||||
except model.DoesNotExist:
|
||||
raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
instance.delete()
|
||||
return Response()
|
||||
|
@ -570,12 +578,12 @@ class PaginatorMixin(object):
|
|||
try:
|
||||
page_num = int(self.request.GET.get('page', '1'))
|
||||
except ValueError:
|
||||
raise ErrorResponse(
|
||||
raise ImmediateResponse(
|
||||
content={'detail': 'That page contains no results'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if page_num not in paginator.page_range:
|
||||
raise ErrorResponse(
|
||||
raise ImmediateResponse(
|
||||
content={'detail': 'That page contains no results'},
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from django.http.multipartparser import MultiPartParserError
|
|||
from django.utils import simplejson as json
|
||||
from djangorestframework import status
|
||||
from djangorestframework.compat import yaml
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework.utils.mediatypes import media_type_matches
|
||||
from xml.etree import ElementTree as ET
|
||||
import datetime
|
||||
|
@ -88,7 +88,7 @@ class JSONParser(BaseParser):
|
|||
try:
|
||||
return (json.load(stream), None)
|
||||
except ValueError, exc:
|
||||
raise ErrorResponse(
|
||||
raise ImmediateResponse(
|
||||
content={'detail': 'JSON parse error - %s' % unicode(exc)},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
@ -111,7 +111,7 @@ if yaml:
|
|||
try:
|
||||
return (yaml.safe_load(stream), None)
|
||||
except ValueError, exc:
|
||||
raise ErrorResponse(
|
||||
raise ImmediateResponse(
|
||||
content={'detail': 'YAML parse error - %s' % unicode(exc)},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
|
@ -172,7 +172,7 @@ class MultiPartParser(BaseParser):
|
|||
try:
|
||||
django_parser = DjangoMultiPartParser(self.view.META, stream, upload_handlers)
|
||||
except MultiPartParserError, exc:
|
||||
raise ErrorResponse(
|
||||
raise ImmediateResponse(
|
||||
content={'detail': 'multipart parse error - %s' % unicode(exc)},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return django_parser.parse()
|
||||
|
|
|
@ -6,7 +6,7 @@ class to your view by setting your View's :attr:`permissions` class attribute.
|
|||
|
||||
from django.core.cache import cache
|
||||
from djangorestframework import status
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
import time
|
||||
|
||||
__all__ = (
|
||||
|
@ -21,12 +21,12 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
_403_FORBIDDEN_RESPONSE = ErrorResponse(
|
||||
_403_FORBIDDEN_RESPONSE = ImmediateResponse(
|
||||
content={'detail': 'You do not have permission to access this resource. ' +
|
||||
'You may need to login or otherwise authenticate the request.'},
|
||||
status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
_503_SERVICE_UNAVAILABLE = ErrorResponse(
|
||||
_503_SERVICE_UNAVAILABLE = ImmediateResponse(
|
||||
content={'detail': 'request was throttled'},
|
||||
status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
|
||||
|
@ -43,7 +43,7 @@ class BasePermission(object):
|
|||
|
||||
def check_permission(self, auth):
|
||||
"""
|
||||
Should simply return, or raise an :exc:`response.ErrorResponse`.
|
||||
Should simply return, or raise an :exc:`response.ImmediateResponse`.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -116,7 +116,7 @@ class BaseThrottle(BasePermission):
|
|||
def check_permission(self, auth):
|
||||
"""
|
||||
Check the throttling.
|
||||
Return `None` or raise an :exc:`.ErrorResponse`.
|
||||
Return `None` or raise an :exc:`.ImmediateResponse`.
|
||||
"""
|
||||
num, period = getattr(self.view, self.attr_name, self.default).split('/')
|
||||
self.num_requests = int(num)
|
||||
|
|
|
@ -45,7 +45,7 @@ class BaseRenderer(object):
|
|||
media_type = None
|
||||
format = None
|
||||
|
||||
def __init__(self, view):
|
||||
def __init__(self, view=None):
|
||||
self.view = view
|
||||
|
||||
def can_handle_response(self, accept):
|
||||
|
@ -218,7 +218,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
|
|||
"""
|
||||
|
||||
# Find the first valid renderer and render the content. (Don't use another documenting renderer.)
|
||||
renderers = [renderer for renderer in view.renderers if not issubclass(renderer, DocumentingTemplateRenderer)]
|
||||
renderers = [renderer for renderer in view.renderer_classes
|
||||
if not issubclass(renderer, DocumentingTemplateRenderer)]
|
||||
if not renderers:
|
||||
return '[No renderers were found]'
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ This enhanced request object offers the following :
|
|||
|
||||
from django.http import HttpRequest
|
||||
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework import status
|
||||
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
@ -194,7 +194,7 @@ class Request(object):
|
|||
"""
|
||||
Parse the request content.
|
||||
|
||||
May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request).
|
||||
May raise a 415 ImmediateResponse (Unsupported Media Type), or a 400 ImmediateResponse (Bad Request).
|
||||
"""
|
||||
if stream is None or content_type is None:
|
||||
return (None, None)
|
||||
|
@ -206,7 +206,7 @@ class Request(object):
|
|||
if parser.can_handle_request(content_type):
|
||||
return parser.parse(stream)
|
||||
|
||||
raise ErrorResponse(content={'error':
|
||||
raise ImmediateResponse(content={'error':
|
||||
'Unsupported media type in request \'%s\'.' % content_type},
|
||||
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from django import forms
|
|||
from django.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
|
||||
from django.db import models
|
||||
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework.serializer import Serializer, _SkipField
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
||||
|
@ -22,7 +22,7 @@ class BaseResource(Serializer):
|
|||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given the request content return the cleaned, validated content.
|
||||
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Typically raises a :exc:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
|
||||
"""
|
||||
return data
|
||||
|
||||
|
@ -73,19 +73,19 @@ class FormResource(Resource):
|
|||
"""
|
||||
Flag to check for unknown fields when validating a form. If set to false and
|
||||
we receive request data that is not expected by the form it raises an
|
||||
:exc:`response.ErrorResponse` with status code 400. If set to true, only
|
||||
:exc:`response.ImmediateResponse` with status code 400. If set to true, only
|
||||
expected fields are validated.
|
||||
"""
|
||||
|
||||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given some content as input return some cleaned, validated content.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Raises a :exc:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
|
||||
|
||||
Validation is standard form validation, with an additional constraint that *no extra unknown fields* may be supplied
|
||||
if :attr:`self.allow_unknown_form_fields` is ``False``.
|
||||
|
||||
On failure the :exc:`response.ErrorResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
On failure the :exc:`response.ImmediateResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
|
||||
If the :obj:`'field-errors'` key exists it is a dict of ``{'field name as string': ['errors as strings', ...]}``.
|
||||
"""
|
||||
|
@ -174,7 +174,7 @@ class FormResource(Resource):
|
|||
detail[u'field_errors'] = field_errors
|
||||
|
||||
# Return HTTP 400 response (BAD REQUEST)
|
||||
raise ErrorResponse(content=detail, status=400)
|
||||
raise ImmediateResponse(content=detail, status=400)
|
||||
|
||||
def get_form_class(self, method=None):
|
||||
"""
|
||||
|
@ -273,14 +273,14 @@ class ModelResource(FormResource):
|
|||
def validate_request(self, data, files=None):
|
||||
"""
|
||||
Given some content as input return some cleaned, validated content.
|
||||
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
|
||||
Raises a :exc:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
|
||||
|
||||
Validation is standard form or model form validation,
|
||||
with an additional constraint that no extra unknown fields may be supplied,
|
||||
and that all fields specified by the fields class attribute must be supplied,
|
||||
even if they are not validated by the form/model form.
|
||||
|
||||
On failure the ErrorResponse content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
On failure the ImmediateResponse content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
|
||||
If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
|
||||
If the ''field-errors'` key exists it is a dict of {field name as string: list of errors as strings}.
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
"""
|
||||
The :mod:`response` module provides Response classes you can use in your
|
||||
views to return a certain HTTP response. Typically a response is *rendered*
|
||||
into a HTTP response depending on what renderers are set on your view and
|
||||
als depending on the accept header of the request.
|
||||
The :mod:`response` module provides :class:`Response` and :class:`ImmediateResponse` classes.
|
||||
|
||||
`Response` is a subclass of `HttpResponse`, and can be similarly instantiated and returned
|
||||
from any view. It is a bit smarter than Django's `HttpResponse` though, for it knows how
|
||||
to use :mod:`renderers` to automatically render its content to a serial format.
|
||||
This is achieved by :
|
||||
|
||||
- determining the accepted types by checking for an overload or an `Accept` header in the request
|
||||
- looking for a suitable renderer and using it on the content given at instantiation
|
||||
|
||||
|
||||
`ImmediateResponse` is an exception that inherits from `Response`. It can be used
|
||||
to abort the request handling (i.e. ``View.get``, ``View.put``, ...),
|
||||
and immediately returning a response.
|
||||
"""
|
||||
|
||||
from django.template.response import SimpleTemplateResponse
|
||||
|
@ -13,7 +23,7 @@ from djangorestframework.utils import MSIE_USER_AGENT_REGEX
|
|||
from djangorestframework import status
|
||||
|
||||
|
||||
__all__ = ('Response', 'ErrorResponse')
|
||||
__all__ = ('Response', 'ImmediateResponse')
|
||||
|
||||
|
||||
class Response(SimpleTemplateResponse):
|
||||
|
@ -26,15 +36,16 @@ class Response(SimpleTemplateResponse):
|
|||
|
||||
def __init__(self, content=None, status=None, request=None, renderers=None):
|
||||
"""
|
||||
content is the raw content.
|
||||
`content` is the raw content, not yet serialized. This must be simple Python
|
||||
data that renderers can handle (cf: dict, str, ...)
|
||||
|
||||
The set of renderers that the response can handle.
|
||||
|
||||
Should be a tuple/list of classes as described in the :mod:`renderers` module.
|
||||
`renderers` is a list/tuple of renderer instances and represents the set of renderers
|
||||
that the response can handle.
|
||||
"""
|
||||
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
|
||||
# which we don't need
|
||||
super(Response, self).__init__(None, status=status)
|
||||
|
||||
# We need to store our content in raw content to avoid overriding HttpResponse's
|
||||
# `content` property
|
||||
self.raw_content = content
|
||||
|
@ -42,17 +53,14 @@ class Response(SimpleTemplateResponse):
|
|||
self.request = request
|
||||
if renderers is not None:
|
||||
self.renderers = renderers
|
||||
# TODO: must go
|
||||
self.view = None
|
||||
|
||||
# TODO: wrap this behavior around dispatch(), ensuring it works
|
||||
# out of the box with existing Django classes that use render_to_response.
|
||||
@property
|
||||
def rendered_content(self):
|
||||
"""
|
||||
The final rendered content. Accessing this attribute triggers the complete rendering cycle :
|
||||
selecting suitable renderer, setting response's actual content type, rendering data.
|
||||
"""
|
||||
renderer, media_type = self._determine_renderer()
|
||||
# TODO: renderer *could* override media_type in .render() if required.
|
||||
|
||||
# Set the media type of the response
|
||||
self['Content-Type'] = renderer.media_type
|
||||
|
@ -65,12 +73,20 @@ class Response(SimpleTemplateResponse):
|
|||
@property
|
||||
def status_text(self):
|
||||
"""
|
||||
Return reason text corresponding to our HTTP response status code.
|
||||
Returns reason text corresponding to our HTTP response status code.
|
||||
Provided for convenience.
|
||||
"""
|
||||
return STATUS_CODE_TEXT.get(self.status, '')
|
||||
|
||||
def _determine_accept_list(self):
|
||||
"""
|
||||
Returns a list of accepted media types. This list is determined from :
|
||||
|
||||
1. overload with `_ACCEPT_QUERY_PARAM`
|
||||
2. `Accept` header of the request
|
||||
|
||||
If those are useless, a default value is returned instead.
|
||||
"""
|
||||
request = self.request
|
||||
if request is None:
|
||||
return ['*/*']
|
||||
|
@ -92,7 +108,7 @@ class Response(SimpleTemplateResponse):
|
|||
|
||||
def _determine_renderer(self):
|
||||
"""
|
||||
Determines the appropriate renderer for the output, given the client's 'Accept' header,
|
||||
Determines the appropriate renderer for the output, given the list of accepted media types,
|
||||
and the :attr:`renderers` set on this class.
|
||||
|
||||
Returns a 2-tuple of `(renderer, media_type)`
|
||||
|
@ -103,16 +119,14 @@ class Response(SimpleTemplateResponse):
|
|||
# attempting more specific media types first
|
||||
# NB. The inner loop here isn't as bad as it first looks :)
|
||||
# Worst case is we're looping over len(accept_list) * len(self.renderers)
|
||||
renderers = [renderer_cls(self.view) for renderer_cls in self.renderers]
|
||||
|
||||
for media_type_list in order_by_precedence(self._determine_accept_list()):
|
||||
for renderer in renderers:
|
||||
for renderer in self.renderers:
|
||||
for media_type in media_type_list:
|
||||
if renderer.can_handle_response(media_type):
|
||||
return renderer, media_type
|
||||
|
||||
# No acceptable renderers were found
|
||||
raise ErrorResponse(content={'detail': 'Could not satisfy the client\'s Accept header',
|
||||
raise ImmediateResponse(content={'detail': 'Could not satisfy the client\'s Accept header',
|
||||
'available_types': self._rendered_media_types},
|
||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
renderers=self.renderers)
|
||||
|
@ -152,10 +166,9 @@ class Response(SimpleTemplateResponse):
|
|||
return self.renderers[0]
|
||||
|
||||
|
||||
class ErrorResponse(Response, BaseException):
|
||||
class ImmediateResponse(Response, BaseException):
|
||||
"""
|
||||
An exception representing an Response that should be returned immediately.
|
||||
Any content should be serialized as-is, without being filtered.
|
||||
A subclass of :class:`Response` used to abort the current request handling.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
|
@ -23,9 +23,10 @@ class UserAgentMungingTest(TestCase):
|
|||
|
||||
class MockView(View):
|
||||
permissions = ()
|
||||
response_class = Response
|
||||
|
||||
def get(self, request):
|
||||
return Response({'a':1, 'b':2, 'c':3})
|
||||
return self.response_class({'a':1, 'b':2, 'c':3})
|
||||
|
||||
self.req = RequestFactory()
|
||||
self.MockView = MockView
|
||||
|
|
|
@ -6,7 +6,7 @@ from djangorestframework.compat import RequestFactory
|
|||
from django.contrib.auth.models import Group, User
|
||||
from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
||||
from djangorestframework.resources import ModelResource
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.tests.models import CustomUser
|
||||
from djangorestframework.tests.testcases import TestModelsTestCase
|
||||
from djangorestframework.views import View
|
||||
|
@ -41,7 +41,7 @@ class TestModelRead(TestModelsTestCase):
|
|||
mixin = ReadModelMixin()
|
||||
mixin.resource = GroupResource
|
||||
|
||||
self.assertRaises(ErrorResponse, mixin.get, request, id=12345)
|
||||
self.assertRaises(ImmediateResponse, mixin.get, request, id=12345)
|
||||
|
||||
|
||||
class TestModelCreation(TestModelsTestCase):
|
||||
|
|
|
@ -73,8 +73,8 @@ class MockGETView(View):
|
|||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
|
||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
|
||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
|
||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import json
|
||||
import unittest
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from django.test import TestCase
|
||||
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.mixins import ResponseMixin
|
||||
from djangorestframework.views import View
|
||||
from djangorestframework.compat import View as DjangoView
|
||||
|
@ -17,13 +18,16 @@ from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRender
|
|||
class TestResponseDetermineRenderer(TestCase):
|
||||
|
||||
def get_response(self, url='', accept_list=[], renderers=[]):
|
||||
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
|
||||
kwargs = {}
|
||||
if accept_list is not None:
|
||||
kwargs['HTTP_ACCEPT'] = HTTP_ACCEPT=','.join(accept_list)
|
||||
request = RequestFactory().get(url, **kwargs)
|
||||
return Response(request=request, renderers=renderers)
|
||||
|
||||
def get_renderer_mock(self, media_type):
|
||||
return type('RendererMock', (BaseRenderer,), {
|
||||
'media_type': media_type,
|
||||
})
|
||||
})()
|
||||
|
||||
def test_determine_accept_list_accept_header(self):
|
||||
"""
|
||||
|
@ -33,6 +37,13 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
response = self.get_response(accept_list=accept_list)
|
||||
self.assertEqual(response._determine_accept_list(), accept_list)
|
||||
|
||||
def test_determine_accept_list_default(self):
|
||||
"""
|
||||
Test that determine_accept_list takes the default renderer if Accept is not specified.
|
||||
"""
|
||||
response = self.get_response(accept_list=None)
|
||||
self.assertEqual(response._determine_accept_list(), ['*/*'])
|
||||
|
||||
def test_determine_accept_list_overriden_header(self):
|
||||
"""
|
||||
Test Accept header overriding.
|
||||
|
@ -47,38 +58,46 @@ class TestResponseDetermineRenderer(TestCase):
|
|||
Test that right renderer is chosen, in the order of Accept list.
|
||||
"""
|
||||
accept_list = ['application/pickle', 'application/json']
|
||||
PRenderer = self.get_renderer_mock('application/pickle')
|
||||
JRenderer = self.get_renderer_mock('application/json')
|
||||
prenderer = self.get_renderer_mock('application/pickle')
|
||||
jrenderer = self.get_renderer_mock('application/json')
|
||||
|
||||
renderers = (PRenderer, JRenderer)
|
||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
||||
response = self.get_response(accept_list=accept_list, renderers=(prenderer, jrenderer))
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, 'application/pickle')
|
||||
self.assertTrue(isinstance(renderer, PRenderer))
|
||||
self.assertTrue(renderer, prenderer)
|
||||
|
||||
renderers = (JRenderer,)
|
||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
||||
response = self.get_response(accept_list=accept_list, renderers=(jrenderer,))
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, 'application/json')
|
||||
self.assertTrue(isinstance(renderer, JRenderer))
|
||||
self.assertTrue(renderer, jrenderer)
|
||||
|
||||
def test_determine_renderer_default(self):
|
||||
"""
|
||||
Test determine renderer when Accept was not specified.
|
||||
"""
|
||||
prenderer = self.get_renderer_mock('application/pickle')
|
||||
|
||||
response = self.get_response(accept_list=None, renderers=(prenderer,))
|
||||
renderer, media_type = response._determine_renderer()
|
||||
self.assertEqual(media_type, '*/*')
|
||||
self.assertTrue(renderer, prenderer)
|
||||
|
||||
def test_determine_renderer_no_renderer(self):
|
||||
"""
|
||||
Test determine renderer when no renderer can satisfy the Accept list.
|
||||
"""
|
||||
accept_list = ['application/json']
|
||||
PRenderer = self.get_renderer_mock('application/pickle')
|
||||
prenderer = self.get_renderer_mock('application/pickle')
|
||||
|
||||
renderers = (PRenderer,)
|
||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
||||
self.assertRaises(ErrorResponse, response._determine_renderer)
|
||||
response = self.get_response(accept_list=accept_list, renderers=(prenderer,))
|
||||
self.assertRaises(ImmediateResponse, response._determine_renderer)
|
||||
|
||||
|
||||
class TestResponseRenderContent(TestCase):
|
||||
|
||||
def get_response(self, url='', accept_list=[], content=None):
|
||||
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
|
||||
return Response(request=request, content=content, renderers=DEFAULT_RENDERERS)
|
||||
return Response(request=request, content=content, renderers=[r() for r in DEFAULT_RENDERERS])
|
||||
|
||||
def test_render(self):
|
||||
"""
|
||||
|
@ -116,7 +135,7 @@ class RendererB(BaseRenderer):
|
|||
|
||||
|
||||
class MockView(ResponseMixin, DjangoView):
|
||||
renderers = (RendererA, RendererB)
|
||||
renderer_classes = (RendererA, RendererB)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||
|
@ -124,22 +143,22 @@ class MockView(ResponseMixin, DjangoView):
|
|||
|
||||
|
||||
class HTMLView(View):
|
||||
renderers = (DocumentingHTMLRenderer, )
|
||||
renderer_classes = (DocumentingHTMLRenderer, )
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
class HTMLView1(View):
|
||||
renderers = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer)
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
return Response('text')
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
|
||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||
url(r'^html$', HTMLView.as_view()),
|
||||
url(r'^html1$', HTMLView1.as_view()),
|
||||
)
|
||||
|
@ -197,11 +216,11 @@ class RendererIntegrationTests(TestCase):
|
|||
self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||
|
||||
# TODO: can't pass because view is a simple Django view and response is an ErrorResponse
|
||||
# def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
|
||||
# """If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||
# resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||
# self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
||||
@unittest.skip('can\'t pass because view is a simple Django view and response is an ImmediateResponse')
|
||||
def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
|
||||
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||
self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
||||
|
||||
def test_specified_renderer_serializes_content_on_format_query(self):
|
||||
"""If a 'format' query is specified, the renderer with the matching
|
||||
|
|
|
@ -2,7 +2,7 @@ from django import forms
|
|||
from django.db import models
|
||||
from django.test import TestCase
|
||||
from djangorestframework.resources import FormResource, ModelResource
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.response import ImmediateResponse
|
||||
from djangorestframework.views import View
|
||||
|
||||
|
||||
|
@ -81,10 +81,10 @@ class TestNonFieldErrors(TestCase):
|
|||
content = {'field1': 'example1', 'field2': 'example2'}
|
||||
try:
|
||||
MockResource(view).validate_request(content, None)
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
||||
else:
|
||||
self.fail('ErrorResponse was not raised')
|
||||
self.fail('ImmediateResponse was not raised')
|
||||
|
||||
|
||||
class TestFormValidation(TestCase):
|
||||
|
@ -120,14 +120,14 @@ class TestFormValidation(TestCase):
|
|||
def validation_failure_raises_response_exception(self, validator):
|
||||
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
|
||||
content = {}
|
||||
self.assertRaises(ErrorResponse, validator.validate_request, content, None)
|
||||
self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
|
||||
|
||||
def validation_does_not_allow_extra_fields_by_default(self, validator):
|
||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
self.assertRaises(ErrorResponse, validator.validate_request, content, None)
|
||||
self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
|
||||
|
||||
def validation_allows_extra_fields_if_explicitly_set(self, validator):
|
||||
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
|
||||
|
@ -154,7 +154,7 @@ class TestFormValidation(TestCase):
|
|||
content = {}
|
||||
try:
|
||||
validator.validate_request(content, None)
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised')
|
||||
|
@ -164,7 +164,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': ''}
|
||||
try:
|
||||
validator.validate_request(content, None)
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised')
|
||||
|
@ -174,7 +174,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
try:
|
||||
validator.validate_request(content, None)
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised')
|
||||
|
@ -184,7 +184,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': '', 'extra': 'extra'}
|
||||
try:
|
||||
validator.validate_request(content, None)
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
|
||||
'extra': ['This field does not exist.']}})
|
||||
else:
|
||||
|
@ -307,14 +307,14 @@ class TestModelFormValidator(TestCase):
|
|||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
|
||||
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
||||
self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
|
||||
|
||||
def test_validate_requires_fields_on_model_forms(self):
|
||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'readonly': 'read only'}
|
||||
self.assertRaises(ErrorResponse, self.validator.validate_request, content, None)
|
||||
self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
|
||||
|
||||
def test_validate_does_not_require_blankable_fields_on_model_forms(self):
|
||||
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
|
||||
|
|
|
@ -13,7 +13,7 @@ from django.utils.safestring import mark_safe
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from djangorestframework.compat import View as DjangoView, apply_markdown
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.response import Response, ImmediateResponse
|
||||
from djangorestframework.mixins import *
|
||||
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
||||
|
||||
|
@ -81,7 +81,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
or `None` to use default behaviour.
|
||||
"""
|
||||
|
||||
renderers = renderers.DEFAULT_RENDERERS
|
||||
renderer_classes = renderers.DEFAULT_RENDERERS
|
||||
"""
|
||||
List of renderers the resource can serialize the response with, ordered by preference.
|
||||
"""
|
||||
|
@ -172,7 +172,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
"""
|
||||
Return an HTTP 405 error if an operation is called which does not have a handler method.
|
||||
"""
|
||||
raise ErrorResponse(content=
|
||||
raise ImmediateResponse(content=
|
||||
{'detail': 'Method \'%s\' not allowed on this resource.' % request.method},
|
||||
status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
|
@ -232,13 +232,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
self.prepare_response(response)
|
||||
|
||||
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
|
||||
# TODO: ugly
|
||||
# TODO: ugly hack to handle both HttpResponse and Response.
|
||||
if hasattr(response, 'raw_content'):
|
||||
response.raw_content = self.filter_response(response.raw_content)
|
||||
else:
|
||||
response.content = self.filter_response(response.content)
|
||||
|
||||
except ErrorResponse, response:
|
||||
except ImmediateResponse, response:
|
||||
# Prepare response for the response cycle.
|
||||
self.prepare_response(response)
|
||||
|
||||
|
@ -259,10 +259,10 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
|||
for name, field in form.fields.iteritems():
|
||||
field_name_types[name] = field.__class__.__name__
|
||||
content['fields'] = field_name_types
|
||||
# Note 'ErrorResponse' is misleading, it's just any response
|
||||
# Note 'ImmediateResponse' is misleading, it's just any response
|
||||
# that should be rendered and returned immediately, without any
|
||||
# response filtering.
|
||||
raise ErrorResponse(content=content, status=status.HTTP_200_OK)
|
||||
raise ImmediateResponse(content=content, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ModelView(View):
|
||||
|
|
Loading…
Reference in New Issue
Block a user