diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/authentication.py | 40 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 21 | ||||
| -rw-r--r-- | rest_framework/permissions.py | 15 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 4 | ||||
| -rw-r--r-- | rest_framework/request.py | 29 | ||||
| -rw-r--r-- | rest_framework/resources.py | 1 | ||||
| -rw-r--r-- | rest_framework/settings.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/request.py | 2 | ||||
| -rw-r--r-- | rest_framework/utils/mediatypes.py | 26 | ||||
| -rw-r--r-- | rest_framework/views.py | 22 | 
10 files changed, 73 insertions, 89 deletions
diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index ee5bd2f2..d7624708 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -1,10 +1,9 @@  """ -The :mod:`authentication` module provides a set of pluggable authentication classes. - -Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` class into a :class:`View` class. +Provides a set of pluggable authentication policies.  """  from django.contrib.auth import authenticate +from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError  from rest_framework.compat import CsrfViewMiddleware  from rest_framework.authtoken.models import Token  import base64 @@ -17,25 +16,14 @@ class BaseAuthentication(object):      def authenticate(self, request):          """ -        Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_ - -        .. [*] The authentication context *will* typically be a :obj:`User`, -            but it need not be.  It can be any user-like object so long as the -            permissions classes (see the :mod:`permissions` module) on the view can -            handle the object and use it to determine if the request has the required -            permissions or not. - -            This can be an important distinction if you're implementing some token -            based authentication mechanism, where the authentication context -            may be more involved than simply mapping to a :obj:`User`. +        Authenticate the request and return a two-tuple of (user, token).          """ -        return None +        raise NotImplementedError(".authenticate() must be overridden.")  class BasicAuthentication(BaseAuthentication):      """ -    Base class for HTTP Basic authentication. -    Subclasses should implement `.authenticate_credentials()`. +    HTTP Basic authentication against username/password.      """      def authenticate(self, request): @@ -43,8 +31,6 @@ class BasicAuthentication(BaseAuthentication):          Returns a `User` if a correct username and password have been supplied          using HTTP Basic authentication.  Otherwise returns `None`.          """ -        from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError -          if 'HTTP_AUTHORIZATION' in request.META:              auth = request.META['HTTP_AUTHORIZATION'].split()              if len(auth) == 2 and auth[0].lower() == "basic": @@ -54,7 +40,8 @@ class BasicAuthentication(BaseAuthentication):                      return None                  try: -                    userid, password = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2]) +                    userid = smart_unicode(auth_parts[0]) +                    password = smart_unicode(auth_parts[2])                  except DjangoUnicodeDecodeError:                      return None @@ -62,15 +49,6 @@ class BasicAuthentication(BaseAuthentication):      def authenticate_credentials(self, userid, password):          """ -        Given the Basic authentication userid and password, authenticate -        and return a user instance. -        """ -        raise NotImplementedError('.authenticate_credentials() must be overridden') - - -class UserBasicAuthentication(BasicAuthentication): -    def authenticate_credentials(self, userid, password): -        """          Authenticate the userid and password against username and password.          """          user = authenticate(username=userid, password=password) @@ -85,8 +63,8 @@ class SessionAuthentication(BaseAuthentication):      def authenticate(self, request):          """ -        Returns a :obj:`User` if the request session currently has a logged in user. -        Otherwise returns :const:`None`. +        Returns a `User` if the request session currently has a logged in user. +        Otherwise returns `None`.          """          # Get the underlying HttpRequest object diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 672f6a16..048b71e1 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -38,7 +38,7 @@ class BaseParser(object):      media_type = None -    def parse(self, string_or_stream, **opts): +    def parse(self, string_or_stream, parser_context=None):          """          The main entry point to parsers.  This is a light wrapper around          `parse_stream`, that instead handles both string and stream objects. @@ -47,9 +47,9 @@ class BaseParser(object):              stream = BytesIO(string_or_stream)          else:              stream = string_or_stream -        return self.parse_stream(stream, **opts) +        return self.parse_stream(stream, parser_context) -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Given a stream to read from, return the deserialized output.          Should return parsed data, or a DataAndFiles object consisting of the @@ -65,7 +65,7 @@ class JSONParser(BaseParser):      media_type = 'application/json' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Returns a 2-tuple of `(data, files)`. @@ -85,7 +85,7 @@ class YAMLParser(BaseParser):      media_type = 'application/yaml' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Returns a 2-tuple of `(data, files)`. @@ -105,7 +105,7 @@ class FormParser(BaseParser):      media_type = 'application/x-www-form-urlencoded' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Returns a 2-tuple of `(data, files)`. @@ -123,15 +123,16 @@ class MultiPartParser(BaseParser):      media_type = 'multipart/form-data' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Returns a DataAndFiles object.          `.data` will be a `QueryDict` containing all the form parameters.          `.files` will be a `QueryDict` containing all the form files.          """ -        meta = opts['meta'] -        upload_handlers = opts['upload_handlers'] +        parser_context = parser_context or {} +        meta = parser_context['meta'] +        upload_handlers = parser_context['upload_handlers']          try:              parser = DjangoMultiPartParser(meta, stream, upload_handlers)              data, files = parser.parse() @@ -147,7 +148,7 @@ class XMLParser(BaseParser):      media_type = 'application/xml' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          try:              tree = ET.parse(stream)          except (ExpatError, ETParseError, ValueError), exc: diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 13ea39ea..6f848cee 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -1,8 +1,5 @@  """ -The :mod:`permissions` module bundles a set of permission classes that are used -for checking if a request passes a certain set of constraints. - -Permission behavior is provided by mixing the :class:`mixins.PermissionsMixin` class into a :class:`View` class. +Provides a set of pluggable permission policies.  """ @@ -16,7 +13,7 @@ class BasePermission(object):      def has_permission(self, request, view, obj=None):          """ -        Should simply return, or raise an :exc:`response.ImmediateResponse`. +        Return `True` if permission is granted, `False` otherwise.          """          raise NotImplementedError(".has_permission() must be overridden.") @@ -64,7 +61,8 @@ class DjangoModelPermissions(BasePermission):      It ensures that the user is authenticated, and has the appropriate      `add`/`change`/`delete` permissions on the model. -    This permission should only be used on views with a `ModelResource`. +    This permission will only be applied against view classes that +    provide a `.model` attribute, such as the generic class-based views.      """      # Map methods into required permission codes. @@ -92,7 +90,10 @@ class DjangoModelPermissions(BasePermission):          return [perm % kwargs for perm in self.perms_map[method]]      def has_permission(self, request, view, obj=None): -        model_cls = view.model +        model_cls = getattr(view, 'model', None) +        if not model_cls: +            return True +          perms = self.get_required_permissions(request.method, model_cls)          if (request.user and diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index e5e4134b..2a3b0b6c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -7,6 +7,7 @@ and providing forms and links depending on the allowed methods, renderers and pa  """  import string  from django import forms +from django.http.multipartparser import parse_header  from django.template import RequestContext, loader  from django.utils import simplejson as json  from rest_framework.compat import yaml @@ -16,7 +17,6 @@ from rest_framework.request import clone_request  from rest_framework.utils import dict2xml  from rest_framework.utils import encoders  from rest_framework.utils.breadcrumbs import get_breadcrumbs -from rest_framework.utils.mediatypes import get_media_type_params  from rest_framework import VERSION  from rest_framework import serializers, parsers @@ -58,7 +58,7 @@ class JSONRenderer(BaseRenderer):          if accepted_media_type:              # If the media type looks like 'application/json; indent=4',              # then pretty print the result. -            params = get_media_type_params(accepted_media_type) +            base_media_type, params = parse_header(accepted_media_type)              indent = params.get('indent', indent)              try:                  indent = max(min(int(indent), 8), 0) diff --git a/rest_framework/request.py b/rest_framework/request.py index 7267b368..6f9cf09a 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -11,9 +11,18 @@ The wrapped request then offers a richer API, in particular :  """  from StringIO import StringIO +from django.http.multipartparser import parse_header  from rest_framework import exceptions  from rest_framework.settings import api_settings -from rest_framework.utils.mediatypes import is_form_media_type + + +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): @@ -35,7 +44,8 @@ def clone_request(request, method):      """      ret = Request(request._request,                    request.parsers, -                  request.authenticators) +                  request.authenticators, +                  request.parser_context)      ret._data = request._data      ret._files = request._files      ret._content_type = request._content_type @@ -65,20 +75,30 @@ class Request(object):      _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE      def __init__(self, request, parsers=None, authenticators=None, -                 negotiator=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._default_parser_context(request) +      def _default_negotiator(self):          return api_settings.DEFAULT_CONTENT_NEGOTIATION() +    def _default_parser_context(self, request): +        return { +            'upload_handlers': request.upload_handlers, +            'meta': request.META, +        } +      @property      def method(self):          """ @@ -253,8 +273,7 @@ class Request(object):          if not parser:              raise exceptions.UnsupportedMediaType(self.content_type) -        parsed = parser.parse(self.stream, meta=self.META, -                              upload_handlers=self.upload_handlers) +        parsed = parser.parse(self.stream, self.parser_context)          # Parser classes may return the raw data, or a          # DataAndFiles object.  Unpack the result as required.          try: diff --git a/rest_framework/resources.py b/rest_framework/resources.py index bb3d581f..dd8a5471 100644 --- a/rest_framework/resources.py +++ b/rest_framework/resources.py @@ -70,6 +70,7 @@ class Resource(ResourceMixin, views.APIView):  ##### RESOURCES AND ROUTERS ARE NOT YET IMPLEMENTED - PLACEHOLDER ONLY #####  class ModelResource(ResourceMixin, views.APIView): +    # TODO: Actually delegation won't work      root_class = generics.ListCreateAPIView      detail_class = generics.RetrieveUpdateDestroyAPIView diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 5ebe7ba5..8bbb2f75 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -35,7 +35,7 @@ DEFAULTS = {      ),      'DEFAULT_AUTHENTICATION': (          'rest_framework.authentication.SessionAuthentication', -        'rest_framework.authentication.UserBasicAuthentication' +        'rest_framework.authentication.BasicAuthentication'      ),      'DEFAULT_PERMISSIONS': (),      'DEFAULT_THROTTLES': (), diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index f00ee85f..f90bebf4 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -27,7 +27,7 @@ factory = RequestFactory()  class PlainTextParser(BaseParser):      media_type = 'text/plain' -    def parse_stream(self, stream, **opts): +    def parse_stream(self, stream, parser_context=None):          """          Returns a 2-tuple of `(data, files)`. diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index 5eba7fb2..ee7f3a54 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -25,32 +25,6 @@ def media_type_matches(lhs, rhs):      return lhs.match(rhs) -def is_form_media_type(media_type): -    """ -    Return True if the media type is a valid form media type as defined by the HTML4 spec. -    (NB. HTML5 also adds text/plain to the list of valid form media types, but we don't support this here) -    """ -    media_type = _MediaType(media_type) -    return media_type.full_type == 'application/x-www-form-urlencoded' or \ -           media_type.full_type == 'multipart/form-data' - - -def add_media_type_param(media_type, key, val): -    """ -    Add a key, value parameter to a media type string, and return the new media type string. -    """ -    media_type = _MediaType(media_type) -    media_type.params[key] = val -    return str(media_type) - - -def get_media_type_params(media_type): -    """ -    Return a dictionary of the parameters on the given media type. -    """ -    return _MediaType(media_type).params - -  def order_by_precedence(media_type_lst):      """      Returns a list of sets of media type strings, ordered by precedence. diff --git a/rest_framework/views.py b/rest_framework/views.py index b3f36085..92d4445f 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -1,8 +1,5 @@  """ -The :mod:`views` module provides the Views you will most probably -be subclassing in your implementation. - -By setting or modifying class attributes on your view, you change it's predefined behaviour. +Provides an APIView class that is used as the base of all class-based views.  """  import re @@ -159,9 +156,19 @@ class APIView(View):          """          raise exceptions.Throttled(wait) +    def get_parser_context(self, request): +        """ +        Returns a dict that is passed through to Parser.parse_stream(), +        as the `parser_context` keyword argument. +        """ +        return { +            'upload_handlers': request.upload_handlers, +            'meta': request.META, +        } +      def get_renderer_context(self):          """ -        Returns a dict that is passed through to the Renderer.render(), +        Returns a dict that is passed through to Renderer.render(),          as the `renderer_context` keyword argument.          """          # Note: Additionally 'response' will also be set on the context, @@ -253,10 +260,13 @@ class APIView(View):          """          Returns the initial request object.          """ +        parser_context = self.get_parser_context(request) +          return Request(request,                         parsers=self.get_parsers(),                         authenticators=self.get_authenticators(), -                       negotiator=self.get_content_negotiator()) +                       negotiator=self.get_content_negotiator(), +                       parser_context=parser_context)      def initial(self, request, *args, **kwargs):          """  | 
