aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/request.py
diff options
context:
space:
mode:
authorTom Christie2012-10-30 14:32:31 +0000
committerTom Christie2012-10-30 14:32:31 +0000
commit9b30dab4f772f67a626e176dc4fae0a3ef9c2c81 (patch)
treeca138abf4792f58ffa28684f784f201ee1eef6d7 /rest_framework/request.py
parent7e5b1501b5cede61a9391fb1a751d2ebcdb37031 (diff)
parent4e7805cb24d73e7f706318b5e5a27e3f9ba39d14 (diff)
downloaddjango-rest-framework-9b30dab4f772f67a626e176dc4fae0a3ef9c2c81.tar.bz2
Merge branch 'restframework2' into rest-framework-2-merge2.0.0
Conflicts: .gitignore .travis.yml AUTHORS README.rst djangorestframework/mixins.py djangorestframework/renderers.py djangorestframework/resources.py djangorestframework/serializer.py djangorestframework/templates/djangorestframework/base.html djangorestframework/templates/djangorestframework/login.html djangorestframework/templatetags/add_query_param.py djangorestframework/tests/accept.py djangorestframework/tests/authentication.py djangorestframework/tests/content.py djangorestframework/tests/reverse.py djangorestframework/tests/serializer.py djangorestframework/views.py docs/examples.rst docs/examples/blogpost.rst docs/examples/modelviews.rst docs/examples/objectstore.rst docs/examples/permissions.rst docs/examples/pygments.rst docs/examples/views.rst docs/howto/alternativeframeworks.rst docs/howto/mixin.rst docs/howto/reverse.rst docs/howto/usingurllib2.rst docs/index.rst docs/topics/release-notes.md examples/sandbox/views.py rest_framework/__init__.py rest_framework/compat.py rest_framework/utils/breadcrumbs.py setup.py
Diffstat (limited to 'rest_framework/request.py')
-rw-r--r--rest_framework/request.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/rest_framework/request.py b/rest_framework/request.py
new file mode 100644
index 00000000..a1827ba4
--- /dev/null
+++ b/rest_framework/request.py
@@ -0,0 +1,317 @@
+"""
+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 django.http.multipartparser import parse_header
+from rest_framework import exceptions
+from rest_framework.settings import api_settings
+
+
+def is_form_media_type(media_type):
+ """
+ Return True if the media type is a valid form media type.
+ """
+ base_media_type, params = parse_header(media_type)
+ return (base_media_type == 'application/x-www-form-urlencoded' or
+ base_media_type == 'multipart/form-data')
+
+
+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
+
+
+def clone_request(request, method):
+ """
+ Internal helper method to clone a request, replacing with a different
+ HTTP method. Used for checking permissions against other methods.
+ """
+ ret = Request(request._request,
+ request.parsers,
+ request.authenticators,
+ request.parser_context)
+ ret._data = request._data
+ ret._files = request._files
+ ret._content_type = request._content_type
+ ret._stream = request._stream
+ ret._method = method
+ if hasattr(request, '_user'):
+ ret._user = request._user
+ if hasattr(request, '_auth'):
+ ret._auth = request._auth
+ return ret
+
+
+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, parsers=None, authenticators=None,
+ negotiator=None, parser_context=None):
+ self._request = request
+ self.parsers = parsers or ()
+ self.authenticators = authenticators or ()
+ self.negotiator = negotiator or self._default_negotiator()
+ self.parser_context = parser_context
+ self._data = Empty
+ self._files = Empty
+ self._method = Empty
+ self._content_type = Empty
+ self._stream = Empty
+
+ if self.parser_context is None:
+ self.parser_context = {}
+ self.parser_context['request'] = self
+
+ def _default_negotiator(self):
+ return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
+
+ @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 QUERY_PARAMS(self):
+ """
+ More semantically correct name for request.GET.
+ """
+ return self._request.GET
+
+ @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()
+
+ # Content overloading - modify the content type, and force 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, self._files = (Empty, Empty)
+
+ def _parse(self):
+ """
+ Parse the request content, returning a two-tuple of (data, files)
+
+ May raise an `UnsupportedMediaType`, or `ParseError` exception.
+ """
+ stream = self.stream
+ media_type = self.content_type
+
+ if stream is None or media_type is None:
+ return (None, None)
+
+ parser = self.negotiator.select_parser(self, self.parsers)
+
+ if not parser:
+ raise exceptions.UnsupportedMediaType(media_type)
+
+ parsed = parser.parse(stream, media_type, self.parser_context)
+
+ # 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)
+
+ def _authenticate(self):
+ """
+ Attempt to authenticate the request using each authentication instance in turn.
+ Returns a two-tuple of (user, authtoken).
+ """
+ for authenticator in self.authenticators:
+ user_auth_tuple = authenticator.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)