aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/authentication.py40
-rw-r--r--rest_framework/parsers.py21
-rw-r--r--rest_framework/permissions.py15
-rw-r--r--rest_framework/renderers.py4
-rw-r--r--rest_framework/request.py29
-rw-r--r--rest_framework/resources.py1
-rw-r--r--rest_framework/settings.py2
-rw-r--r--rest_framework/tests/request.py2
-rw-r--r--rest_framework/utils/mediatypes.py26
-rw-r--r--rest_framework/views.py22
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):
"""