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): """ |
