diff options
Diffstat (limited to 'rest_framework/renderers.py')
| -rw-r--r-- | rest_framework/renderers.py | 668 |
1 files changed, 371 insertions, 297 deletions
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3a03ca33..920d2bc4 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -8,24 +8,27 @@ REST framework also provides an HTML renderer the renders the browsable API. """ from __future__ import unicode_literals -import copy import json +import django from django import forms from django.core.exceptions import ImproperlyConfigured +from django.core.paginator import Page 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.xmlutils import SimplerXMLGenerator -from rest_framework.compat import StringIO -from rest_framework.compat import six -from rest_framework.compat import smart_text -from rest_framework.compat import yaml +from django.utils import six +from rest_framework import exceptions, serializers, status, VERSION +from rest_framework.compat import SHORT_SEPARATORS, LONG_SEPARATORS, INDENT_SEPARATORS +from rest_framework.exceptions import ParseError from rest_framework.settings import api_settings -from rest_framework.request import clone_request +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.utils.formatting import get_view_name, get_view_description -from rest_framework import exceptions, parsers, status, VERSION +from rest_framework.utils.field_mapping import ClassLookupDict + + +def zero_as_none(value): + return None if value == 0 else value class BaseRenderer(object): @@ -37,170 +40,77 @@ class BaseRenderer(object): media_type = None format = None charset = 'utf-8' + render_style = 'text' def render(self, data, accepted_media_type=None, renderer_context=None): - raise NotImplemented('Renderer class requires .render() to be implemented') + raise NotImplementedError('Renderer class requires .render() to be implemented') class JSONRenderer(BaseRenderer): """ Renderer which serializes to JSON. - Applies JSON's backslash-u character escaping for non-ascii characters. """ media_type = 'application/json' format = 'json' encoder_class = encoders.JSONEncoder - ensure_ascii = True - charset = 'utf-8' - # Note that JSON encodings must be utf-8, utf-16 or utf-32. - # See: http://www.ietf.org/rfc/rfc4627.txt - - def render(self, data, accepted_media_type=None, renderer_context=None): - """ - Render `data` into JSON. - """ - if data is None: - return '' + ensure_ascii = not api_settings.UNICODE_JSON + compact = api_settings.COMPACT_JSON - # If 'indent' is provided in the context, then pretty print the result. - # E.g. If we're being called by the BrowsableAPIRenderer. - renderer_context = renderer_context or {} - indent = renderer_context.get('indent', None) + # We don't set a charset because JSON is a binary encoding, + # that can be encoded as utf-8, utf-16 or utf-32. + # See: http://www.ietf.org/rfc/rfc4627.txt + # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/ + charset = None + def get_indent(self, accepted_media_type, renderer_context): if accepted_media_type: # If the media type looks like 'application/json; indent=4', # then pretty print the result. + # Note that we coerce `indent=0` into `indent=None`. base_media_type, params = parse_header(accepted_media_type.encode('ascii')) - indent = params.get('indent', indent) try: - indent = max(min(int(indent), 8), 0) - except (ValueError, TypeError): - indent = None - - ret = json.dumps(data, cls=self.encoder_class, - indent=indent, ensure_ascii=self.ensure_ascii) - - # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, - # but if ensure_ascii=False, the return type is underspecified, - # and may (or may not) be unicode. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(ret, six.text_type): - return bytes(ret.encode(self.charset)) - return ret - - -class UnicodeJSONRenderer(JSONRenderer): - ensure_ascii = False - charset = 'utf-8' - """ - Renderer which serializes to JSON. - Does *not* apply JSON's character escaping for non-ascii characters. - """ - - -class JSONPRenderer(JSONRenderer): - """ - Renderer which serializes to json, - wrapping the json output in a callback function. - """ - - media_type = 'application/javascript' - format = 'jsonp' - callback_parameter = 'callback' - default_callback = 'callback' - - def get_callback(self, renderer_context): - """ - 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 {} - return params.get(self.callback_parameter, self.default_callback) - - def render(self, data, accepted_media_type=None, renderer_context=None): - """ - Renders into jsonp, wrapping the json output in a callback function. - - Clients may set the callback function name using a query parameter - on the URL, for example: ?callback=exampleCallbackName - """ - renderer_context = renderer_context or {} - callback = self.get_callback(renderer_context) - json = super(JSONPRenderer, self).render(data, accepted_media_type, - renderer_context) - return callback.encode(self.charset) + b'(' + json + b');' - + return zero_as_none(max(min(int(params['indent']), 8), 0)) + except (KeyError, ValueError, TypeError): + pass -class XMLRenderer(BaseRenderer): - """ - Renderer which serializes to XML. - """ - - media_type = 'application/xml' - format = 'xml' - charset = 'utf-8' + # If 'indent' is provided in the context, then pretty print the result. + # E.g. If we're being called by the BrowsableAPIRenderer. + return renderer_context.get('indent', None) def render(self, data, accepted_media_type=None, renderer_context=None): """ - Renders *obj* into serialized XML. + Render `data` into JSON, returning a bytestring. """ if data is None: - return '' - - stream = StringIO() - - xml = SimplerXMLGenerator(stream, self.charset) - xml.startDocument() - xml.startElement("root", {}) - - self._to_xml(xml, data) + return bytes() - xml.endElement("root") - xml.endDocument() - return stream.getvalue() - - def _to_xml(self, xml, data): - if isinstance(data, (list, tuple)): - for item in data: - xml.startElement("list-item", {}) - self._to_xml(xml, item) - xml.endElement("list-item") - - elif isinstance(data, dict): - for key, value in six.iteritems(data): - xml.startElement(key, {}) - self._to_xml(xml, value) - xml.endElement(key) - - elif data is None: - # Don't output any value - pass + renderer_context = renderer_context or {} + indent = self.get_indent(accepted_media_type, renderer_context) + if indent is None: + separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS else: - xml.characters(smart_text(data)) + separators = INDENT_SEPARATORS + ret = json.dumps( + data, cls=self.encoder_class, + indent=indent, ensure_ascii=self.ensure_ascii, + separators=separators + ) -class YAMLRenderer(BaseRenderer): - """ - Renderer which serializes to YAML. - """ - - media_type = 'application/yaml' - format = 'yaml' - encoder = encoders.SafeDumper - charset = 'utf-8' - - def render(self, data, accepted_media_type=None, renderer_context=None): - """ - Renders *obj* into serialized YAML. - """ - assert yaml, 'YAMLRenderer requires pyyaml to be installed' - - if data is None: - return '' - - return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) + # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, + # but if ensure_ascii=False, the return type is underspecified, + # and may (or may not) be unicode. + # On python 3.x json.dumps() returns unicode strings. + if isinstance(ret, six.text_type): + # We always fully escape \u2028 and \u2029 to ensure we output JSON + # that is a strict javascript subset. If bytes were returned + # by json.dumps() then we don't have these characters in any case. + # See: http://timelessrepo.com/json-isnt-a-javascript-subset + ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') + return bytes(ret.encode('utf-8')) + return ret class TemplateHTMLRenderer(BaseRenderer): @@ -271,7 +181,11 @@ class TemplateHTMLRenderer(BaseRenderer): return [self.template_name] elif hasattr(view, 'get_template_names'): return view.get_template_names() - raise ImproperlyConfigured('Returned a template response with no template_name') + elif hasattr(view, 'template_name'): + return [view.template_name] + raise ImproperlyConfigured( + 'Returned a template response with no `template_name` attribute set on either the view or response' + ) def get_exception_template(self, response): template_names = [name % {'status_code': response.status_code} @@ -317,6 +231,132 @@ class StaticHTMLRenderer(TemplateHTMLRenderer): return data +class HTMLFormRenderer(BaseRenderer): + """ + Renderers serializer data into an HTML form. + + If the serializer was instantiated without an object then this will + return an HTML form not bound to any object, + otherwise it will return an HTML form with the appropriate initial data + populated from the object. + + Note that rendering of field and form errors is not currently supported. + """ + media_type = 'text/html' + format = 'form' + 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.FileField: { + 'base_template': 'input.html', + 'input_type': 'file' + }, + 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): + if isinstance(field._field, serializers.HiddenField): + return '' + + 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(template_name) + context = RequestContext(request, { + 'form': form, + 'style': style + }) + return template.render(context) + + class BrowsableAPIRenderer(BaseRenderer): """ HTML renderer used to self-document the API. @@ -325,6 +365,7 @@ class BrowsableAPIRenderer(BaseRenderer): format = 'api' template = 'rest_framework/api.html' charset = 'utf-8' + form_renderer_class = HTMLFormRenderer def get_default_renderer(self, view): """ @@ -333,8 +374,13 @@ class BrowsableAPIRenderer(BaseRenderer): """ renderers = [renderer for renderer in view.renderer_classes if not issubclass(renderer, BrowsableAPIRenderer)] + non_template_renderers = [renderer for renderer in renderers + if not hasattr(renderer, 'get_template_names')] + if not renderers: return None + elif non_template_renderers: + return non_template_renderers[0]() return renderers[0]() def get_content(self, renderer, data, @@ -349,7 +395,10 @@ class BrowsableAPIRenderer(BaseRenderer): renderer_context['indent'] = 4 content = renderer.render(data, accepted_media_type, renderer_context) - if renderer.charset is None: + render_style = getattr(renderer, 'render_style', 'text') + assert render_style in ['text', 'binary'], 'Expected .render_style ' \ + '"text" or "binary", but got "%s"' % render_style + if render_style == 'binary': return '[%d bytes of binary content]' % len(content) return content @@ -358,7 +407,7 @@ class BrowsableAPIRenderer(BaseRenderer): """ Returns True if a form should be shown for this method. """ - if not method in view.allowed_methods: + if method not in view.allowed_methods: return # Not a valid method if not api_settings.FORM_METHOD_OVERRIDE: @@ -372,202 +421,227 @@ class BrowsableAPIRenderer(BaseRenderer): return False # Doesn't have permissions return True - def serializer_to_form_fields(self, serializer): - fields = {} - 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, '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) - kwargs['widget'] = widget - - if getattr(v, 'default', None) is not None: - kwargs['initial'] = v.default - - if getattr(v, 'label', None) is not None: - kwargs['label'] = v.label - - if getattr(v, 'help_text', None) is not None: - kwargs['help_text'] = v.help_text - - fields[k] = v.form_field_class(**kwargs) - - return fields - - def _get_form(self, view, method, request): - # We need to impersonate a request with the correct method, - # so that eg. any dynamic get_serializer_class methods return the - # correct form for each method. - restore = view.request - request = clone_request(request, method) - view.request = request - try: - return self.get_form(view, method, request) - finally: - view.request = restore - - def _get_raw_data_form(self, view, method, request, media_types): - # We need to impersonate a request with the correct method, - # so that eg. any dynamic get_serializer_class methods return the - # correct form for each method. - restore = view.request - request = clone_request(request, method) - view.request = request - try: - return self.get_raw_data_form(view, method, request, media_types) - finally: - view.request = restore - - def get_form(self, view, method, request): + def get_rendered_html_form(self, data, view, method, request): """ - Get a form, possibly bound to either the input or output data. - In the absence on of the Resource having an associated form then - provide a form that can be used to submit arbitrary content. - """ - obj = getattr(view, 'object', None) - if not self.show_form_for_method(view, method, request, obj): - return - - if method in ('DELETE', 'OPTIONS'): - return True # Don't actually need to return a form + Return a string representing a rendered HTML form, possibly bound to + either the input or output data. - if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: - return - - serializer = view.get_serializer(instance=obj) - fields = self.serializer_to_form_fields(serializer) + In the absence of the View having an associated form then return None. + """ + # See issue #2089 for refactoring this. + serializer = getattr(data, 'serializer', None) + if serializer and not getattr(serializer, 'many', False): + instance = getattr(serializer, 'instance', None) + if isinstance(instance, Page): + instance = None + else: + instance = None - # Creating an on the fly form see: - # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python - OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) - data = (obj is not None) and serializer.data or None - form_instance = OnTheFlyForm(data) - return form_instance + # 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: + kwargs = {'data': request.data} + except ParseError: + kwargs = {} + existing_serializer = serializer + else: + kwargs = {} + existing_serializer = None + + with override_method(view, request, method) as request: + if not self.show_form_for_method(view, method, request, instance): + return + + if method in ('DELETE', 'OPTIONS'): + return True # Don't actually need to return a form + + if ( + not getattr(view, 'get_serializer', None) or + not any(is_form_media_type(parser.media_type) for parser in view.parser_classes) + ): + return + + if existing_serializer is not None: + serializer = existing_serializer + else: + if method in ('PUT', 'PATCH'): + serializer = view.get_serializer(instance=instance, **kwargs) + else: + serializer = view.get_serializer(**kwargs) + + if hasattr(serializer, 'initial_data'): + serializer.is_valid() + + form_renderer = self.form_renderer_class() + 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, view, method, request, media_types): + 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) """ - - # 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 value as the content of the request. - if not (api_settings.FORM_CONTENT_OVERRIDE - and api_settings.FORM_CONTENTTYPE_OVERRIDE): - return None - - # Check permissions - obj = getattr(view, 'object', None) - if not self.show_form_for_method(view, method, request, obj): - return - - content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE - content_field = api_settings.FORM_CONTENT_OVERRIDE - choices = [(media_type, media_type) for media_type in media_types] - initial = media_types[0] - - # NB. http://jacobian.org/writing/dynamic-form-generation/ - class GenericContentForm(forms.Form): - def __init__(self): - super(GenericContentForm, self).__init__() - - self.fields[content_type_field] = forms.ChoiceField( - label='Media type', - choices=choices, - initial=initial - ) - self.fields[content_field] = forms.CharField( - label='Content', - widget=forms.Textarea - ) - - return GenericContentForm() + # See issue #2089 for refactoring this. + serializer = getattr(data, 'serializer', None) + if serializer and not getattr(serializer, 'many', False): + instance = getattr(serializer, 'instance', None) + if isinstance(instance, Page): + 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 + # value as the content of the request. + if not (api_settings.FORM_CONTENT_OVERRIDE and + api_settings.FORM_CONTENTTYPE_OVERRIDE): + return None + + # Check permissions + if not self.show_form_for_method(view, method, request, instance): + return + + # If possible, serialize the initial content for the generic form + default_parser = view.parser_classes[0] + renderer_class = getattr(default_parser, 'renderer_class', None) + if (hasattr(view, 'get_serializer') and renderer_class): + # View has a serializer defined and parser class has a + # corresponding renderer that can be used to render the data. + + if method in ('PUT', 'PATCH'): + serializer = view.get_serializer(instance=instance) + else: + serializer = view.get_serializer() + + # Render the raw data content + renderer = renderer_class() + accepted = self.accepted_media_type + context = self.renderer_context.copy() + context['indent'] = 4 + content = renderer.render(serializer.data, accepted, context) + else: + content = None + + # Generate a generic form that includes a content type field, + # and a content field. + content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE + content_field = api_settings.FORM_CONTENT_OVERRIDE + + media_types = [parser.media_type for parser in view.parser_classes] + choices = [(media_type, media_type) for media_type in media_types] + initial = media_types[0] + + # NB. http://jacobian.org/writing/dynamic-form-generation/ + class GenericContentForm(forms.Form): + def __init__(self): + super(GenericContentForm, self).__init__() + + self.fields[content_type_field] = forms.ChoiceField( + label='Media type', + choices=choices, + initial=initial + ) + self.fields[content_field] = forms.CharField( + label='Content', + widget=forms.Textarea, + initial=content + ) + + return GenericContentForm() def get_name(self, view): - return get_view_name(view.__class__, getattr(view, 'suffix', None)) + return view.get_view_name() def get_description(self, view): - return get_view_description(view.__class__, html=True) + return view.get_view_description(html=True) def get_breadcrumbs(self, request): return get_breadcrumbs(request.path) - def render(self, data, accepted_media_type=None, renderer_context=None): + def get_context(self, data, accepted_media_type, renderer_context): """ - Render the HTML for the browsable API representation. + Returns the context used to render. """ - accepted_media_type = accepted_media_type or '' - renderer_context = renderer_context or {} - view = renderer_context['view'] request = renderer_context['request'] response = renderer_context['response'] - media_types = [parser.media_type for parser in view.parser_classes] renderer = self.get_default_renderer(view) - content = self.get_content(renderer, data, accepted_media_type, renderer_context) - - put_form = self._get_form(view, 'PUT', request) - post_form = self._get_form(view, 'POST', request) - patch_form = self._get_form(view, 'PATCH', request) - delete_form = self._get_form(view, 'DELETE', request) - options_form = self._get_form(view, 'OPTIONS', request) - raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) - raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) - raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) + 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 - name = self.get_name(view) - description = self.get_description(view) - breadcrumb_list = self.get_breadcrumbs(request) + response_headers = dict(response.items()) + renderer_content_type = '' + if renderer: + renderer_content_type = '%s' % renderer.media_type + if renderer.charset: + renderer_content_type += ' ;%s' % renderer.charset + response_headers['Content-Type'] = renderer_content_type - template = loader.get_template(self.template) - context = RequestContext(request, { - 'content': content, + if hasattr(view, 'paginator') and view.paginator.display_page_controls: + paginator = view.paginator + else: + paginator = None + + context = { + 'content': self.get_content(renderer, data, accepted_media_type, renderer_context), 'view': view, 'request': request, 'response': response, - 'description': description, - 'name': name, + 'description': self.get_description(view), + 'name': self.get_name(view), 'version': VERSION, - 'breadcrumblist': breadcrumb_list, + 'paginator': paginator, + 'breadcrumblist': self.get_breadcrumbs(request), 'allowed_methods': view.allowed_methods, - 'available_formats': [renderer.format for renderer in view.renderer_classes], + 'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes], + 'response_headers': response_headers, - 'put_form': put_form, - 'post_form': post_form, - 'patch_form': patch_form, - 'delete_form': delete_form, - 'options_form': options_form, + '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, 'raw_data_patch_form': raw_data_patch_form, 'raw_data_put_or_patch_form': raw_data_put_or_patch_form, + 'display_edit_forms': bool(response.status_code != 403), + 'api_settings': api_settings - }) + } + return context + def render(self, data, accepted_media_type=None, renderer_context=None): + """ + Render the HTML for the browsable API representation. + """ + self.accepted_media_type = accepted_media_type or '' + self.renderer_context = renderer_context or {} + + template = loader.get_template(self.template) + context = self.get_context(data, accepted_media_type, renderer_context) + context = RequestContext(renderer_context['request'], context) ret = template.render(context) # 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) + response = renderer_context['response'] if response.status_code == status.HTTP_204_NO_CONTENT: response.status_code = status.HTTP_200_OK @@ -578,7 +652,7 @@ class MultiPartRenderer(BaseRenderer): media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg' format = 'multipart' charset = 'utf-8' - BOUNDARY = 'BoUnDaRyStRiNg' + BOUNDARY = 'BoUnDaRyStRiNg' if django.VERSION >= (1, 5) else b'BoUnDaRyStRiNg' def render(self, data, accepted_media_type=None, renderer_context=None): return encode_multipart(self.BOUNDARY, data) |
