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.py196
1 files changed, 157 insertions, 39 deletions
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index 3bf03e62..e87d16d0 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -13,17 +13,20 @@ import django
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.http.multipartparser import parse_header
-from django.template import RequestContext, loader, Template
+from django.template import Context, RequestContext, loader, Template
from django.test.client import encode_multipart
from django.utils import six
from django.utils.xmlutils import SimplerXMLGenerator
-from rest_framework.compat import StringIO, smart_text, yaml
+from rest_framework import exceptions, serializers, status, VERSION
+from rest_framework.compat import (
+ SHORT_SEPARATORS, LONG_SEPARATORS, StringIO, smart_text, yaml
+)
from rest_framework.exceptions import ParseError
from rest_framework.settings import api_settings
from rest_framework.request import is_form_media_type, override_method
from rest_framework.utils import encoders
from rest_framework.utils.breadcrumbs import get_breadcrumbs
-from rest_framework import exceptions, status, VERSION
+from rest_framework.utils.field_mapping import ClassLookupDict
def zero_as_none(value):
@@ -86,7 +89,7 @@ class JSONRenderer(BaseRenderer):
renderer_context = renderer_context or {}
indent = self.get_indent(accepted_media_type, renderer_context)
- separators = (',', ':') if (indent is None and self.compact) else (', ', ': ')
+ separators = SHORT_SEPARATORS if (indent is None and self.compact) else LONG_SEPARATORS
ret = json.dumps(
data, cls=self.encoder_class,
@@ -120,7 +123,7 @@ class JSONPRenderer(JSONRenderer):
Determine the name of the callback to wrap around the json output.
"""
request = renderer_context.get('request', None)
- params = request and request.QUERY_PARAMS or {}
+ params = request and request.query_params or {}
return params.get(self.callback_parameter, self.default_callback)
def render(self, data, accepted_media_type=None, renderer_context=None):
@@ -338,18 +341,109 @@ class HTMLFormRenderer(BaseRenderer):
"""
media_type = 'text/html'
format = 'form'
- template = 'rest_framework/form.html'
charset = 'utf-8'
+ template_pack = 'rest_framework/horizontal/'
+ base_template = 'form.html'
+
+ default_style = ClassLookupDict({
+ serializers.Field: {
+ 'base_template': 'input.html',
+ 'input_type': 'text'
+ },
+ serializers.EmailField: {
+ 'base_template': 'input.html',
+ 'input_type': 'email'
+ },
+ serializers.URLField: {
+ 'base_template': 'input.html',
+ 'input_type': 'url'
+ },
+ serializers.IntegerField: {
+ 'base_template': 'input.html',
+ 'input_type': 'number'
+ },
+ serializers.DateTimeField: {
+ 'base_template': 'input.html',
+ 'input_type': 'datetime-local'
+ },
+ serializers.DateField: {
+ 'base_template': 'input.html',
+ 'input_type': 'date'
+ },
+ serializers.TimeField: {
+ 'base_template': 'input.html',
+ 'input_type': 'time'
+ },
+ serializers.BooleanField: {
+ 'base_template': 'checkbox.html'
+ },
+ serializers.ChoiceField: {
+ 'base_template': 'select.html', # Also valid: 'radio.html'
+ },
+ serializers.MultipleChoiceField: {
+ 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
+ },
+ serializers.RelatedField: {
+ 'base_template': 'select.html', # Also valid: 'radio.html'
+ },
+ serializers.ManyRelatedField: {
+ 'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
+ },
+ serializers.Serializer: {
+ 'base_template': 'fieldset.html'
+ },
+ serializers.ListSerializer: {
+ 'base_template': 'list_fieldset.html'
+ }
+ })
+
+ def render_field(self, field, parent_style):
+ style = dict(self.default_style[field])
+ style.update(field.style)
+ if 'template_pack' not in style:
+ style['template_pack'] = parent_style.get('template_pack', self.template_pack)
+ style['renderer'] = self
+
+ if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
+ field.value = field.value.rstrip('Z')
+
+ if 'template' in style:
+ template_name = style['template']
+ else:
+ template_name = style['template_pack'].strip('/') + '/' + style['base_template']
+
+ template = loader.get_template(template_name)
+ context = Context({'field': field, 'style': style})
+ return template.render(context)
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render serializer data and return an HTML form, as a string.
"""
+ form = data.serializer
+ meta = getattr(form, 'Meta', None)
+ style = getattr(meta, 'style', {})
+ if 'template_pack' not in style:
+ style['template_pack'] = self.template_pack
+ if 'base_template' not in style:
+ style['base_template'] = self.base_template
+ style['renderer'] = self
+
+ # This API needs to be finessed and finalized for 3.1
+ if 'template' in renderer_context:
+ template_name = renderer_context['template']
+ elif 'template' in style:
+ template_name = style['template']
+ else:
+ template_name = style['template_pack'].strip('/') + '/' + style['base_template']
+
renderer_context = renderer_context or {}
request = renderer_context['request']
-
- template = loader.get_template(self.template)
- context = RequestContext(request, {'form': data})
+ template = loader.get_template(template_name)
+ context = RequestContext(request, {
+ 'form': form,
+ 'style': style
+ })
return template.render(context)
@@ -417,27 +511,35 @@ class BrowsableAPIRenderer(BaseRenderer):
return False # Doesn't have permissions
return True
- def get_rendered_html_form(self, view, method, request):
+ def get_rendered_html_form(self, data, view, method, request):
"""
Return a string representing a rendered HTML form, possibly bound to
either the input or output data.
In the absence of the View having an associated form then return None.
"""
- if request.method == method:
+ # See issue #2089 for refactoring this.
+ serializer = getattr(data, 'serializer', None)
+ if serializer and not getattr(serializer, 'many', False):
+ instance = getattr(serializer, 'instance', None)
+ else:
+ instance = None
+
+ # If this is valid serializer data, and the form is for the same
+ # HTTP method as was used in the request then use the existing
+ # serializer instance, rather than dynamically creating a new one.
+ if request.method == method and serializer is not None:
try:
- data = request.DATA
- # files = request.FILES
+ data = request.data
except ParseError:
data = None
- # files = None
+ existing_serializer = serializer
else:
data = None
- # files = None
+ existing_serializer = None
with override_method(view, request, method) as request:
- obj = getattr(view, 'object', None)
- if not self.show_form_for_method(view, method, request, obj):
+ if not self.show_form_for_method(view, method, request, instance):
return
if method in ('DELETE', 'OPTIONS'):
@@ -449,19 +551,38 @@ class BrowsableAPIRenderer(BaseRenderer):
):
return
- serializer = view.get_serializer(instance=obj, data=data)
- serializer.is_valid()
- data = serializer.data
-
+ if existing_serializer is not None:
+ serializer = existing_serializer
+ else:
+ if method in ('PUT', 'PATCH'):
+ serializer = view.get_serializer(instance=instance, data=data)
+ else:
+ serializer = view.get_serializer(data=data)
+ if data is not None:
+ serializer.is_valid()
form_renderer = self.form_renderer_class()
- return form_renderer.render(data, self.accepted_media_type, self.renderer_context)
-
- def get_raw_data_form(self, view, method, request):
+ return form_renderer.render(
+ serializer.data,
+ self.accepted_media_type,
+ dict(
+ list(self.renderer_context.items()) +
+ [('template', 'rest_framework/api_form.html')]
+ )
+ )
+
+ def get_raw_data_form(self, data, view, method, request):
"""
Returns a form that allows for arbitrary content types to be tunneled
via standard HTML forms.
(Which are typically application/x-www-form-urlencoded)
"""
+ # See issue #2089 for refactoring this.
+ serializer = getattr(data, 'serializer', None)
+ if serializer and not getattr(serializer, 'many', False):
+ instance = getattr(serializer, 'instance', None)
+ else:
+ instance = None
+
with override_method(view, request, method) as request:
# If we're not using content overloading there's no point in
# supplying a generic form, as the view won't treat the form's
@@ -471,8 +592,7 @@ class BrowsableAPIRenderer(BaseRenderer):
return None
# Check permissions
- obj = getattr(view, 'object', None)
- if not self.show_form_for_method(view, method, request, obj):
+ if not self.show_form_for_method(view, method, request, instance):
return
# If possible, serialize the initial content for the generic form
@@ -482,12 +602,10 @@ class BrowsableAPIRenderer(BaseRenderer):
# View has a serializer defined and parser class has a
# corresponding renderer that can be used to render the data.
- # Get a read-only version of the serializer
- serializer = view.get_serializer(instance=obj)
- if obj is None:
- for name, field in serializer.fields.items():
- if getattr(field, 'read_only', None):
- del serializer.fields[name]
+ if method in ('PUT', 'PATCH'):
+ serializer = view.get_serializer(instance=instance)
+ else:
+ serializer = view.get_serializer()
# Render the raw data content
renderer = renderer_class()
@@ -544,9 +662,9 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer = self.get_default_renderer(view)
- raw_data_post_form = self.get_raw_data_form(view, 'POST', request)
- raw_data_put_form = self.get_raw_data_form(view, 'PUT', request)
- raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request)
+ raw_data_post_form = self.get_raw_data_form(data, view, 'POST', request)
+ raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
+ raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request)
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
response_headers = dict(response.items())
@@ -570,10 +688,10 @@ class BrowsableAPIRenderer(BaseRenderer):
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
'response_headers': response_headers,
- # 'put_form': self.get_rendered_html_form(view, 'PUT', request),
- # 'post_form': self.get_rendered_html_form(view, 'POST', request),
- # 'delete_form': self.get_rendered_html_form(view, 'DELETE', request),
- # 'options_form': self.get_rendered_html_form(view, 'OPTIONS', request),
+ 'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
+ 'post_form': self.get_rendered_html_form(data, view, 'POST', request),
+ 'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request),
+ 'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request),
'raw_data_put_form': raw_data_put_form,
'raw_data_post_form': raw_data_post_form,