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 import status
|
||||||
from djangorestframework.renderers import BaseRenderer
|
from djangorestframework.renderers import BaseRenderer
|
||||||
from djangorestframework.resources import Resource, FormResource, ModelResource
|
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.request import request_class_factory
|
||||||
from djangorestframework.utils import as_tuple, allowed_methods
|
from djangorestframework.utils import as_tuple, allowed_methods
|
||||||
|
|
||||||
|
@ -80,28 +80,37 @@ class RequestMixin(object):
|
||||||
|
|
||||||
class ResponseMixin(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.
|
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.
|
||||||
Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
|
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.
|
The set of response renderers that the view can handle.
|
||||||
|
|
||||||
Should be a tuple/list of classes as described in the :mod:`renderers` module.
|
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):
|
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:
|
if hasattr(response, 'request') and response.request is None:
|
||||||
response.request = self.request
|
response.request = self.request
|
||||||
|
|
||||||
# Always add these headers.
|
# Always add these headers.
|
||||||
response['Allow'] = ', '.join(allowed_methods(self))
|
response['Allow'] = ', '.join(allowed_methods(self))
|
||||||
# sample to allow caching using Vary http header
|
# 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
|
# merge with headers possibly set at some point in the view
|
||||||
for name, value in self.headers.items():
|
for name, value in self.headers.items():
|
||||||
response[name] = value
|
response[name] = value
|
||||||
|
|
||||||
# set the views renderers on the response
|
# set the views renderers on the response
|
||||||
response.renderers = self.renderers
|
response.renderers = self.get_renderers()
|
||||||
# TODO: must disappear
|
|
||||||
response.view = self
|
|
||||||
self.response = response
|
self.response = response
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -121,21 +129,21 @@ class ResponseMixin(object):
|
||||||
"""
|
"""
|
||||||
Return an list of all the media types that this view can render.
|
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
|
@property
|
||||||
def _rendered_formats(self):
|
def _rendered_formats(self):
|
||||||
"""
|
"""
|
||||||
Return a list of all the formats that this view can render.
|
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
|
@property
|
||||||
def _default_renderer(self):
|
def _default_renderer(self):
|
||||||
"""
|
"""
|
||||||
Return the view's default renderer class.
|
Return the view's default renderer class.
|
||||||
"""
|
"""
|
||||||
return self.renderers[0]
|
return self.get_renderers()[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def headers(self):
|
def headers(self):
|
||||||
|
@ -195,7 +203,7 @@ class AuthMixin(object):
|
||||||
# TODO: wrap this behavior around dispatch()
|
# TODO: wrap this behavior around dispatch()
|
||||||
def _check_permissions(self):
|
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
|
user = self.user
|
||||||
for permission_cls in self.permissions:
|
for permission_cls in self.permissions:
|
||||||
|
@ -223,7 +231,7 @@ class ResourceMixin(object):
|
||||||
"""
|
"""
|
||||||
Returns the cleaned, validated request content.
|
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'):
|
if not hasattr(self, '_content'):
|
||||||
self._content = self.validate_request(self.request.DATA, self.request.FILES)
|
self._content = self.validate_request(self.request.DATA, self.request.FILES)
|
||||||
|
@ -234,7 +242,7 @@ class ResourceMixin(object):
|
||||||
"""
|
"""
|
||||||
Returns the cleaned, validated query parameters.
|
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)
|
return self.validate_request(self.request.GET)
|
||||||
|
|
||||||
|
@ -253,7 +261,7 @@ class ResourceMixin(object):
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given the request *data* and optional *files*, return the cleaned, validated content.
|
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)
|
return self._resource.validate_request(data, files)
|
||||||
|
|
||||||
|
@ -384,7 +392,7 @@ class ReadModelMixin(ModelMixin):
|
||||||
try:
|
try:
|
||||||
self.model_instance = self.get_instance(**query_kwargs)
|
self.model_instance = self.get_instance(**query_kwargs)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
|
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
return self.model_instance
|
return self.model_instance
|
||||||
|
|
||||||
|
@ -463,7 +471,7 @@ class DeleteModelMixin(ModelMixin):
|
||||||
try:
|
try:
|
||||||
instance = self.get_instance(**query_kwargs)
|
instance = self.get_instance(**query_kwargs)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
|
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
instance.delete()
|
instance.delete()
|
||||||
return Response()
|
return Response()
|
||||||
|
@ -570,12 +578,12 @@ class PaginatorMixin(object):
|
||||||
try:
|
try:
|
||||||
page_num = int(self.request.GET.get('page', '1'))
|
page_num = int(self.request.GET.get('page', '1'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ErrorResponse(
|
raise ImmediateResponse(
|
||||||
content={'detail': 'That page contains no results'},
|
content={'detail': 'That page contains no results'},
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if page_num not in paginator.page_range:
|
if page_num not in paginator.page_range:
|
||||||
raise ErrorResponse(
|
raise ImmediateResponse(
|
||||||
content={'detail': 'That page contains no results'},
|
content={'detail': 'That page contains no results'},
|
||||||
status=status.HTTP_404_NOT_FOUND)
|
status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from django.http.multipartparser import MultiPartParserError
|
||||||
from django.utils import simplejson as json
|
from django.utils import simplejson as json
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
from djangorestframework.compat import yaml
|
from djangorestframework.compat import yaml
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
from djangorestframework.utils.mediatypes import media_type_matches
|
from djangorestframework.utils.mediatypes import media_type_matches
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -88,7 +88,7 @@ class JSONParser(BaseParser):
|
||||||
try:
|
try:
|
||||||
return (json.load(stream), None)
|
return (json.load(stream), None)
|
||||||
except ValueError, exc:
|
except ValueError, exc:
|
||||||
raise ErrorResponse(
|
raise ImmediateResponse(
|
||||||
content={'detail': 'JSON parse error - %s' % unicode(exc)},
|
content={'detail': 'JSON parse error - %s' % unicode(exc)},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ if yaml:
|
||||||
try:
|
try:
|
||||||
return (yaml.safe_load(stream), None)
|
return (yaml.safe_load(stream), None)
|
||||||
except ValueError, exc:
|
except ValueError, exc:
|
||||||
raise ErrorResponse(
|
raise ImmediateResponse(
|
||||||
content={'detail': 'YAML parse error - %s' % unicode(exc)},
|
content={'detail': 'YAML parse error - %s' % unicode(exc)},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
else:
|
else:
|
||||||
|
@ -172,7 +172,7 @@ class MultiPartParser(BaseParser):
|
||||||
try:
|
try:
|
||||||
django_parser = DjangoMultiPartParser(self.view.META, stream, upload_handlers)
|
django_parser = DjangoMultiPartParser(self.view.META, stream, upload_handlers)
|
||||||
except MultiPartParserError, exc:
|
except MultiPartParserError, exc:
|
||||||
raise ErrorResponse(
|
raise ImmediateResponse(
|
||||||
content={'detail': 'multipart parse error - %s' % unicode(exc)},
|
content={'detail': 'multipart parse error - %s' % unicode(exc)},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
return django_parser.parse()
|
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 django.core.cache import cache
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
import time
|
import time
|
||||||
|
|
||||||
__all__ = (
|
__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. ' +
|
content={'detail': 'You do not have permission to access this resource. ' +
|
||||||
'You may need to login or otherwise authenticate the request.'},
|
'You may need to login or otherwise authenticate the request.'},
|
||||||
status=status.HTTP_403_FORBIDDEN)
|
status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
_503_SERVICE_UNAVAILABLE = ErrorResponse(
|
_503_SERVICE_UNAVAILABLE = ImmediateResponse(
|
||||||
content={'detail': 'request was throttled'},
|
content={'detail': 'request was throttled'},
|
||||||
status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class BasePermission(object):
|
||||||
|
|
||||||
def check_permission(self, auth):
|
def check_permission(self, auth):
|
||||||
"""
|
"""
|
||||||
Should simply return, or raise an :exc:`response.ErrorResponse`.
|
Should simply return, or raise an :exc:`response.ImmediateResponse`.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class BaseThrottle(BasePermission):
|
||||||
def check_permission(self, auth):
|
def check_permission(self, auth):
|
||||||
"""
|
"""
|
||||||
Check the throttling.
|
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('/')
|
num, period = getattr(self.view, self.attr_name, self.default).split('/')
|
||||||
self.num_requests = int(num)
|
self.num_requests = int(num)
|
||||||
|
|
|
@ -45,7 +45,7 @@ class BaseRenderer(object):
|
||||||
media_type = None
|
media_type = None
|
||||||
format = None
|
format = None
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, view=None):
|
||||||
self.view = view
|
self.view = view
|
||||||
|
|
||||||
def can_handle_response(self, accept):
|
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.)
|
# 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:
|
if not renderers:
|
||||||
return '[No renderers were found]'
|
return '[No renderers were found]'
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ This enhanced request object offers the following :
|
||||||
|
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
|
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
|
||||||
from djangorestframework.utils import as_tuple
|
from djangorestframework.utils import as_tuple
|
||||||
|
@ -194,7 +194,7 @@ class Request(object):
|
||||||
"""
|
"""
|
||||||
Parse the request content.
|
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:
|
if stream is None or content_type is None:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
@ -206,7 +206,7 @@ class Request(object):
|
||||||
if parser.can_handle_request(content_type):
|
if parser.can_handle_request(content_type):
|
||||||
return parser.parse(stream)
|
return parser.parse(stream)
|
||||||
|
|
||||||
raise ErrorResponse(content={'error':
|
raise ImmediateResponse(content={'error':
|
||||||
'Unsupported media type in request \'%s\'.' % content_type},
|
'Unsupported media type in request \'%s\'.' % content_type},
|
||||||
status=status.HTTP_415_UNSUPPORTED_MEDIA_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.core.urlresolvers import reverse, get_urlconf, get_resolver, NoReverseMatch
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
from djangorestframework.serializer import Serializer, _SkipField
|
from djangorestframework.serializer import Serializer, _SkipField
|
||||||
from djangorestframework.utils import as_tuple
|
from djangorestframework.utils import as_tuple
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class BaseResource(Serializer):
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given the request content return the cleaned, validated content.
|
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
|
return data
|
||||||
|
|
||||||
|
@ -73,19 +73,19 @@ class FormResource(Resource):
|
||||||
"""
|
"""
|
||||||
Flag to check for unknown fields when validating a form. If set to false and
|
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
|
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.
|
expected fields are validated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
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
|
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``.
|
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:`'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', ...]}``.
|
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
|
detail[u'field_errors'] = field_errors
|
||||||
|
|
||||||
# Return HTTP 400 response (BAD REQUEST)
|
# 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):
|
def get_form_class(self, method=None):
|
||||||
"""
|
"""
|
||||||
|
@ -273,14 +273,14 @@ class ModelResource(FormResource):
|
||||||
def validate_request(self, data, files=None):
|
def validate_request(self, data, files=None):
|
||||||
"""
|
"""
|
||||||
Given some content as input return some cleaned, validated content.
|
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,
|
Validation is standard form or model form validation,
|
||||||
with an additional constraint that no extra unknown fields may be supplied,
|
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,
|
and that all fields specified by the fields class attribute must be supplied,
|
||||||
even if they are not validated by the form/model form.
|
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 :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}.
|
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
|
The :mod:`response` module provides :class:`Response` and :class:`ImmediateResponse` classes.
|
||||||
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
|
`Response` is a subclass of `HttpResponse`, and can be similarly instantiated and returned
|
||||||
als depending on the accept header of the request.
|
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
|
from django.template.response import SimpleTemplateResponse
|
||||||
|
@ -13,7 +23,7 @@ from djangorestframework.utils import MSIE_USER_AGENT_REGEX
|
||||||
from djangorestframework import status
|
from djangorestframework import status
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('Response', 'ErrorResponse')
|
__all__ = ('Response', 'ImmediateResponse')
|
||||||
|
|
||||||
|
|
||||||
class Response(SimpleTemplateResponse):
|
class Response(SimpleTemplateResponse):
|
||||||
|
@ -26,15 +36,16 @@ class Response(SimpleTemplateResponse):
|
||||||
|
|
||||||
def __init__(self, content=None, status=None, request=None, renderers=None):
|
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.
|
`renderers` is a list/tuple of renderer instances and represents the set of renderers
|
||||||
|
that the response can handle.
|
||||||
Should be a tuple/list of classes as described in the :mod:`renderers` module.
|
|
||||||
"""
|
"""
|
||||||
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
|
# First argument taken by `SimpleTemplateResponse.__init__` is template_name,
|
||||||
# which we don't need
|
# which we don't need
|
||||||
super(Response, self).__init__(None, status=status)
|
super(Response, self).__init__(None, status=status)
|
||||||
|
|
||||||
# We need to store our content in raw content to avoid overriding HttpResponse's
|
# We need to store our content in raw content to avoid overriding HttpResponse's
|
||||||
# `content` property
|
# `content` property
|
||||||
self.raw_content = content
|
self.raw_content = content
|
||||||
|
@ -42,17 +53,14 @@ class Response(SimpleTemplateResponse):
|
||||||
self.request = request
|
self.request = request
|
||||||
if renderers is not None:
|
if renderers is not None:
|
||||||
self.renderers = renderers
|
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
|
@property
|
||||||
def rendered_content(self):
|
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()
|
renderer, media_type = self._determine_renderer()
|
||||||
# TODO: renderer *could* override media_type in .render() if required.
|
|
||||||
|
|
||||||
# Set the media type of the response
|
# Set the media type of the response
|
||||||
self['Content-Type'] = renderer.media_type
|
self['Content-Type'] = renderer.media_type
|
||||||
|
@ -65,12 +73,20 @@ class Response(SimpleTemplateResponse):
|
||||||
@property
|
@property
|
||||||
def status_text(self):
|
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.
|
Provided for convenience.
|
||||||
"""
|
"""
|
||||||
return STATUS_CODE_TEXT.get(self.status, '')
|
return STATUS_CODE_TEXT.get(self.status, '')
|
||||||
|
|
||||||
def _determine_accept_list(self):
|
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
|
request = self.request
|
||||||
if request is None:
|
if request is None:
|
||||||
return ['*/*']
|
return ['*/*']
|
||||||
|
@ -92,7 +108,7 @@ class Response(SimpleTemplateResponse):
|
||||||
|
|
||||||
def _determine_renderer(self):
|
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.
|
and the :attr:`renderers` set on this class.
|
||||||
|
|
||||||
Returns a 2-tuple of `(renderer, media_type)`
|
Returns a 2-tuple of `(renderer, media_type)`
|
||||||
|
@ -103,16 +119,14 @@ class Response(SimpleTemplateResponse):
|
||||||
# attempting more specific media types first
|
# attempting more specific media types first
|
||||||
# NB. The inner loop here isn't as bad as it first looks :)
|
# 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)
|
# 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 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:
|
for media_type in media_type_list:
|
||||||
if renderer.can_handle_response(media_type):
|
if renderer.can_handle_response(media_type):
|
||||||
return renderer, media_type
|
return renderer, media_type
|
||||||
|
|
||||||
# No acceptable renderers were found
|
# 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},
|
'available_types': self._rendered_media_types},
|
||||||
status=status.HTTP_406_NOT_ACCEPTABLE,
|
status=status.HTTP_406_NOT_ACCEPTABLE,
|
||||||
renderers=self.renderers)
|
renderers=self.renderers)
|
||||||
|
@ -152,10 +166,9 @@ class Response(SimpleTemplateResponse):
|
||||||
return self.renderers[0]
|
return self.renderers[0]
|
||||||
|
|
||||||
|
|
||||||
class ErrorResponse(Response, BaseException):
|
class ImmediateResponse(Response, BaseException):
|
||||||
"""
|
"""
|
||||||
An exception representing an Response that should be returned immediately.
|
A subclass of :class:`Response` used to abort the current request handling.
|
||||||
Any content should be serialized as-is, without being filtered.
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,10 @@ class UserAgentMungingTest(TestCase):
|
||||||
|
|
||||||
class MockView(View):
|
class MockView(View):
|
||||||
permissions = ()
|
permissions = ()
|
||||||
|
response_class = Response
|
||||||
|
|
||||||
def get(self, request):
|
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.req = RequestFactory()
|
||||||
self.MockView = MockView
|
self.MockView = MockView
|
||||||
|
|
|
@ -6,7 +6,7 @@ from djangorestframework.compat import RequestFactory
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
|
||||||
from djangorestframework.resources import ModelResource
|
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.models import CustomUser
|
||||||
from djangorestframework.tests.testcases import TestModelsTestCase
|
from djangorestframework.tests.testcases import TestModelsTestCase
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
|
@ -41,7 +41,7 @@ class TestModelRead(TestModelsTestCase):
|
||||||
mixin = ReadModelMixin()
|
mixin = ReadModelMixin()
|
||||||
mixin.resource = GroupResource
|
mixin.resource = GroupResource
|
||||||
|
|
||||||
self.assertRaises(ErrorResponse, mixin.get, request, id=12345)
|
self.assertRaises(ImmediateResponse, mixin.get, request, id=12345)
|
||||||
|
|
||||||
|
|
||||||
class TestModelCreation(TestModelsTestCase):
|
class TestModelCreation(TestModelsTestCase):
|
||||||
|
|
|
@ -73,8 +73,8 @@ class MockGETView(View):
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
|
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderer_classes=[JSONRenderer, JSONPRenderer])),
|
||||||
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
|
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderer_classes=[JSONPRenderer])),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import json
|
import json
|
||||||
|
import unittest
|
||||||
|
|
||||||
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.response import Response, ErrorResponse
|
from djangorestframework.response import Response, ImmediateResponse
|
||||||
from djangorestframework.mixins import ResponseMixin
|
from djangorestframework.mixins import ResponseMixin
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
from djangorestframework.compat import View as DjangoView
|
from djangorestframework.compat import View as DjangoView
|
||||||
|
@ -17,13 +18,16 @@ from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRender
|
||||||
class TestResponseDetermineRenderer(TestCase):
|
class TestResponseDetermineRenderer(TestCase):
|
||||||
|
|
||||||
def get_response(self, url='', accept_list=[], renderers=[]):
|
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)
|
return Response(request=request, renderers=renderers)
|
||||||
|
|
||||||
def get_renderer_mock(self, media_type):
|
def get_renderer_mock(self, media_type):
|
||||||
return type('RendererMock', (BaseRenderer,), {
|
return type('RendererMock', (BaseRenderer,), {
|
||||||
'media_type': media_type,
|
'media_type': media_type,
|
||||||
})
|
})()
|
||||||
|
|
||||||
def test_determine_accept_list_accept_header(self):
|
def test_determine_accept_list_accept_header(self):
|
||||||
"""
|
"""
|
||||||
|
@ -32,6 +36,13 @@ class TestResponseDetermineRenderer(TestCase):
|
||||||
accept_list = ['application/pickle', 'application/json']
|
accept_list = ['application/pickle', 'application/json']
|
||||||
response = self.get_response(accept_list=accept_list)
|
response = self.get_response(accept_list=accept_list)
|
||||||
self.assertEqual(response._determine_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):
|
def test_determine_accept_list_overriden_header(self):
|
||||||
"""
|
"""
|
||||||
|
@ -47,38 +58,46 @@ class TestResponseDetermineRenderer(TestCase):
|
||||||
Test that right renderer is chosen, in the order of Accept list.
|
Test that right renderer is chosen, in the order of Accept list.
|
||||||
"""
|
"""
|
||||||
accept_list = ['application/pickle', 'application/json']
|
accept_list = ['application/pickle', 'application/json']
|
||||||
PRenderer = self.get_renderer_mock('application/pickle')
|
prenderer = self.get_renderer_mock('application/pickle')
|
||||||
JRenderer = self.get_renderer_mock('application/json')
|
jrenderer = self.get_renderer_mock('application/json')
|
||||||
|
|
||||||
renderers = (PRenderer, JRenderer)
|
response = self.get_response(accept_list=accept_list, renderers=(prenderer, jrenderer))
|
||||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
|
||||||
renderer, media_type = response._determine_renderer()
|
renderer, media_type = response._determine_renderer()
|
||||||
self.assertEqual(media_type, 'application/pickle')
|
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=(jrenderer,))
|
||||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
|
||||||
renderer, media_type = response._determine_renderer()
|
renderer, media_type = response._determine_renderer()
|
||||||
self.assertEqual(media_type, 'application/json')
|
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):
|
def test_determine_renderer_no_renderer(self):
|
||||||
"""
|
"""
|
||||||
Test determine renderer when no renderer can satisfy the Accept list.
|
Test determine renderer when no renderer can satisfy the Accept list.
|
||||||
"""
|
"""
|
||||||
accept_list = ['application/json']
|
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=(prenderer,))
|
||||||
response = self.get_response(accept_list=accept_list, renderers=renderers)
|
self.assertRaises(ImmediateResponse, response._determine_renderer)
|
||||||
self.assertRaises(ErrorResponse, response._determine_renderer)
|
|
||||||
|
|
||||||
|
|
||||||
class TestResponseRenderContent(TestCase):
|
class TestResponseRenderContent(TestCase):
|
||||||
|
|
||||||
def get_response(self, url='', accept_list=[], content=None):
|
def get_response(self, url='', accept_list=[], content=None):
|
||||||
request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
|
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):
|
def test_render(self):
|
||||||
"""
|
"""
|
||||||
|
@ -116,7 +135,7 @@ class RendererB(BaseRenderer):
|
||||||
|
|
||||||
|
|
||||||
class MockView(ResponseMixin, DjangoView):
|
class MockView(ResponseMixin, DjangoView):
|
||||||
renderers = (RendererA, RendererB)
|
renderer_classes = (RendererA, RendererB)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||||
|
@ -124,22 +143,22 @@ class MockView(ResponseMixin, DjangoView):
|
||||||
|
|
||||||
|
|
||||||
class HTMLView(View):
|
class HTMLView(View):
|
||||||
renderers = (DocumentingHTMLRenderer, )
|
renderer_classes = (DocumentingHTMLRenderer, )
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return Response('text')
|
return Response('text')
|
||||||
|
|
||||||
|
|
||||||
class HTMLView1(View):
|
class HTMLView1(View):
|
||||||
renderers = (DocumentingHTMLRenderer, JSONRenderer)
|
renderer_classes = (DocumentingHTMLRenderer, JSONRenderer)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return Response('text')
|
return Response('text')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||||
url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
|
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
||||||
url(r'^html$', HTMLView.as_view()),
|
url(r'^html$', HTMLView.as_view()),
|
||||||
url(r'^html1$', HTMLView1.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.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
|
||||||
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
self.assertEquals(resp.status_code, DUMMYSTATUS)
|
||||||
|
|
||||||
# TODO: can't pass because view is a simple Django view and response is an ErrorResponse
|
@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):
|
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."""
|
"""If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
|
||||||
# resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
|
||||||
# self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
|
||||||
|
|
||||||
def test_specified_renderer_serializes_content_on_format_query(self):
|
def test_specified_renderer_serializes_content_on_format_query(self):
|
||||||
"""If a 'format' query is specified, the renderer with the matching
|
"""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.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from djangorestframework.resources import FormResource, ModelResource
|
from djangorestframework.resources import FormResource, ModelResource
|
||||||
from djangorestframework.response import ErrorResponse
|
from djangorestframework.response import ImmediateResponse
|
||||||
from djangorestframework.views import View
|
from djangorestframework.views import View
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ class TestNonFieldErrors(TestCase):
|
||||||
content = {'field1': 'example1', 'field2': 'example2'}
|
content = {'field1': 'example1', 'field2': 'example2'}
|
||||||
try:
|
try:
|
||||||
MockResource(view).validate_request(content, None)
|
MockResource(view).validate_request(content, None)
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
||||||
else:
|
else:
|
||||||
self.fail('ErrorResponse was not raised')
|
self.fail('ImmediateResponse was not raised')
|
||||||
|
|
||||||
|
|
||||||
class TestFormValidation(TestCase):
|
class TestFormValidation(TestCase):
|
||||||
|
@ -120,14 +120,14 @@ class TestFormValidation(TestCase):
|
||||||
def validation_failure_raises_response_exception(self, validator):
|
def validation_failure_raises_response_exception(self, validator):
|
||||||
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
|
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
|
||||||
content = {}
|
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):
|
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.
|
"""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
|
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)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
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):
|
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."""
|
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
|
||||||
|
@ -154,7 +154,7 @@ class TestFormValidation(TestCase):
|
||||||
content = {}
|
content = {}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised')
|
self.fail('ResourceException was not raised')
|
||||||
|
@ -164,7 +164,7 @@ class TestFormValidation(TestCase):
|
||||||
content = {'qwerty': ''}
|
content = {'qwerty': ''}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised')
|
self.fail('ResourceException was not raised')
|
||||||
|
@ -174,7 +174,7 @@ class TestFormValidation(TestCase):
|
||||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
|
self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
|
||||||
else:
|
else:
|
||||||
self.fail('ResourceException was not raised')
|
self.fail('ResourceException was not raised')
|
||||||
|
@ -184,7 +184,7 @@ class TestFormValidation(TestCase):
|
||||||
content = {'qwerty': '', 'extra': 'extra'}
|
content = {'qwerty': '', 'extra': 'extra'}
|
||||||
try:
|
try:
|
||||||
validator.validate_request(content, None)
|
validator.validate_request(content, None)
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
|
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
|
||||||
'extra': ['This field does not exist.']}})
|
'extra': ['This field does not exist.']}})
|
||||||
else:
|
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
|
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)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
|
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):
|
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.
|
"""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
|
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)"""
|
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||||
content = {'readonly': 'read only'}
|
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):
|
def test_validate_does_not_require_blankable_fields_on_model_forms(self):
|
||||||
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
|
"""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 django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from djangorestframework.compat import View as DjangoView, apply_markdown
|
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.mixins import *
|
||||||
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
|
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.
|
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.
|
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.
|
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},
|
{'detail': 'Method \'%s\' not allowed on this resource.' % request.method},
|
||||||
status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
|
@ -232,13 +232,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
self.prepare_response(response)
|
self.prepare_response(response)
|
||||||
|
|
||||||
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
|
# 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'):
|
if hasattr(response, 'raw_content'):
|
||||||
response.raw_content = self.filter_response(response.raw_content)
|
response.raw_content = self.filter_response(response.raw_content)
|
||||||
else:
|
else:
|
||||||
response.content = self.filter_response(response.content)
|
response.content = self.filter_response(response.content)
|
||||||
|
|
||||||
except ErrorResponse, response:
|
except ImmediateResponse, response:
|
||||||
# Prepare response for the response cycle.
|
# Prepare response for the response cycle.
|
||||||
self.prepare_response(response)
|
self.prepare_response(response)
|
||||||
|
|
||||||
|
@ -259,10 +259,10 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
|
||||||
for name, field in form.fields.iteritems():
|
for name, field in form.fields.iteritems():
|
||||||
field_name_types[name] = field.__class__.__name__
|
field_name_types[name] = field.__class__.__name__
|
||||||
content['fields'] = field_name_types
|
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
|
# that should be rendered and returned immediately, without any
|
||||||
# response filtering.
|
# response filtering.
|
||||||
raise ErrorResponse(content=content, status=status.HTTP_200_OK)
|
raise ImmediateResponse(content=content, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ModelView(View):
|
class ModelView(View):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user