aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/request.py
diff options
context:
space:
mode:
authorTom Christie2012-09-20 13:06:27 +0100
committerTom Christie2012-09-20 13:06:27 +0100
commit4b691c402707775c3048a90531024f3bc5be6f91 (patch)
tree3adfc54b0d8b70e4ea78edf7091f7827fa68f47b /rest_framework/request.py
parenta1bcfbfe926621820832e32b0427601e1140b4f7 (diff)
downloaddjango-rest-framework-4b691c402707775c3048a90531024f3bc5be6f91.tar.bz2
Change package name: djangorestframework -> rest_framework
Diffstat (limited to 'rest_framework/request.py')
-rw-r--r--rest_framework/request.py284
1 files changed, 284 insertions, 0 deletions
diff --git a/rest_framework/request.py b/rest_framework/request.py
new file mode 100644
index 00000000..ee5e46d0
--- /dev/null
+++ b/rest_framework/request.py
@@ -0,0 +1,284 @@
+"""
+The :mod:`request` module provides a :class:`Request` class used to wrap the standard `request`
+object received in all the views.
+
+The wrapped request then offers a richer API, in particular :
+
+ - content automatically parsed according to `Content-Type` header,
+ and available as :meth:`.DATA<Request.DATA>`
+ - full support of PUT method, including support for file uploads
+ - form overloading of HTTP method, content type and content
+"""
+from StringIO import StringIO
+
+from rest_framework import exceptions
+from rest_framework.settings import api_settings
+from rest_framework.utils.mediatypes import is_form_media_type
+
+
+__all__ = ('Request',)
+
+
+class Empty(object):
+ """
+ Placeholder for unset attributes.
+ Cannot use `None`, as that may be a valid value.
+ """
+ pass
+
+
+def _hasattr(obj, name):
+ return not getattr(obj, name) is Empty
+
+
+class Request(object):
+ """
+ Wrapper allowing to enhance a standard `HttpRequest` instance.
+
+ Kwargs:
+ - request(HttpRequest). The original request instance.
+ - parsers_classes(list/tuple). The parsers to use for parsing the
+ request content.
+ - authentication_classes(list/tuple). The authentications used to try
+ authenticating the request's user.
+ """
+
+ _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE
+ _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE
+ _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE
+
+ def __init__(self, request, parser_classes=None, authentication_classes=None):
+ self._request = request
+ self.parser_classes = parser_classes or ()
+ self.authentication_classes = authentication_classes 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.parser_classes]
+
+ def get_authentications(self):
+ """
+ Instantiates and returns the list of parsers the request will use.
+ """
+ return [authentication() for authentication in self.authentication_classes]
+
+ @property
+ def method(self):
+ """
+ Returns the HTTP method.
+
+ This allows the `method` to be overridden by using a hidden `form`
+ field on a form POST request.
+ """
+ if not _hasattr(self, '_method'):
+ 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.
+ """
+ if not _hasattr(self, '_content_type'):
+ self._load_method_and_content_type()
+ return self._content_type
+
+ @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
+
+ @property
+ def DATA(self):
+ """
+ Parses the request body and returns the data.
+
+ Similar to usual behaviour of `request.POST`, except that it handles
+ arbitrary parsers, and also works on methods other than POST (eg PUT).
+ """
+ if not _hasattr(self, '_data'):
+ self._load_data_and_files()
+ return self._data
+
+ @property
+ def FILES(self):
+ """
+ Parses the request body and returns any files uploaded in the request.
+
+ Similar to usual behaviour of `request.FILES`, except that it handles
+ arbitrary parsers, and also works on methods other than POST (eg PUT).
+ """
+ if not _hasattr(self, '_files'):
+ self._load_data_and_files()
+ return self._files
+
+ @property
+ def user(self):
+ """
+ Returns the user associated with the current request, as authenticated
+ by the authentication classes provided to the request.
+ """
+ if not hasattr(self, '_user'):
+ self._user, self._auth = self._authenticate()
+ return self._user
+
+ @property
+ def auth(self):
+ """
+ Returns any non-user authentication information associated with the
+ request, such as an authentication token.
+ """
+ if not hasattr(self, '_auth'):
+ self._user, self._auth = self._authenticate()
+ return self._auth
+
+ def _load_data_and_files(self):
+ """
+ Parses the request content into self.DATA and self.FILES.
+ """
+ if not _hasattr(self, '_content_type'):
+ self._load_method_and_content_type()
+
+ if not _hasattr(self, '_data'):
+ self._data, self._files = self._parse()
+
+ 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()
+ # if the HTTP method was not overloaded, we take the raw HTTP method
+ if not _hasattr(self, '_method'):
+ self._method = self._request.method
+
+ def _load_stream(self):
+ """
+ Return the content body of the request, as a stream.
+ """
+ try:
+ content_length = int(self.META.get('CONTENT_LENGTH',
+ self.META.get('HTTP_CONTENT_LENGTH')))
+ except (ValueError, TypeError):
+ content_length = 0
+
+ if content_length == 0:
+ self._stream = None
+ elif hasattr(self._request, 'read'):
+ self._stream = self._request
+ else:
+ self._stream = StringIO(self.raw_post_data)
+
+ def _perform_form_overloading(self):
+ """
+ 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.
+ """
+
+ USE_FORM_OVERLOADING = (
+ self._METHOD_PARAM or
+ (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM)
+ )
+
+ # We only need to use form overloading on form POST requests.
+ if (not USE_FORM_OVERLOADING
+ or self._request.method != 'POST'
+ or not is_form_media_type(self._content_type)):
+ return
+
+ # At this point we're committed to parsing the request as form data.
+ self._data = self._request.POST
+ self._files = self._request.FILES
+
+ # Method overloading - change the method and remove the param from the content.
+ if (self._METHOD_PARAM and
+ self._METHOD_PARAM in self._data):
+ self._method = self._data[self._METHOD_PARAM].upper()
+ self._data.pop(self._METHOD_PARAM)
+
+ # Content overloading - modify the content type, and re-parse.
+ if (self._CONTENT_PARAM and
+ self._CONTENTTYPE_PARAM and
+ self._CONTENT_PARAM in self._data and
+ self._CONTENTTYPE_PARAM in self._data):
+ self._content_type = self._data[self._CONTENTTYPE_PARAM]
+ self._stream = StringIO(self._data[self._CONTENT_PARAM])
+ self._data.pop(self._CONTENTTYPE_PARAM)
+ self._data.pop(self._CONTENT_PARAM)
+ self._data, self._files = self._parse()
+
+ def _parse(self):
+ """
+ Parse the request content, returning a two-tuple of (data, files)
+
+ May raise an `UnsupportedMediaType`, or `ParseError` exception.
+ """
+ if self.stream is None or self.content_type is None:
+ return (None, None)
+
+ for parser in self.get_parsers():
+ if parser.can_handle_request(self.content_type):
+ parsed = parser.parse(self.stream, meta=self.META,
+ upload_handlers=self.upload_handlers)
+ # Parser classes may return the raw data, or a
+ # DataAndFiles object. Unpack the result as required.
+ try:
+ return (parsed.data, parsed.files)
+ except AttributeError:
+ return (parsed, None)
+
+ raise exceptions.UnsupportedMediaType(self._content_type)
+
+ def _authenticate(self):
+ """
+ Attempt to authenticate the request using each authentication instance in turn.
+ Returns a two-tuple of (user, authtoken).
+ """
+ for authentication in self.get_authentications():
+ user_auth_tuple = authentication.authenticate(self)
+ if not user_auth_tuple is None:
+ return user_auth_tuple
+ return self._not_authenticated()
+
+ def _not_authenticated(self):
+ """
+ Return a two-tuple of (user, authtoken), representing an
+ unauthenticated request.
+
+ By default this will be (AnonymousUser, None).
+ """
+ if api_settings.UNAUTHENTICATED_USER:
+ user = api_settings.UNAUTHENTICATED_USER()
+ else:
+ user = None
+
+ if api_settings.UNAUTHENTICATED_TOKEN:
+ auth = api_settings.UNAUTHENTICATED_TOKEN()
+ else:
+ auth = None
+
+ return (user, auth)
+
+ def __getattr__(self, attr):
+ """
+ Proxy other attributes to the underlying HttpRequest object.
+ """
+ return getattr(self._request, attr)