aboutsummaryrefslogtreecommitdiffstats
path: root/djangorestframework/response.py
diff options
context:
space:
mode:
Diffstat (limited to 'djangorestframework/response.py')
-rw-r--r--djangorestframework/response.py172
1 files changed, 152 insertions, 20 deletions
diff --git a/djangorestframework/response.py b/djangorestframework/response.py
index 13f0477e..be2c3ebe 100644
--- a/djangorestframework/response.py
+++ b/djangorestframework/response.py
@@ -1,44 +1,176 @@
"""
-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`, for it renders automatically
+its content to a serial format by using a list of :mod:`renderers`.
+
+To determine the content type to which it must render, default behaviour is to use standard
+HTTP Accept header content negotiation. But `Response` also supports overriding the content type
+by specifying an ``_accept=`` parameter in the URL. Also, `Response` will ignore `Accept` headers
+from Internet Explorer user agents and use a sensible browser `Accept` header instead.
+
+
+`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.core.handlers.wsgi import STATUS_CODE_TEXT
-__all__ = ('Response', 'ErrorResponse')
+from djangorestframework.utils.mediatypes import order_by_precedence
+from djangorestframework.utils import MSIE_USER_AGENT_REGEX
+from djangorestframework import status
+
-# TODO: remove raw_content/cleaned_content and just use content?
+__all__ = ('Response', 'ImmediateResponse')
-class Response(object):
+class Response(SimpleTemplateResponse):
"""
An HttpResponse that may include content that hasn't yet been serialized.
+
+ Kwargs:
+ - content(object). The raw content, not yet serialized. This must be simple Python \
+ data that renderers can handle (e.g.: `dict`, `str`, ...)
+ - renderers(list/tuple). The renderers to use for rendering the response content.
"""
- def __init__(self, status=200, content=None, headers=None):
- self.status = status
- self.media_type = None
+ _ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
+ _IGNORE_IE_ACCEPT_HEADER = True
+
+ def __init__(self, content=None, status=None, request=None, renderers=None):
+ # 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
self.has_content_body = content is not None
- self.raw_content = content # content prior to filtering
- self.cleaned_content = content # content after filtering
- self.headers = headers or {}
+ self.request = request
+ if renderers is not None:
+ self.renderers = renderers
+
+ @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()
+
+ # Set the media type of the response
+ self['Content-Type'] = renderer.media_type
+
+ # Render the response content
+ if self.has_content_body:
+ return renderer.render(self.raw_content, media_type)
+ return renderer.render()
@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, '')
+ return STATUS_CODE_TEXT.get(self.status_code, '')
+
+ 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 ['*/*']
+
+ if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
+ # Use _accept parameter override
+ return [request.GET.get(self._ACCEPT_QUERY_PARAM)]
+ elif (self._IGNORE_IE_ACCEPT_HEADER and
+ 'HTTP_USER_AGENT' in request.META and
+ MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT'])):
+ # Ignore MSIE's broken accept behavior and do something sensible instead
+ return ['text/html', '*/*']
+ elif 'HTTP_ACCEPT' in request.META:
+ # Use standard HTTP Accept negotiation
+ return [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')]
+ else:
+ # No accept header specified
+ return ['*/*']
+
+ def _determine_renderer(self):
+ """
+ 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)`
-class ErrorResponse(Exception):
+ See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ """
+ # Check the acceptable media types against each renderer,
+ # 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)
+ for media_type_list in order_by_precedence(self._determine_accept_list()):
+ 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 ImmediateResponse({'detail': 'Could not satisfy the client\'s Accept header',
+ 'available_types': self._rendered_media_types},
+ status=status.HTTP_406_NOT_ACCEPTABLE,
+ renderers=self.renderers)
+
+ def _get_renderers(self):
+ if hasattr(self, '_renderers'):
+ return self._renderers
+ return ()
+
+ def _set_renderers(self, value):
+ self._renderers = value
+
+ renderers = property(_get_renderers, _set_renderers)
+
+ @property
+ def _rendered_media_types(self):
+ """
+ Return an list of all the media types that this response can render.
+ """
+ return [renderer.media_type for renderer in self.renderers]
+
+ @property
+ def _rendered_formats(self):
+ """
+ Return a list of all the formats that this response can render.
+ """
+ return [renderer.format for renderer in self.renderers]
+
+ @property
+ def _default_renderer(self):
+ """
+ Return the response's default renderer class.
+ """
+ return self.renderers[0]
+
+
+class ImmediateResponse(Response, Exception):
"""
- 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.
"""
- def __init__(self, status, content=None, headers={}):
- self.response = Response(status, content=content, headers=headers)
+ def __str__(self):
+ """
+ Since this class is also an exception it has to provide a sensible
+ representation for the cases when it is treated as an exception.
+ """
+ return ('%s must be caught in try/except block, '
+ 'and returned as a normal HttpResponse' % self.__class__.__name__)