2012-01-22 23:28:34 +04:00
|
|
|
"""
|
2012-02-07 18:22:14 +04:00
|
|
|
The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request`
|
|
|
|
object received in all the views.
|
2012-01-24 23:21:10 +04:00
|
|
|
|
2012-02-07 18:22:14 +04:00
|
|
|
The wrapped request then offers a richer API, in particular :
|
2012-01-24 23:21:10 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
- content automatically parsed according to `Content-Type` header,
|
|
|
|
and available as :meth:`.DATA<Request.DATA>`
|
2012-01-24 23:21:10 +04:00
|
|
|
- full support of PUT method, including support for file uploads
|
|
|
|
- form overloading of HTTP method, content type and content
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
|
|
|
|
2012-01-25 02:11:54 +04:00
|
|
|
from djangorestframework import status
|
2012-02-20 13:36:03 +04:00
|
|
|
from djangorestframework.utils.mediatypes import is_form_media_type
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
from StringIO import StringIO
|
|
|
|
|
|
|
|
|
2012-01-24 23:21:10 +04:00
|
|
|
__all__ = ('Request',)
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
class Empty:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _hasattr(obj, name):
|
|
|
|
return not getattr(obj, name) is Empty
|
|
|
|
|
|
|
|
|
2012-01-22 23:28:34 +04:00
|
|
|
class Request(object):
|
2012-01-24 23:21:10 +04:00
|
|
|
"""
|
2012-02-07 18:22:14 +04:00
|
|
|
Wrapper allowing to enhance a standard `HttpRequest` instance.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
- request(HttpRequest). The original request instance.
|
|
|
|
- parsers(list/tuple). The parsers to use for parsing the request content.
|
2012-01-24 23:21:10 +04:00
|
|
|
"""
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
_USE_FORM_OVERLOADING = True
|
|
|
|
_METHOD_PARAM = '_method'
|
|
|
|
_CONTENTTYPE_PARAM = '_content_type'
|
|
|
|
_CONTENT_PARAM = '_content'
|
|
|
|
|
2012-02-07 17:38:54 +04:00
|
|
|
def __init__(self, request=None, parsers=None):
|
2012-02-25 22:45:17 +04:00
|
|
|
self._request = request
|
|
|
|
self.parsers = parsers or ()
|
|
|
|
self._data = Empty
|
|
|
|
self._files = Empty
|
|
|
|
self._method = Empty
|
|
|
|
self._content_type = Empty
|
|
|
|
self._stream = Empty
|
|
|
|
|
|
|
|
def get_parsers(self):
|
|
|
|
"""
|
|
|
|
Instantiates and returns the list of parsers the request will use.
|
|
|
|
"""
|
|
|
|
return [parser() for parser in self.parsers]
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
@property
|
|
|
|
def method(self):
|
|
|
|
"""
|
|
|
|
Returns the HTTP method.
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
This allows the `method` to be overridden by using a hidden `form`
|
|
|
|
field on a form POST request.
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_method'):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._load_method_and_content_type()
|
|
|
|
return self._method
|
|
|
|
|
|
|
|
@property
|
|
|
|
def content_type(self):
|
|
|
|
"""
|
|
|
|
Returns the content type header.
|
|
|
|
|
|
|
|
This should be used instead of ``request.META.get('HTTP_CONTENT_TYPE')``,
|
|
|
|
as it allows the content type to be overridden by using a hidden form
|
|
|
|
field on a form POST request.
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_content_type'):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._load_method_and_content_type()
|
|
|
|
return self._content_type
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
@property
|
|
|
|
def stream(self):
|
|
|
|
"""
|
|
|
|
Returns an object that may be used to stream the request content.
|
|
|
|
"""
|
|
|
|
if not _hasattr(self, '_stream'):
|
|
|
|
self._load_stream()
|
|
|
|
return self._stream
|
|
|
|
|
2012-01-22 23:28:34 +04:00
|
|
|
@property
|
|
|
|
def DATA(self):
|
|
|
|
"""
|
|
|
|
Parses the request body and returns the data.
|
|
|
|
|
|
|
|
Similar to ``request.POST``, except that it handles arbitrary parsers,
|
|
|
|
and also works on methods other than POST (eg PUT).
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_data'):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._load_data_and_files()
|
|
|
|
return self._data
|
|
|
|
|
|
|
|
@property
|
|
|
|
def FILES(self):
|
|
|
|
"""
|
|
|
|
Parses the request body and returns the files.
|
|
|
|
Similar to ``request.FILES``, except that it handles arbitrary parsers,
|
|
|
|
and also works on methods other than POST (eg PUT).
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_files'):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._load_data_and_files()
|
|
|
|
return self._files
|
|
|
|
|
|
|
|
def _load_data_and_files(self):
|
|
|
|
"""
|
|
|
|
Parses the request content into self.DATA and self.FILES.
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_content_type'):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._load_method_and_content_type()
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_data'):
|
|
|
|
(self._data, self._files) = self._parse()
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
def _load_method_and_content_type(self):
|
|
|
|
"""
|
|
|
|
Sets the method and content_type, and then check if they've been overridden.
|
|
|
|
"""
|
|
|
|
self._content_type = self.META.get('HTTP_CONTENT_TYPE', self.META.get('CONTENT_TYPE', ''))
|
|
|
|
self._perform_form_overloading()
|
2012-02-20 13:36:03 +04:00
|
|
|
# if the HTTP method was not overloaded, we take the raw HTTP method
|
2012-02-25 22:45:17 +04:00
|
|
|
if not _hasattr(self, '_method'):
|
|
|
|
self._method = self._request.method
|
2012-01-22 23:28:34 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
def _load_stream(self):
|
2012-01-22 23:28:34 +04:00
|
|
|
try:
|
2012-02-25 22:45:17 +04:00
|
|
|
content_length = int(self.META.get('CONTENT_LENGTH',
|
|
|
|
self.META.get('HTTP_CONTENT_LENGTH')))
|
2012-01-22 23:28:34 +04:00
|
|
|
except (ValueError, TypeError):
|
|
|
|
content_length = 0
|
|
|
|
|
|
|
|
if content_length == 0:
|
2012-02-25 22:45:17 +04:00
|
|
|
self._stream = None
|
|
|
|
elif hasattr(self._request, 'read'):
|
|
|
|
self._stream = self._request
|
|
|
|
else:
|
|
|
|
self._stream = StringIO(self.raw_post_data)
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
def _perform_form_overloading(self):
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
If this is a form POST request, then we need to check if the method and
|
|
|
|
content/content_type have been overridden by setting them in hidden
|
|
|
|
form fields or not.
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
|
|
|
|
|
|
|
# We only need to use form overloading on form POST requests.
|
2012-02-25 22:45:17 +04:00
|
|
|
if (not self._USE_FORM_OVERLOADING
|
|
|
|
or self._request.method != 'POST'
|
|
|
|
or not is_form_media_type(self._content_type)):
|
2012-01-22 23:28:34 +04:00
|
|
|
return
|
|
|
|
|
|
|
|
# At this point we're committed to parsing the request as form data.
|
2012-02-25 22:45:17 +04:00
|
|
|
self._data = self._request.POST
|
|
|
|
self._files = self._request.FILES
|
2012-01-22 23:28:34 +04:00
|
|
|
|
|
|
|
# Method overloading - change the method and remove the param from the content.
|
2012-02-25 22:45:17 +04:00
|
|
|
if self._METHOD_PARAM in self._data:
|
|
|
|
# NOTE: `pop` on a `QueryDict` returns a list of values.
|
2012-01-22 23:28:34 +04:00
|
|
|
self._method = self._data.pop(self._METHOD_PARAM)[0].upper()
|
|
|
|
|
|
|
|
# Content overloading - modify the content type, and re-parse.
|
2012-02-25 22:45:17 +04:00
|
|
|
if (self._CONTENT_PARAM in self._data and
|
|
|
|
self._CONTENTTYPE_PARAM in self._data):
|
2012-01-22 23:28:34 +04:00
|
|
|
self._content_type = self._data.pop(self._CONTENTTYPE_PARAM)[0]
|
2012-02-25 22:45:17 +04:00
|
|
|
self._stream = StringIO(self._data.pop(self._CONTENT_PARAM)[0])
|
|
|
|
(self._data, self._files) = self._parse()
|
2012-01-22 23:28:34 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
def _parse(self):
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
|
|
|
Parse the request content.
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
May raise a 415 ImmediateResponse (Unsupported Media Type), or a
|
|
|
|
400 ImmediateResponse (Bad Request).
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
if self.stream is None or self.content_type is None:
|
2012-01-22 23:28:34 +04:00
|
|
|
return (None, None)
|
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
for parser in self.get_parsers():
|
|
|
|
if parser.can_handle_request(self.content_type):
|
|
|
|
return parser.parse(self.stream, self.META, self.upload_handlers)
|
2012-01-22 23:28:34 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
self._raise_415_response(self._content_type)
|
2012-01-22 23:28:34 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
def _raise_415_response(self, content_type):
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
Raise a 415 response if we cannot parse the given content type.
|
2012-01-22 23:28:34 +04:00
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
from djangorestframework.response import ImmediateResponse
|
2012-01-22 23:28:34 +04:00
|
|
|
|
2012-02-25 22:45:17 +04:00
|
|
|
raise ImmediateResponse(
|
|
|
|
{
|
|
|
|
'error': 'Unsupported media type in request \'%s\'.'
|
|
|
|
% content_type
|
|
|
|
},
|
|
|
|
status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
|
2012-02-07 17:38:54 +04:00
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
Proxy other attributes to the underlying HttpRequest object.
|
2012-02-07 17:38:54 +04:00
|
|
|
"""
|
2012-02-25 22:45:17 +04:00
|
|
|
return getattr(self._request, name)
|