aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/renderers.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/renderers.py')
-rw-r--r--rest_framework/renderers.py153
1 files changed, 98 insertions, 55 deletions
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index c64fb517..0a34abaa 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -4,14 +4,14 @@ Renderers are used to serialize a response into specific media types.
They give us a generic way of being able to handle various media types
on the response, such as JSON encoded data or HTML output.
-REST framework also provides an HTML renderer the renders the browseable API.
+REST framework also provides an HTML renderer the renders the browsable API.
"""
import copy
import string
+import json
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 django.template import RequestContext, loader, Template
from rest_framework.compat import yaml
from rest_framework.exceptions import ConfigurationError
from rest_framework.settings import api_settings
@@ -19,8 +19,8 @@ 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 import VERSION
-from rest_framework import serializers, parsers
+from rest_framework import VERSION, status
+from rest_framework import parsers
class BaseRenderer(object):
@@ -100,7 +100,7 @@ class JSONPRenderer(JSONRenderer):
callback = self.get_callback(renderer_context)
json = super(JSONPRenderer, self).render(data, accepted_media_type,
renderer_context)
- return "%s(%s);" % (callback, json)
+ return u"%s(%s);" % (callback, json)
class XMLRenderer(BaseRenderer):
@@ -139,18 +139,33 @@ class YAMLRenderer(BaseRenderer):
return yaml.dump(data, stream=None, Dumper=self.encoder)
-class HTMLRenderer(BaseRenderer):
+class TemplateHTMLRenderer(BaseRenderer):
"""
- A Base class provided for convenience.
+ An HTML renderer for use with templates.
- Render the object simply by using the given template.
- To create a template renderer, subclass this class, and set
- the :attr:`media_type` and :attr:`template` attributes.
+ The data supplied to the Response object should be a dictionary that will
+ be used as context for the template.
+
+ The template name is determined by (in order of preference):
+
+ 1. An explicit `.template_name` attribute set on the response.
+ 2. An explicit `.template_name` attribute set on this class.
+ 3. The return result of calling `view.get_template_names()`.
+
+ For example:
+ data = {'users': User.objects.all()}
+ return Response(data, template_name='users.html')
+
+ For pre-rendered HTML, see StaticHTMLRenderer.
"""
media_type = 'text/html'
format = 'html'
template_name = None
+ exception_template_names = [
+ '%(status_code)s.html',
+ 'api_exception.html'
+ ]
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
@@ -167,15 +182,21 @@ class HTMLRenderer(BaseRenderer):
request = renderer_context['request']
response = renderer_context['response']
- template_names = self.get_template_names(response, view)
- template = self.resolve_template(template_names)
- context = self.resolve_context(data, request)
+ if response.exception:
+ template = self.get_exception_template(response)
+ else:
+ template_names = self.get_template_names(response, view)
+ template = self.resolve_template(template_names)
+
+ context = self.resolve_context(data, request, response)
return template.render(context)
def resolve_template(self, template_names):
return loader.select_template(template_names)
- def resolve_context(self, data, request):
+ def resolve_context(self, data, request, response):
+ if response.exception:
+ data['status_code'] = response.status_code
return RequestContext(request, data)
def get_template_names(self, response, view):
@@ -187,6 +208,48 @@ class HTMLRenderer(BaseRenderer):
return view.get_template_names()
raise ConfigurationError('Returned a template response with no template_name')
+ def get_exception_template(self, response):
+ template_names = [name % {'status_code': response.status_code}
+ for name in self.exception_template_names]
+
+ try:
+ # Try to find an appropriate error template
+ return self.resolve_template(template_names)
+ except:
+ # Fall back to using eg '404 Not Found'
+ return Template('%d %s' % (response.status_code,
+ response.status_text.title()))
+
+
+# Note, subclass TemplateHTMLRenderer simply for the exception behavior
+class StaticHTMLRenderer(TemplateHTMLRenderer):
+ """
+ An HTML renderer class that simply returns pre-rendered HTML.
+
+ The data supplied to the Response object should be a string representing
+ the pre-rendered HTML content.
+
+ For example:
+ data = '<html><body>example</body></html>'
+ return Response(data)
+
+ For template rendered HTML, see TemplateHTMLRenderer.
+ """
+ media_type = 'text/html'
+ format = 'html'
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ renderer_context = renderer_context or {}
+ response = renderer_context['response']
+
+ if response and response.exception:
+ request = renderer_context['request']
+ template = self.get_exception_template(response)
+ context = self.resolve_context(data, request, response)
+ return template.render(context)
+
+ return data
+
class BrowsableAPIRenderer(BaseRenderer):
"""
@@ -224,7 +287,7 @@ class BrowsableAPIRenderer(BaseRenderer):
return content
- def show_form_for_method(self, view, method, request):
+ def show_form_for_method(self, view, method, request, obj):
"""
Returns True if a form should be shown for this method.
"""
@@ -236,46 +299,32 @@ class BrowsableAPIRenderer(BaseRenderer):
request = clone_request(request, method)
try:
- if not view.has_permission(request):
+ if not view.has_permission(request, obj):
return # Don't have permission
except:
return # Don't have permission and exception explicitly raise
return True
def serializer_to_form_fields(self, serializer):
- field_mapping = {
- serializers.FloatField: forms.FloatField,
- serializers.IntegerField: forms.IntegerField,
- serializers.DateTimeField: forms.DateTimeField,
- serializers.DateField: forms.DateField,
- serializers.EmailField: forms.EmailField,
- serializers.CharField: forms.CharField,
- serializers.BooleanField: forms.BooleanField,
- serializers.PrimaryKeyRelatedField: forms.ModelChoiceField,
- serializers.ManyPrimaryKeyRelatedField: forms.ModelMultipleChoiceField
- }
-
fields = {}
- for k, v in serializer.get_fields(True).items():
- if getattr(v, 'readonly', True):
+ for k, v in serializer.get_fields().items():
+ if getattr(v, 'read_only', True):
continue
kwargs = {}
kwargs['required'] = v.required
- if getattr(v, 'queryset', None):
- kwargs['queryset'] = v.queryset
+ #if getattr(v, 'queryset', None):
+ # kwargs['queryset'] = v.queryset
+
+ if getattr(v, 'choices', None) is not None:
+ kwargs['choices'] = v.choices
+
+ if getattr(v, 'regex', None) is not None:
+ kwargs['regex'] = v.regex
if getattr(v, 'widget', None):
widget = copy.deepcopy(v.widget)
- # If choices have friendly readable names,
- # then add in the identities too
- if getattr(widget, 'choices', None):
- choices = widget.choices
- if any([ident != desc for (ident, desc) in choices]):
- choices = [(ident, "%s (%s)" % (desc, ident))
- for (ident, desc) in choices]
- widget.choices = choices
kwargs['widget'] = widget
if getattr(v, 'default', None) is not None:
@@ -283,10 +332,7 @@ class BrowsableAPIRenderer(BaseRenderer):
kwargs['label'] = k
- try:
- fields[k] = field_mapping[v.__class__](**kwargs)
- except KeyError:
- fields[k] = forms.CharField(**kwargs)
+ fields[k] = v.form_field_class(**kwargs)
return fields
def get_form(self, view, method, request):
@@ -295,7 +341,8 @@ class BrowsableAPIRenderer(BaseRenderer):
In the absence on of the Resource having an associated form then
provide a form that can be used to submit arbitrary content.
"""
- if not self.show_form_for_method(view, method, request):
+ obj = getattr(view, 'object', None)
+ if not self.show_form_for_method(view, method, request, obj):
return
if method == 'DELETE' or method == 'OPTIONS':
@@ -305,17 +352,13 @@ class BrowsableAPIRenderer(BaseRenderer):
media_types = [parser.media_type for parser in view.parser_classes]
return self.get_generic_content_form(media_types)
- # Creating an on the fly form see: http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
- obj, data = None, None
- if getattr(view, 'object', None):
- obj = view.object
-
serializer = view.get_serializer(instance=obj)
fields = self.serializer_to_form_fields(serializer)
+ # Creating an on the fly form see:
+ # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python
OnTheFlyForm = type("OnTheFlyForm", (forms.Form,), fields)
- if obj:
- data = serializer.data
+ data = (obj is not None) and serializer.data or None
form_instance = OnTheFlyForm(data)
return form_instance
@@ -416,7 +459,7 @@ class BrowsableAPIRenderer(BaseRenderer):
# 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_code == 204:
- response.status_code = 200
+ if response.status_code == status.HTTP_204_NO_CONTENT:
+ response.status_code = status.HTTP_200_OK
return ret