diff options
Diffstat (limited to 'djangorestframework/emitters.py')
| -rw-r--r-- | djangorestframework/emitters.py | 147 |
1 files changed, 11 insertions, 136 deletions
diff --git a/djangorestframework/emitters.py b/djangorestframework/emitters.py index 36feea05..2990d313 100644 --- a/djangorestframework/emitters.py +++ b/djangorestframework/emitters.py @@ -4,11 +4,10 @@ by serializing the output along with documentation regarding the Resource, outpu and providing forms and links depending on the allowed methods, emitters and parsers on the Resource. """ from django.conf import settings -from django.http import HttpResponse from django.template import RequestContext, loader from django import forms -from djangorestframework.response import NoContent, ResponseException +from djangorestframework.response import ErrorResponse from djangorestframework.utils import dict2xml, url_resolves from djangorestframework.markdownwrapper import apply_markdown from djangorestframework.breadcrumbs import get_breadcrumbs @@ -18,7 +17,6 @@ from djangorestframework import status from urllib import quote_plus import string import re -from decimal import Decimal try: import json @@ -26,132 +24,9 @@ except ImportError: import simplejson as json -_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] - - # TODO: Rename verbose to something more appropriate -# TODO: NoContent could be handled more cleanly. It'd be nice if it was handled by default, +# TODO: Maybe None could be handled more cleanly. It'd be nice if it was handled by default, # and only have an emitter output anything if it explicitly provides support for that. class BaseEmitter(object): @@ -162,10 +37,10 @@ class BaseEmitter(object): def __init__(self, resource): self.resource = resource - def emit(self, output=NoContent, verbose=False): + def emit(self, output=None, verbose=False): """By default emit simply returns the ouput as-is. Override this method to provide for other behaviour.""" - if output is NoContent: + if output is None: return '' return output @@ -177,8 +52,8 @@ class TemplateEmitter(BaseEmitter): media_type = None template = None - def emit(self, output=NoContent, verbose=False): - if output is NoContent: + def emit(self, output=None, verbose=False): + if output is None: return '' context = RequestContext(self.request, output) @@ -276,7 +151,7 @@ class DocumentingTemplateEmitter(BaseEmitter): return GenericContentForm(resource) - def emit(self, output=NoContent): + def emit(self, output=None): content = self._get_content(self.resource, self.resource.request, output) form_instance = self._get_form_instance(self.resource) @@ -324,8 +199,8 @@ class JSONEmitter(BaseEmitter): """Emitter which serializes to JSON""" media_type = 'application/json' - def emit(self, output=NoContent, verbose=False): - if output is NoContent: + def emit(self, output=None, verbose=False): + if output is None: return '' if verbose: return json.dumps(output, indent=4, sort_keys=True) @@ -336,8 +211,8 @@ class XMLEmitter(BaseEmitter): """Emitter which serializes to XML.""" media_type = 'application/xml' - def emit(self, output=NoContent, verbose=False): - if output is NoContent: + def emit(self, output=None, verbose=False): + if output is None: return '' return dict2xml(output) |
