2011-05-19 21:36:30 +04:00
|
|
|
"""
|
2011-12-29 17:31:12 +04:00
|
|
|
The :mod:`response` module provides Response classes you can use in your
|
|
|
|
views to return a certain HTTP response. Typically a response is *rendered*
|
2011-05-19 21:36:30 +04:00
|
|
|
into a HTTP response depending on what renderers are set on your view and
|
2011-12-29 17:31:12 +04:00
|
|
|
als depending on the accept header of the request.
|
2011-05-19 21:36:30 +04:00
|
|
|
"""
|
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
from django.template.response import SimpleTemplateResponse
|
2011-01-24 02:08:16 +03:00
|
|
|
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
from djangorestframework.utils.mediatypes import order_by_precedence
|
|
|
|
from djangorestframework.utils import MSIE_USER_AGENT_REGEX
|
|
|
|
from djangorestframework import status
|
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
__all__ = ('Response', 'ErrorResponse')
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2012-01-21 22:33:34 +04:00
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
class Response(SimpleTemplateResponse):
|
2011-05-10 19:01:58 +04:00
|
|
|
"""
|
|
|
|
An HttpResponse that may include content that hasn't yet been serialized.
|
|
|
|
"""
|
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
_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):
|
|
|
|
"""
|
|
|
|
content is the raw content.
|
|
|
|
|
|
|
|
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,
|
|
|
|
# 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
|
2011-04-11 19:38:00 +04:00
|
|
|
self.has_content_body = content is not None
|
2012-02-02 20:19:44 +04:00
|
|
|
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):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
|
|
# Render the response content
|
|
|
|
if self.has_content_body:
|
|
|
|
return renderer.render(self.raw_content, media_type)
|
|
|
|
return renderer.render()
|
2011-12-29 17:31:12 +04:00
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
@property
|
|
|
|
def status_text(self):
|
2011-05-10 19:01:58 +04:00
|
|
|
"""
|
|
|
|
Return reason text corresponding to our HTTP response status code.
|
|
|
|
Provided for convenience.
|
|
|
|
"""
|
2011-01-24 02:08:16 +03:00
|
|
|
return STATUS_CODE_TEXT.get(self.status, '')
|
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
def _determine_accept_list(self):
|
|
|
|
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 client's 'Accept' header,
|
|
|
|
and the :attr:`renderers` set on this class.
|
|
|
|
|
|
|
|
Returns a 2-tuple of `(renderer, media_type)`
|
|
|
|
|
|
|
|
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)
|
|
|
|
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 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',
|
|
|
|
'available_types': self._rendered_media_types},
|
|
|
|
status=status.HTTP_406_NOT_ACCEPTABLE,
|
|
|
|
renderers=self.renderers)
|
|
|
|
|
|
|
|
def _get_renderers(self):
|
|
|
|
"""
|
|
|
|
This just provides a default when renderers havent' been set.
|
|
|
|
"""
|
|
|
|
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]
|
|
|
|
|
2011-01-24 02:08:16 +03:00
|
|
|
|
2012-02-02 20:19:44 +04:00
|
|
|
class ErrorResponse(Response, BaseException):
|
2011-05-10 19:01:58 +04:00
|
|
|
"""
|
|
|
|
An exception representing an Response that should be returned immediately.
|
|
|
|
Any content should be serialized as-is, without being filtered.
|
|
|
|
"""
|
2012-02-02 20:19:44 +04:00
|
|
|
pass
|
2011-05-10 19:01:58 +04:00
|
|
|
|