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/emitters.py | |
| parent | 659898ffaf24f74b62e73c487cd81bad21904790 (diff) | |
| parent | b508ca38d44f458e3eabaa4ffd3500d80a71eb9e (diff) | |
| download | django-rest-framework-da60f68f50dbcc177cb8b31df428d2daa905e9c6.tar.bz2 | |
Merge previous checkins
Diffstat (limited to 'djangorestframework/emitters.py')
| -rw-r--r-- | djangorestframework/emitters.py | 131 |
1 files changed, 131 insertions, 0 deletions
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 |
