diff options
| author | Tom Christie | 2012-09-20 13:06:27 +0100 | 
|---|---|---|
| committer | Tom Christie | 2012-09-20 13:06:27 +0100 | 
| commit | 4b691c402707775c3048a90531024f3bc5be6f91 (patch) | |
| tree | 3adfc54b0d8b70e4ea78edf7091f7827fa68f47b /rest_framework/request.py | |
| parent | a1bcfbfe926621820832e32b0427601e1140b4f7 (diff) | |
| download | django-rest-framework-4b691c402707775c3048a90531024f3bc5be6f91.tar.bz2 | |
Change package name: djangorestframework -> rest_framework
Diffstat (limited to 'rest_framework/request.py')
| -rw-r--r-- | rest_framework/request.py | 284 | 
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) | 
