diff options
| author | Tom Christie | 2011-04-27 18:20:29 +0100 |
|---|---|---|
| committer | Tom Christie | 2011-04-27 18:20:29 +0100 |
| commit | da60f68f50dbcc177cb8b31df428d2daa905e9c6 (patch) | |
| tree | 6393597cdf4b6b0cc84f5ce792d3770d011aa189 /djangorestframework | |
| parent | 659898ffaf24f74b62e73c487cd81bad21904790 (diff) | |
| parent | b508ca38d44f458e3eabaa4ffd3500d80a71eb9e (diff) | |
| download | django-rest-framework-da60f68f50dbcc177cb8b31df428d2daa905e9c6.tar.bz2 | |
Merge previous checkins
Diffstat (limited to 'djangorestframework')
| -rw-r--r-- | djangorestframework/authenticators.py | 18 | ||||
| -rw-r--r-- | djangorestframework/emitters.py | 131 | ||||
| -rw-r--r-- | djangorestframework/parsers.py | 47 | ||||
| -rw-r--r-- | djangorestframework/tests/authentication.py | 4 | ||||
| -rw-r--r-- | djangorestframework/tests/reverse.py | 3 |
5 files changed, 195 insertions, 8 deletions
diff --git a/djangorestframework/authenticators.py b/djangorestframework/authenticators.py index 19181b7d..29fbb818 100644 --- a/djangorestframework/authenticators.py +++ b/djangorestframework/authenticators.py @@ -64,14 +64,16 @@ class UserLoggedInAuthenticator(BaseAuthenticator): """Use Django's built-in request session for authentication.""" def authenticate(self, request): if getattr(request, 'user', None) and request.user.is_active: - # Temporarily set request.POST to view.RAW_CONTENT, - # so that we use our more generic request parsing, - # in preference to Django's form-only request parsing. - request._post = self.view.RAW_CONTENT - resp = CsrfViewMiddleware().process_view(request, None, (), {}) - del(request._post) - if resp is None: # csrf passed - return request.user + # If this is a POST request we enforce CSRF validation. + if request.method.upper() == 'POST': + # Temporarily replace request.POST with .RAW_CONTENT, + # so that we use our more generic request parsing + request._post = self.mixin.RAW_CONTENT + resp = CsrfViewMiddleware().process_view(request, None, (), {}) + del(request._post) + if resp is not None: # csrf failed + return None + return request.user return None diff --git a/djangorestframework/emitters.py b/djangorestframework/emitters.py index 39046919..0adddca9 100644 --- a/djangorestframework/emitters.py +++ b/djangorestframework/emitters.py @@ -3,10 +3,14 @@ django-rest-framework also provides HTML and PlainText emitters that help self-d by serializing the output along with documentation regarding the Resource, output status and headers, and providing forms and links depending on the allowed methods, emitters and parsers on the Resource. """ +from django import forms from django.conf import settings from django.template import RequestContext, loader from django.utils import simplejson as json +<<<<<<< local from django import forms +======= +>>>>>>> other from djangorestframework.response import ErrorResponse from djangorestframework.utils import dict2xml, url_resolves @@ -18,6 +22,133 @@ from djangorestframework import status from urllib import quote_plus import string import re +<<<<<<< local +======= +from decimal import Decimal + + +_MSIE_USER_AGENT = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )') + + +class EmitterMixin(object): + """Adds behaviour for pluggable Emitters to a :class:`.Resource` or Django :class:`View`. class. + + Default behaviour is to use standard HTTP Accept header content negotiation. + Also supports overidding the content type by specifying an _accept= parameter in the URL. + Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.""" + + ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params + REWRITE_IE_ACCEPT_HEADER = True + + request = None + response = None + emitters = () + + def emit(self, response): + """Takes a :class:`Response` object and returns a Django :class:`HttpResponse`.""" + self.response = response + + try: + emitter = self._determine_emitter(self.request) + except ResponseException, exc: + emitter = self.default_emitter + response = exc.response + + # Serialize the response content + if response.has_content_body: + content = emitter(self).emit(output=response.cleaned_content) + else: + content = emitter(self).emit() + + # Munge DELETE Response code to allow us to return content + # (Do this *after* we've rendered the template so that we include the normal deletion response code in the output) + if response.status == 204: + response.status = 200 + + # Build the HTTP Response + # TODO: Check if emitter.mimetype is underspecified, or if a content-type header has been set + resp = HttpResponse(content, mimetype=emitter.media_type, status=response.status) + for (key, val) in response.headers.items(): + resp[key] = val + + return resp + + + def _determine_emitter(self, request): + """Return the appropriate emitter for the output, given the client's 'Accept' header, + and the content types that this Resource knows how to serve. + + See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html""" + + if self.ACCEPT_QUERY_PARAM and request.GET.get(self.ACCEPT_QUERY_PARAM, None): + # Use _accept parameter override + accept_list = [request.GET.get(self.ACCEPT_QUERY_PARAM)] + elif self.REWRITE_IE_ACCEPT_HEADER and request.META.has_key('HTTP_USER_AGENT') and _MSIE_USER_AGENT.match(request.META['HTTP_USER_AGENT']): + accept_list = ['text/html', '*/*'] + elif request.META.has_key('HTTP_ACCEPT'): + # Use standard HTTP Accept negotiation + accept_list = request.META["HTTP_ACCEPT"].split(',') + else: + # No accept header specified + return self.default_emitter + + # Parse the accept header into a dict of {qvalue: set of media types} + # We ignore mietype parameters + accept_dict = {} + for token in accept_list: + components = token.split(';') + mimetype = components[0].strip() + qvalue = Decimal('1.0') + + if len(components) > 1: + # Parse items that have a qvalue eg text/html;q=0.9 + try: + (q, num) = components[-1].split('=') + if q == 'q': + qvalue = Decimal(num) + except: + # Skip malformed entries + continue + + if accept_dict.has_key(qvalue): + accept_dict[qvalue].add(mimetype) + else: + accept_dict[qvalue] = set((mimetype,)) + + # Convert to a list of sets ordered by qvalue (highest first) + accept_sets = [accept_dict[qvalue] for qvalue in sorted(accept_dict.keys(), reverse=True)] + + for accept_set in accept_sets: + # Return any exact match + for emitter in self.emitters: + if emitter.media_type in accept_set: + return emitter + + # Return any subtype match + for emitter in self.emitters: + if emitter.media_type.split('/')[0] + '/*' in accept_set: + return emitter + + # Return default + if '*/*' in accept_set: + return self.default_emitter + + + raise ResponseException(status.HTTP_406_NOT_ACCEPTABLE, + {'detail': 'Could not statisfy the client\'s Accept header', + 'available_types': self.emitted_media_types}) + + @property + def emitted_media_types(self): + """Return an list of all the media types that this resource can emit.""" + return [emitter.media_type for emitter in self.emitters] + + @property + def default_emitter(self): + """Return the resource's most prefered emitter. + (This emitter is used if the client does not send and Accept: header, or sends Accept: */*)""" + return self.emitters[0] +>>>>>>> other diff --git a/djangorestframework/parsers.py b/djangorestframework/parsers.py index caa76277..03f8bf8f 100644 --- a/djangorestframework/parsers.py +++ b/djangorestframework/parsers.py @@ -11,12 +11,59 @@ We need a method to be able to: from django.http.multipartparser import MultiPartParser as DjangoMPParser from django.utils import simplejson as json +<<<<<<< local from djangorestframework.response import ErrorResponse +======= +from djangorestframework.response import ResponseException +>>>>>>> other from djangorestframework import status from djangorestframework.utils import as_tuple from djangorestframework.mediatypes import MediaType from djangorestframework.compat import parse_qs +<<<<<<< local +======= +try: + from urlparse import parse_qs +except ImportError: + from cgi import parse_qs + +class ParserMixin(object): + parsers = () + + def parse(self, stream, content_type): + """ + Parse the request content. + + May raise a 415 ResponseException (Unsupported Media Type), + or a 400 ResponseException (Bad Request). + """ + parsers = as_tuple(self.parsers) + + parser = None + for parser_cls in parsers: + if parser_cls.handles(content_type): + parser = parser_cls(self) + break + + if parser is None: + raise ResponseException(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE, + {'error': 'Unsupported media type in request \'%s\'.' % + content_type.media_type}) + + return parser.parse(stream) + + @property + def parsed_media_types(self): + """Return an list of all the media types that this ParserMixin can parse.""" + return [parser.media_type for parser in self.parsers] + + @property + def default_parser(self): + """Return the ParerMixin's most prefered emitter. + (This has no behavioural effect, but is may be used by documenting emitters)""" + return self.parsers[0] +>>>>>>> other class BaseParser(object): diff --git a/djangorestframework/tests/authentication.py b/djangorestframework/tests/authentication.py index f2c249a6..a43a87b3 100644 --- a/djangorestframework/tests/authentication.py +++ b/djangorestframework/tests/authentication.py @@ -1,8 +1,12 @@ from django.conf.urls.defaults import patterns +<<<<<<< local from django.test import TestCase from django.test import Client from django.contrib.auth.models import User from django.contrib.auth import login +======= +from django.test import Client, TestCase +>>>>>>> other from django.utils import simplejson as json from djangorestframework.compat import RequestFactory diff --git a/djangorestframework/tests/reverse.py b/djangorestframework/tests/reverse.py index 28fee63b..63e2080a 100644 --- a/djangorestframework/tests/reverse.py +++ b/djangorestframework/tests/reverse.py @@ -5,7 +5,10 @@ from django.utils import simplejson as json from djangorestframework.resource import Resource +<<<<<<< local +======= +>>>>>>> other class MockResource(Resource): """Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified""" |
