2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-18 01:39:07 +04:00
|
|
|
Renderers are used to serialize a response into specific media types.
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-18 01:39:07 +04:00
|
|
|
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.
|
|
|
|
|
2015-09-29 22:59:09 +03:00
|
|
|
REST framework also provides an HTML renderer that renders the browsable API.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-11-22 03:20:49 +04:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2017-03-03 18:24:37 +03:00
|
|
|
import base64
|
2015-11-18 17:52:57 +03:00
|
|
|
from collections import OrderedDict
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
from django import forms
|
2016-04-12 06:04:20 +03:00
|
|
|
from django.conf import settings
|
2013-06-05 16:45:28 +04:00
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
2014-12-09 00:56:06 +03:00
|
|
|
from django.core.paginator import Page
|
2012-10-15 16:27:50 +04:00
|
|
|
from django.http.multipartparser import parse_header
|
2017-10-05 21:41:38 +03:00
|
|
|
from django.template import engines, loader
|
2013-06-28 20:17:39 +04:00
|
|
|
from django.test.client import encode_multipart
|
2014-08-19 20:06:55 +04:00
|
|
|
from django.utils import six
|
2017-03-03 18:24:37 +03:00
|
|
|
from django.utils.html import mark_safe
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2015-06-25 23:55:51 +03:00
|
|
|
from rest_framework import VERSION, exceptions, serializers, status
|
|
|
|
from rest_framework.compat import (
|
2016-07-04 18:38:17 +03:00
|
|
|
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi,
|
2017-10-05 21:41:38 +03:00
|
|
|
pygments_css
|
2015-06-25 23:55:51 +03:00
|
|
|
)
|
2013-12-09 11:34:08 +04:00
|
|
|
from rest_framework.exceptions import ParseError
|
2013-08-29 15:55:56 +04:00
|
|
|
from rest_framework.request import is_form_media_type, override_method
|
2012-09-20 16:06:27 +04:00
|
|
|
from rest_framework.settings import api_settings
|
2017-07-07 19:47:08 +03:00
|
|
|
from rest_framework.utils import encoders, json
|
2012-09-20 16:06:27 +04:00
|
|
|
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
2014-10-01 22:44:46 +04:00
|
|
|
from rest_framework.utils.field_mapping import ClassLookupDict
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2014-09-12 14:38:22 +04:00
|
|
|
def zero_as_none(value):
|
|
|
|
return None if value == 0 else value
|
|
|
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
class BaseRenderer(object):
|
|
|
|
"""
|
2012-10-18 01:39:07 +04:00
|
|
|
All renderers should extend this class, setting the `media_type`
|
|
|
|
and `format` attributes, and override the `.render()` method.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
media_type = None
|
|
|
|
format = None
|
2013-05-21 00:00:56 +04:00
|
|
|
charset = 'utf-8'
|
2013-08-23 19:45:55 +04:00
|
|
|
render_style = 'text'
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2015-01-19 17:23:13 +03:00
|
|
|
raise NotImplementedError('Renderer class requires .render() to be implemented')
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2013-05-18 20:21:43 +04:00
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
class JSONRenderer(BaseRenderer):
|
|
|
|
"""
|
2013-05-22 19:46:15 +04:00
|
|
|
Renderer which serializes to JSON.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
media_type = 'application/json'
|
|
|
|
format = 'json'
|
|
|
|
encoder_class = encoders.JSONEncoder
|
2014-09-12 14:38:22 +04:00
|
|
|
ensure_ascii = not api_settings.UNICODE_JSON
|
|
|
|
compact = api_settings.COMPACT_JSON
|
2017-07-10 22:23:12 +03:00
|
|
|
strict = api_settings.STRICT_JSON
|
2014-08-11 19:20:27 +04:00
|
|
|
|
|
|
|
# We don't set a charset because JSON is a binary encoding,
|
|
|
|
# that can be encoded as utf-8, utf-16 or utf-32.
|
2018-01-08 18:22:32 +03:00
|
|
|
# See: https://www.ietf.org/rfc/rfc4627.txt
|
2013-08-23 19:45:55 +04:00
|
|
|
# Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/
|
2014-08-11 19:20:27 +04:00
|
|
|
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.
|
2014-09-12 14:38:22 +04:00
|
|
|
# Note that we coerce `indent=0` into `indent=None`.
|
2014-08-11 19:20:27 +04:00
|
|
|
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
|
|
|
|
try:
|
2014-09-12 14:38:22 +04:00
|
|
|
return zero_as_none(max(min(int(params['indent']), 8), 0))
|
2014-08-11 19:20:27 +04:00
|
|
|
except (KeyError, ValueError, TypeError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2014-08-11 19:20:27 +04:00
|
|
|
Render `data` into JSON, returning a bytestring.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-05 14:12:52 +04:00
|
|
|
if data is None:
|
2013-08-23 19:45:55 +04:00
|
|
|
return bytes()
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
renderer_context = renderer_context or {}
|
2014-08-11 19:20:27 +04:00
|
|
|
indent = self.get_indent(accepted_media_type, renderer_context)
|
2015-01-19 17:41:10 +03:00
|
|
|
|
|
|
|
if indent is None:
|
|
|
|
separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS
|
|
|
|
else:
|
|
|
|
separators = INDENT_SEPARATORS
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2014-08-19 16:28:07 +04:00
|
|
|
ret = json.dumps(
|
|
|
|
data, cls=self.encoder_class,
|
2014-09-12 14:38:22 +04:00
|
|
|
indent=indent, ensure_ascii=self.ensure_ascii,
|
2017-07-10 22:23:12 +03:00
|
|
|
allow_nan=not self.strict, separators=separators
|
2014-08-19 16:28:07 +04:00
|
|
|
)
|
2013-05-21 00:00:56 +04:00
|
|
|
|
2013-05-22 19:46:15 +04:00
|
|
|
# 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):
|
2014-12-04 01:33:34 +03:00
|
|
|
# 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')
|
2013-08-23 19:45:55 +04:00
|
|
|
return bytes(ret.encode('utf-8'))
|
2013-05-21 00:00:56 +04:00
|
|
|
return ret
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2012-10-28 22:12:56 +04:00
|
|
|
class TemplateHTMLRenderer(BaseRenderer):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-28 22:12:56 +04:00
|
|
|
An HTML renderer for use with templates.
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-28 22:12:56 +04:00
|
|
|
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.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-05 14:12:52 +04:00
|
|
|
media_type = 'text/html'
|
|
|
|
format = 'html'
|
2012-10-05 15:13:44 +04:00
|
|
|
template_name = None
|
2012-11-06 14:44:19 +04:00
|
|
|
exception_template_names = [
|
|
|
|
'%(status_code)s.html',
|
|
|
|
'api_exception.html'
|
|
|
|
]
|
2013-05-18 20:21:43 +04:00
|
|
|
charset = 'utf-8'
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-05 15:13:44 +04:00
|
|
|
Renders data to HTML, using Django's standard template rendering.
|
|
|
|
|
|
|
|
The template name is determined by (in order of preference):
|
|
|
|
|
|
|
|
1. An explicit .template_name set on the response.
|
|
|
|
2. An explicit .template_name set on this class.
|
|
|
|
3. The return result of calling view.get_template_names().
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-10 15:15:18 +04:00
|
|
|
renderer_context = renderer_context or {}
|
|
|
|
view = renderer_context['view']
|
|
|
|
request = renderer_context['request']
|
|
|
|
response = renderer_context['response']
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-11-06 14:44:19 +04:00
|
|
|
if response.exception:
|
|
|
|
template = self.get_exception_template(response)
|
|
|
|
else:
|
|
|
|
template_names = self.get_template_names(response, view)
|
|
|
|
template = self.resolve_template(template_names)
|
|
|
|
|
2016-08-05 15:33:25 +03:00
|
|
|
if hasattr(self, 'resolve_context'):
|
|
|
|
# Fallback for older versions.
|
2016-08-09 19:48:29 +03:00
|
|
|
context = self.resolve_context(data, request, response)
|
2016-08-05 15:33:25 +03:00
|
|
|
else:
|
|
|
|
context = self.get_template_context(data, renderer_context)
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context, request=request)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-05 15:13:44 +04:00
|
|
|
def resolve_template(self, template_names):
|
|
|
|
return loader.select_template(template_names)
|
|
|
|
|
2016-08-02 15:11:41 +03:00
|
|
|
def get_template_context(self, data, renderer_context):
|
|
|
|
response = renderer_context['response']
|
2012-11-06 14:44:19 +04:00
|
|
|
if response.exception:
|
|
|
|
data['status_code'] = response.status_code
|
2015-11-18 17:25:58 +03:00
|
|
|
return data
|
2012-10-05 15:13:44 +04:00
|
|
|
|
|
|
|
def get_template_names(self, response, view):
|
|
|
|
if response.template_name:
|
|
|
|
return [response.template_name]
|
|
|
|
elif self.template_name:
|
|
|
|
return [self.template_name]
|
|
|
|
elif hasattr(view, 'get_template_names'):
|
|
|
|
return view.get_template_names()
|
2013-09-26 19:09:08 +04:00
|
|
|
elif hasattr(view, 'template_name'):
|
|
|
|
return [view.template_name]
|
2014-12-05 02:29:28 +03:00
|
|
|
raise ImproperlyConfigured(
|
|
|
|
'Returned a template response with no `template_name` attribute set on either the view or response'
|
|
|
|
)
|
2012-10-05 15:13:44 +04:00
|
|
|
|
2012-11-06 14:44:19 +04:00
|
|
|
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)
|
2013-02-06 17:05:17 +04:00
|
|
|
except Exception:
|
2012-11-06 14:44:19 +04:00
|
|
|
# Fall back to using eg '404 Not Found'
|
2017-10-05 21:41:38 +03:00
|
|
|
body = '%d %s' % (response.status_code, response.status_text.title())
|
|
|
|
template = engines['django'].from_string(body)
|
|
|
|
return template
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-11-06 14:44:19 +04:00
|
|
|
|
|
|
|
# Note, subclass TemplateHTMLRenderer simply for the exception behavior
|
|
|
|
class StaticHTMLRenderer(TemplateHTMLRenderer):
|
2012-10-28 22:12:56 +04:00
|
|
|
"""
|
|
|
|
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'
|
2013-05-18 20:21:43 +04:00
|
|
|
charset = 'utf-8'
|
2012-10-28 22:12:56 +04:00
|
|
|
|
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2012-11-06 14:44:19 +04:00
|
|
|
renderer_context = renderer_context or {}
|
2017-01-08 19:33:43 +03:00
|
|
|
response = renderer_context.get('response')
|
2012-11-06 14:44:19 +04:00
|
|
|
|
|
|
|
if response and response.exception:
|
|
|
|
request = renderer_context['request']
|
|
|
|
template = self.get_exception_template(response)
|
2016-08-05 15:33:25 +03:00
|
|
|
if hasattr(self, 'resolve_context'):
|
|
|
|
context = self.resolve_context(data, request, response)
|
|
|
|
else:
|
|
|
|
context = self.get_template_context(data, renderer_context)
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context, request=request)
|
2012-11-06 14:44:19 +04:00
|
|
|
|
2012-10-28 22:12:56 +04:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
2013-08-23 17:38:31 +04:00
|
|
|
class HTMLFormRenderer(BaseRenderer):
|
2013-08-23 19:16:41 +04:00
|
|
|
"""
|
|
|
|
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.
|
2013-08-23 19:51:34 +04:00
|
|
|
|
|
|
|
Note that rendering of field and form errors is not currently supported.
|
2013-08-23 19:16:41 +04:00
|
|
|
"""
|
|
|
|
media_type = 'text/html'
|
|
|
|
format = 'form'
|
|
|
|
charset = 'utf-8'
|
2015-10-12 23:14:58 +03:00
|
|
|
template_pack = 'rest_framework/vertical/'
|
2014-10-15 18:13:28 +04:00
|
|
|
base_template = 'form.html'
|
2013-08-23 17:38:31 +04:00
|
|
|
|
2014-10-15 18:13:28 +04:00
|
|
|
default_style = ClassLookupDict({
|
2014-10-01 22:44:46 +04:00
|
|
|
serializers.Field: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'text'
|
2014-10-01 22:44:46 +04:00
|
|
|
},
|
2014-10-15 18:13:28 +04:00
|
|
|
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'
|
|
|
|
},
|
2016-10-10 15:03:46 +03:00
|
|
|
serializers.FloatField: {
|
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'number'
|
|
|
|
},
|
2014-10-15 18:13:28 +04:00
|
|
|
serializers.DateTimeField: {
|
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'datetime-local'
|
2014-10-01 22:44:46 +04:00
|
|
|
},
|
2014-10-15 18:13:28 +04:00
|
|
|
serializers.DateField: {
|
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'date'
|
|
|
|
},
|
|
|
|
serializers.TimeField: {
|
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'time'
|
|
|
|
},
|
2014-11-12 23:46:09 +03:00
|
|
|
serializers.FileField: {
|
|
|
|
'base_template': 'input.html',
|
|
|
|
'input_type': 'file'
|
|
|
|
},
|
2014-10-15 18:13:28 +04:00
|
|
|
serializers.BooleanField: {
|
|
|
|
'base_template': 'checkbox.html'
|
2014-10-01 22:44:46 +04:00
|
|
|
},
|
|
|
|
serializers.ChoiceField: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'select.html', # Also valid: 'radio.html'
|
2014-10-01 22:44:46 +04:00
|
|
|
},
|
|
|
|
serializers.MultipleChoiceField: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
|
2014-10-02 00:35:27 +04:00
|
|
|
},
|
2014-11-13 22:35:03 +03:00
|
|
|
serializers.RelatedField: {
|
|
|
|
'base_template': 'select.html', # Also valid: 'radio.html'
|
|
|
|
},
|
|
|
|
serializers.ManyRelatedField: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'select_multiple.html', # Also valid: 'checkbox_multiple.html'
|
2014-10-09 18:11:19 +04:00
|
|
|
},
|
|
|
|
serializers.Serializer: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'fieldset.html'
|
2014-10-09 18:11:19 +04:00
|
|
|
},
|
|
|
|
serializers.ListSerializer: {
|
2014-10-15 18:13:28 +04:00
|
|
|
'base_template': 'list_fieldset.html'
|
2015-03-03 14:34:06 +03:00
|
|
|
},
|
2018-01-02 12:50:49 +03:00
|
|
|
serializers.ListField: {
|
|
|
|
'base_template': 'list_field.html'
|
|
|
|
},
|
|
|
|
serializers.DictField: {
|
|
|
|
'base_template': 'dict_field.html'
|
|
|
|
},
|
2015-03-03 14:34:06 +03:00
|
|
|
serializers.FilePathField: {
|
|
|
|
'base_template': 'select.html',
|
|
|
|
},
|
2017-10-25 11:55:41 +03:00
|
|
|
serializers.JSONField: {
|
|
|
|
'base_template': 'textarea.html',
|
|
|
|
},
|
2014-10-01 22:44:46 +04:00
|
|
|
})
|
|
|
|
|
2014-10-15 18:13:28 +04:00
|
|
|
def render_field(self, field, parent_style):
|
2015-02-12 15:03:00 +03:00
|
|
|
if isinstance(field._field, serializers.HiddenField):
|
2015-01-21 17:26:25 +03:00
|
|
|
return ''
|
|
|
|
|
2014-10-15 18:13:28 +04:00
|
|
|
style = dict(self.default_style[field])
|
|
|
|
style.update(field.style)
|
|
|
|
if 'template_pack' not in style:
|
2014-10-16 23:47:40 +04:00
|
|
|
style['template_pack'] = parent_style.get('template_pack', self.template_pack)
|
2014-10-15 18:13:28 +04:00
|
|
|
style['renderer'] = self
|
2014-10-02 00:35:27 +04:00
|
|
|
|
2015-07-14 17:47:13 +03:00
|
|
|
# Get a clone of the field with text-only value representation.
|
|
|
|
field = field.as_form_field()
|
|
|
|
|
2014-10-15 18:13:28 +04:00
|
|
|
if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type):
|
2014-10-02 19:24:24 +04:00
|
|
|
field.value = field.value.rstrip('Z')
|
2014-10-02 00:35:27 +04:00
|
|
|
|
2014-10-15 18:13:28 +04:00
|
|
|
if 'template' in style:
|
|
|
|
template_name = style['template']
|
|
|
|
else:
|
|
|
|
template_name = style['template_pack'].strip('/') + '/' + style['base_template']
|
|
|
|
|
2014-10-01 22:44:46 +04:00
|
|
|
template = loader.get_template(template_name)
|
2015-11-18 17:25:58 +03:00
|
|
|
context = {'field': field, 'style': style}
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context)
|
2014-10-01 22:44:46 +04:00
|
|
|
|
2013-08-23 19:10:20 +04:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2013-08-23 19:51:34 +04:00
|
|
|
"""
|
|
|
|
Render serializer data and return an HTML form, as a string.
|
|
|
|
"""
|
2016-01-26 20:58:01 +03:00
|
|
|
renderer_context = renderer_context or {}
|
2014-10-15 18:13:28 +04:00
|
|
|
form = data.serializer
|
2015-10-06 12:58:20 +03:00
|
|
|
|
|
|
|
style = renderer_context.get('style', {})
|
2014-10-15 18:13:28 +04:00
|
|
|
if 'template_pack' not in style:
|
|
|
|
style['template_pack'] = self.template_pack
|
|
|
|
style['renderer'] = self
|
|
|
|
|
2015-10-06 12:58:20 +03:00
|
|
|
template_pack = style['template_pack'].strip('/')
|
|
|
|
template_name = template_pack + '/' + self.base_template
|
2014-10-15 18:13:28 +04:00
|
|
|
template = loader.get_template(template_name)
|
2015-11-18 17:25:58 +03:00
|
|
|
context = {
|
2014-10-15 18:13:28 +04:00
|
|
|
'form': form,
|
|
|
|
'style': style
|
2015-11-18 17:25:58 +03:00
|
|
|
}
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context)
|
2013-08-23 17:38:31 +04:00
|
|
|
|
|
|
|
|
2012-10-09 18:58:48 +04:00
|
|
|
class BrowsableAPIRenderer(BaseRenderer):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-09-20 20:44:34 +04:00
|
|
|
HTML renderer used to self-document the API.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-09-20 20:44:34 +04:00
|
|
|
media_type = 'text/html'
|
2012-10-05 14:12:52 +04:00
|
|
|
format = 'api'
|
2012-09-20 20:44:34 +04:00
|
|
|
template = 'rest_framework/api.html'
|
2015-08-21 18:13:52 +03:00
|
|
|
filter_template = 'rest_framework/filters/base.html'
|
2017-11-10 11:42:21 +03:00
|
|
|
code_style = 'emacs'
|
2013-05-18 20:21:43 +04:00
|
|
|
charset = 'utf-8'
|
2013-08-23 19:10:20 +04:00
|
|
|
form_renderer_class = HTMLFormRenderer
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
def get_default_renderer(self, view):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-10 15:15:18 +04:00
|
|
|
Return an instance of the first valid renderer.
|
|
|
|
(Don't use another documenting renderer.)
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
renderers = [renderer for renderer in view.renderer_classes
|
2012-10-09 18:58:48 +04:00
|
|
|
if not issubclass(renderer, BrowsableAPIRenderer)]
|
2013-09-26 19:09:08 +04:00
|
|
|
non_template_renderers = [renderer for renderer in renderers
|
|
|
|
if not hasattr(renderer, 'get_template_names')]
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
if not renderers:
|
2012-10-10 15:15:18 +04:00
|
|
|
return None
|
2013-09-26 19:09:08 +04:00
|
|
|
elif non_template_renderers:
|
|
|
|
return non_template_renderers[0]()
|
2012-10-10 15:15:18 +04:00
|
|
|
return renderers[0]()
|
|
|
|
|
|
|
|
def get_content(self, renderer, data,
|
|
|
|
accepted_media_type, renderer_context):
|
|
|
|
"""
|
|
|
|
Get the content as if it had been rendered by the default
|
|
|
|
non-documenting renderer.
|
|
|
|
"""
|
|
|
|
if not renderer:
|
2012-09-20 16:06:27 +04:00
|
|
|
return '[No renderers were found]'
|
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
renderer_context['indent'] = 4
|
|
|
|
content = renderer.render(data, accepted_media_type, renderer_context)
|
|
|
|
|
2013-08-23 19:45:55 +04:00
|
|
|
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':
|
2013-05-19 00:12:44 +04:00
|
|
|
return '[%d bytes of binary content]' % len(content)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
return content
|
|
|
|
|
2012-10-26 15:46:15 +04:00
|
|
|
def show_form_for_method(self, view, method, request, obj):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-22 01:04:12 +04:00
|
|
|
Returns True if a form should be shown for this method.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2014-08-19 16:54:52 +04:00
|
|
|
if method not in view.allowed_methods:
|
2012-09-28 00:51:46 +04:00
|
|
|
return # Not a valid method
|
|
|
|
|
2012-10-15 17:03:36 +04:00
|
|
|
try:
|
2013-05-18 12:36:09 +04:00
|
|
|
view.check_permissions(request)
|
2013-05-19 02:52:02 +04:00
|
|
|
if obj is not None:
|
|
|
|
view.check_object_permissions(request, obj)
|
2013-02-11 17:02:20 +04:00
|
|
|
except exceptions.APIException:
|
|
|
|
return False # Doesn't have permissions
|
2012-10-22 01:04:12 +04:00
|
|
|
return True
|
2012-09-28 00:51:46 +04:00
|
|
|
|
2015-03-22 17:36:30 +03:00
|
|
|
def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
|
|
|
|
kwargs['context'] = {
|
|
|
|
'request': request,
|
|
|
|
'format': self.format,
|
|
|
|
'view': view_instance
|
|
|
|
}
|
|
|
|
return serializer_class(*args, **kwargs)
|
|
|
|
|
2014-10-02 19:24:24 +04:00
|
|
|
def get_rendered_html_form(self, data, view, method, request):
|
2012-10-22 01:04:12 +04:00
|
|
|
"""
|
2013-08-23 19:10:20 +04:00
|
|
|
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.
|
2012-10-22 01:04:12 +04:00
|
|
|
"""
|
2014-11-18 18:38:31 +03:00
|
|
|
# See issue #2089 for refactoring this.
|
2014-10-02 19:24:24 +04:00
|
|
|
serializer = getattr(data, 'serializer', None)
|
|
|
|
if serializer and not getattr(serializer, 'many', False):
|
|
|
|
instance = getattr(serializer, 'instance', None)
|
2014-12-09 00:56:06 +03:00
|
|
|
if isinstance(instance, Page):
|
|
|
|
instance = None
|
2014-10-02 19:24:24 +04:00
|
|
|
else:
|
|
|
|
instance = None
|
|
|
|
|
2014-11-20 19:14:51 +03:00
|
|
|
# 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:
|
2013-12-09 11:34:08 +04:00
|
|
|
try:
|
2014-12-17 17:14:51 +03:00
|
|
|
kwargs = {'data': request.data}
|
2013-12-09 11:34:08 +04:00
|
|
|
except ParseError:
|
2014-12-17 17:14:51 +03:00
|
|
|
kwargs = {}
|
2014-11-07 17:08:20 +03:00
|
|
|
existing_serializer = serializer
|
2013-10-02 19:13:34 +04:00
|
|
|
else:
|
2014-12-17 17:14:51 +03:00
|
|
|
kwargs = {}
|
2014-11-07 17:08:20 +03:00
|
|
|
existing_serializer = None
|
2013-10-02 19:13:34 +04:00
|
|
|
|
2013-08-29 15:55:56 +04:00
|
|
|
with override_method(view, request, method) as request:
|
2014-10-02 19:24:24 +04:00
|
|
|
if not self.show_form_for_method(view, method, request, instance):
|
2013-08-29 15:55:56 +04:00
|
|
|
return
|
|
|
|
|
|
|
|
if method in ('DELETE', 'OPTIONS'):
|
|
|
|
return True # Don't actually need to return a form
|
|
|
|
|
2015-03-22 17:36:30 +03:00
|
|
|
has_serializer = getattr(view, 'get_serializer', None)
|
|
|
|
has_serializer_class = getattr(view, 'serializer_class', None)
|
|
|
|
|
2014-08-19 16:28:07 +04:00
|
|
|
if (
|
2015-03-22 17:36:30 +03:00
|
|
|
(not has_serializer and not has_serializer_class) or
|
2015-02-17 13:58:00 +03:00
|
|
|
not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)
|
2014-08-19 16:28:07 +04:00
|
|
|
):
|
2013-08-29 15:55:56 +04:00
|
|
|
return
|
|
|
|
|
2014-11-07 17:08:20 +03:00
|
|
|
if existing_serializer is not None:
|
2016-06-08 19:13:20 +03:00
|
|
|
try:
|
|
|
|
return self.render_form_for_serializer(existing_serializer)
|
|
|
|
except TypeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if has_serializer:
|
|
|
|
if method in ('PUT', 'PATCH'):
|
|
|
|
serializer = view.get_serializer(instance=instance, **kwargs)
|
|
|
|
else:
|
|
|
|
serializer = view.get_serializer(**kwargs)
|
2014-11-07 17:08:20 +03:00
|
|
|
else:
|
2016-06-08 19:13:20 +03:00
|
|
|
# at this point we must have a serializer_class
|
|
|
|
if method in ('PUT', 'PATCH'):
|
|
|
|
serializer = self._get_serializer(view.serializer_class, view,
|
|
|
|
request, instance=instance, **kwargs)
|
2014-11-18 18:11:40 +03:00
|
|
|
else:
|
2016-06-08 19:13:20 +03:00
|
|
|
serializer = self._get_serializer(view.serializer_class, view,
|
|
|
|
request, **kwargs)
|
|
|
|
|
|
|
|
return self.render_form_for_serializer(serializer)
|
|
|
|
|
|
|
|
def render_form_for_serializer(self, serializer):
|
|
|
|
if hasattr(serializer, 'initial_data'):
|
|
|
|
serializer.is_valid()
|
|
|
|
|
|
|
|
form_renderer = self.form_renderer_class()
|
|
|
|
return form_renderer.render(
|
|
|
|
serializer.data,
|
|
|
|
self.accepted_media_type,
|
|
|
|
{'style': {'template_pack': 'rest_framework/horizontal'}}
|
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2014-10-02 19:24:24 +04:00
|
|
|
def get_raw_data_form(self, data, view, method, request):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-10 15:15:18 +04:00
|
|
|
Returns a form that allows for arbitrary content types to be tunneled
|
|
|
|
via standard HTML forms.
|
2012-09-20 16:06:27 +04:00
|
|
|
(Which are typically application/x-www-form-urlencoded)
|
|
|
|
"""
|
2014-11-18 18:38:31 +03:00
|
|
|
# See issue #2089 for refactoring this.
|
2014-10-02 19:24:24 +04:00
|
|
|
serializer = getattr(data, 'serializer', None)
|
|
|
|
if serializer and not getattr(serializer, 'many', False):
|
|
|
|
instance = getattr(serializer, 'instance', None)
|
2014-12-09 00:56:06 +03:00
|
|
|
if isinstance(instance, Page):
|
|
|
|
instance = None
|
2014-10-02 19:24:24 +04:00
|
|
|
else:
|
|
|
|
instance = None
|
|
|
|
|
2013-08-29 15:55:56 +04:00
|
|
|
with override_method(view, request, method) as request:
|
|
|
|
# Check permissions
|
2014-10-02 19:24:24 +04:00
|
|
|
if not self.show_form_for_method(view, method, request, instance):
|
2013-08-29 15:55:56 +04:00
|
|
|
return
|
|
|
|
|
2013-08-29 20:23:26 +04:00
|
|
|
# If possible, serialize the initial content for the generic form
|
|
|
|
default_parser = view.parser_classes[0]
|
|
|
|
renderer_class = getattr(default_parser, 'renderer_class', None)
|
2017-01-08 19:09:23 +03:00
|
|
|
if hasattr(view, 'get_serializer') and renderer_class:
|
2013-08-29 20:23:26 +04:00
|
|
|
# View has a serializer defined and parser class has a
|
|
|
|
# corresponding renderer that can be used to render the data.
|
|
|
|
|
2014-11-18 18:11:40 +03:00
|
|
|
if method in ('PUT', 'PATCH'):
|
|
|
|
serializer = view.get_serializer(instance=instance)
|
|
|
|
else:
|
|
|
|
serializer = view.get_serializer()
|
2013-08-29 20:23:26 +04:00
|
|
|
|
|
|
|
# Render the raw data content
|
|
|
|
renderer = renderer_class()
|
|
|
|
accepted = self.accepted_media_type
|
2013-08-29 23:39:05 +04:00
|
|
|
context = self.renderer_context.copy()
|
|
|
|
context['indent'] = 4
|
2017-10-16 10:35:53 +03:00
|
|
|
|
|
|
|
# strip HiddenField from output
|
|
|
|
data = serializer.data.copy()
|
|
|
|
for name, field in serializer.fields.items():
|
|
|
|
if isinstance(field, serializers.HiddenField):
|
|
|
|
data.pop(name, None)
|
2017-07-10 13:43:36 +03:00
|
|
|
content = renderer.render(data, accepted, context)
|
2017-11-06 12:02:48 +03:00
|
|
|
# Renders returns bytes, but CharField expects a str.
|
|
|
|
content = content.decode('utf-8')
|
2013-08-29 20:23:26 +04:00
|
|
|
else:
|
|
|
|
content = None
|
|
|
|
|
|
|
|
# Generate a generic form that includes a content type field,
|
|
|
|
# and a content field.
|
|
|
|
media_types = [parser.media_type for parser in view.parser_classes]
|
2013-08-29 15:55:56 +04:00
|
|
|
choices = [(media_type, media_type) for media_type in media_types]
|
|
|
|
initial = media_types[0]
|
|
|
|
|
|
|
|
class GenericContentForm(forms.Form):
|
2015-09-17 17:17:29 +03:00
|
|
|
_content_type = forms.ChoiceField(
|
|
|
|
label='Media type',
|
|
|
|
choices=choices,
|
|
|
|
initial=initial,
|
|
|
|
widget=forms.Select(attrs={'data-override': 'content-type'})
|
|
|
|
)
|
|
|
|
_content = forms.CharField(
|
|
|
|
label='Content',
|
|
|
|
widget=forms.Textarea(attrs={'data-override': 'content'}),
|
2017-09-04 12:04:48 +03:00
|
|
|
initial=content,
|
|
|
|
required=False
|
2015-09-17 17:17:29 +03:00
|
|
|
)
|
2013-08-29 15:55:56 +04:00
|
|
|
|
|
|
|
return GenericContentForm()
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
def get_name(self, view):
|
2013-08-18 01:44:51 +04:00
|
|
|
return view.get_view_name()
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2015-08-05 15:59:55 +03:00
|
|
|
def get_description(self, view, status_code):
|
|
|
|
if status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN):
|
|
|
|
return ''
|
2013-08-18 01:44:51 +04:00
|
|
|
return view.get_view_description(html=True)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2013-04-30 17:34:03 +04:00
|
|
|
def get_breadcrumbs(self, request):
|
2015-05-19 17:49:37 +03:00
|
|
|
return get_breadcrumbs(request.path, request)
|
2013-04-30 17:34:03 +04:00
|
|
|
|
2015-08-27 16:35:39 +03:00
|
|
|
def get_filter_form(self, data, view, request):
|
2015-08-21 18:13:52 +03:00
|
|
|
if not hasattr(view, 'get_queryset') or not hasattr(view, 'filter_backends'):
|
|
|
|
return
|
|
|
|
|
2015-08-27 16:35:39 +03:00
|
|
|
# Infer if this is a list view or not.
|
|
|
|
paginator = getattr(view, 'paginator', None)
|
2015-10-22 13:37:27 +03:00
|
|
|
if isinstance(data, list):
|
|
|
|
pass
|
2017-01-08 19:09:23 +03:00
|
|
|
elif paginator is not None and data is not None:
|
2015-08-27 16:35:39 +03:00
|
|
|
try:
|
|
|
|
paginator.get_results(data)
|
|
|
|
except (TypeError, KeyError):
|
|
|
|
return
|
|
|
|
elif not isinstance(data, list):
|
|
|
|
return
|
|
|
|
|
2015-08-21 18:13:52 +03:00
|
|
|
queryset = view.get_queryset()
|
|
|
|
elements = []
|
|
|
|
for backend in view.filter_backends:
|
|
|
|
if hasattr(backend, 'to_html'):
|
|
|
|
html = backend().to_html(request, queryset, view)
|
2015-08-27 16:25:44 +03:00
|
|
|
if html:
|
|
|
|
elements.append(html)
|
2015-08-21 18:13:52 +03:00
|
|
|
|
|
|
|
if not elements:
|
|
|
|
return
|
|
|
|
|
|
|
|
template = loader.get_template(self.filter_template)
|
2015-11-18 17:25:58 +03:00
|
|
|
context = {'elements': elements}
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context)
|
2015-08-21 18:13:52 +03:00
|
|
|
|
2013-09-13 16:51:11 +04:00
|
|
|
def get_context(self, data, accepted_media_type, renderer_context):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2013-09-13 16:51:11 +04:00
|
|
|
Returns the context used to render.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-10 15:15:18 +04:00
|
|
|
view = renderer_context['view']
|
|
|
|
request = renderer_context['request']
|
|
|
|
response = renderer_context['response']
|
2013-08-27 14:22:19 +04:00
|
|
|
|
2012-10-10 15:15:18 +04:00
|
|
|
renderer = self.get_default_renderer(view)
|
2013-02-22 12:39:26 +04:00
|
|
|
|
2014-10-02 19:24:24 +04:00
|
|
|
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)
|
2013-02-22 12:39:26 +04:00
|
|
|
raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2015-11-18 17:52:57 +03:00
|
|
|
response_headers = OrderedDict(sorted(response.items()))
|
2014-02-21 21:12:41 +04:00
|
|
|
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
|
|
|
|
|
2015-04-08 20:32:02 +03:00
|
|
|
if getattr(view, 'paginator', None) and view.paginator.display_page_controls:
|
2015-01-15 19:52:07 +03:00
|
|
|
paginator = view.paginator
|
|
|
|
else:
|
|
|
|
paginator = None
|
|
|
|
|
2016-08-18 13:24:03 +03:00
|
|
|
csrf_cookie_name = settings.CSRF_COOKIE_NAME
|
2017-11-20 11:35:54 +03:00
|
|
|
csrf_header_name = settings.CSRF_HEADER_NAME
|
2016-08-18 13:24:03 +03:00
|
|
|
if csrf_header_name.startswith('HTTP_'):
|
|
|
|
csrf_header_name = csrf_header_name[5:]
|
|
|
|
csrf_header_name = csrf_header_name.replace('_', '-')
|
|
|
|
|
2013-09-13 18:54:44 +04:00
|
|
|
context = {
|
2013-09-13 16:51:11 +04:00
|
|
|
'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
|
2017-11-10 11:42:21 +03:00
|
|
|
'code_style': pygments_css(self.code_style),
|
2012-09-20 20:44:34 +04:00
|
|
|
'view': view,
|
|
|
|
'request': request,
|
|
|
|
'response': response,
|
2016-06-01 17:31:00 +03:00
|
|
|
'user': request.user,
|
2015-08-05 15:59:55 +03:00
|
|
|
'description': self.get_description(view, response.status_code),
|
2013-09-13 16:51:11 +04:00
|
|
|
'name': self.get_name(view),
|
2012-09-20 16:06:27 +04:00
|
|
|
'version': VERSION,
|
2015-01-15 19:52:07 +03:00
|
|
|
'paginator': paginator,
|
2013-09-13 16:51:11 +04:00
|
|
|
'breadcrumblist': self.get_breadcrumbs(request),
|
2012-09-28 00:51:46 +04:00
|
|
|
'allowed_methods': view.allowed_methods,
|
2014-08-19 16:28:07 +04:00
|
|
|
'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
|
2014-02-21 21:12:41 +04:00
|
|
|
'response_headers': response_headers,
|
2013-02-22 12:39:26 +04:00
|
|
|
|
2014-10-02 19:24:24 +04:00
|
|
|
'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),
|
2013-02-22 12:39:26 +04:00
|
|
|
|
2015-08-27 16:35:39 +03:00
|
|
|
'filter_form': self.get_filter_form(data, view, request),
|
2015-08-21 18:13:52 +03:00
|
|
|
|
2013-02-22 12:39:26 +04:00
|
|
|
'raw_data_put_form': raw_data_put_form,
|
2013-10-02 19:13:34 +04:00
|
|
|
'raw_data_post_form': raw_data_post_form,
|
2013-02-22 12:39:26 +04:00
|
|
|
'raw_data_patch_form': raw_data_patch_form,
|
|
|
|
'raw_data_put_or_patch_form': raw_data_put_or_patch_form,
|
|
|
|
|
2013-09-13 18:54:44 +04:00
|
|
|
'display_edit_forms': bool(response.status_code != 403),
|
2013-09-13 16:51:11 +04:00
|
|
|
|
2016-04-12 06:04:20 +03:00
|
|
|
'api_settings': api_settings,
|
2016-08-18 13:24:03 +03:00
|
|
|
'csrf_cookie_name': csrf_cookie_name,
|
|
|
|
'csrf_header_name': csrf_header_name
|
2013-09-13 18:54:44 +04:00
|
|
|
}
|
2013-09-13 16:51:11 +04:00
|
|
|
return context
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2013-09-13 16:51:11 +04:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
|
|
|
"""
|
|
|
|
Render the HTML for the browsable API representation.
|
|
|
|
"""
|
2013-09-13 18:54:44 +04:00
|
|
|
self.accepted_media_type = accepted_media_type or ''
|
|
|
|
self.renderer_context = renderer_context or {}
|
|
|
|
|
2013-09-13 16:51:11 +04:00
|
|
|
template = loader.get_template(self.template)
|
|
|
|
context = self.get_context(data, accepted_media_type, renderer_context)
|
2017-10-05 21:41:38 +03:00
|
|
|
ret = template.render(context, request=renderer_context['request'])
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
# 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)
|
2013-09-13 16:51:11 +04:00
|
|
|
response = renderer_context['response']
|
2012-11-19 20:35:28 +04:00
|
|
|
if response.status_code == status.HTTP_204_NO_CONTENT:
|
|
|
|
response.status_code = status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
return ret
|
2013-06-28 20:17:39 +04:00
|
|
|
|
|
|
|
|
2015-05-12 16:49:09 +03:00
|
|
|
class AdminRenderer(BrowsableAPIRenderer):
|
|
|
|
template = 'rest_framework/admin.html'
|
|
|
|
format = 'admin'
|
|
|
|
|
2015-05-19 18:30:45 +03:00
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
|
|
|
self.accepted_media_type = accepted_media_type or ''
|
|
|
|
self.renderer_context = renderer_context or {}
|
|
|
|
|
2015-07-23 18:16:48 +03:00
|
|
|
response = renderer_context['response']
|
|
|
|
request = renderer_context['request']
|
|
|
|
view = self.renderer_context['view']
|
|
|
|
|
|
|
|
if response.status_code == status.HTTP_400_BAD_REQUEST:
|
|
|
|
# Errors still need to display the list or detail information.
|
|
|
|
# The only way we can get at that is to simulate a GET request.
|
|
|
|
self.error_form = self.get_rendered_html_form(data, view, request.method, request)
|
|
|
|
self.error_title = {'POST': 'Create', 'PUT': 'Edit'}.get(request.method, 'Errors')
|
|
|
|
|
|
|
|
with override_method(view, request, 'GET') as request:
|
|
|
|
response = view.get(request, *view.args, **view.kwargs)
|
|
|
|
data = response.data
|
|
|
|
|
2015-05-19 18:30:45 +03:00
|
|
|
template = loader.get_template(self.template)
|
|
|
|
context = self.get_context(data, accepted_media_type, renderer_context)
|
2017-10-05 21:41:38 +03:00
|
|
|
ret = template.render(context, request=renderer_context['request'])
|
2015-05-19 18:30:45 +03:00
|
|
|
|
|
|
|
# Creation and deletion should use redirects in the admin style.
|
2017-01-08 19:09:23 +03:00
|
|
|
if response.status_code == status.HTTP_201_CREATED and 'Location' in response:
|
2016-05-16 11:22:28 +03:00
|
|
|
response.status_code = status.HTTP_303_SEE_OTHER
|
2015-07-30 16:07:51 +03:00
|
|
|
response['Location'] = request.build_absolute_uri()
|
2015-05-19 18:30:45 +03:00
|
|
|
ret = ''
|
|
|
|
|
|
|
|
if response.status_code == status.HTTP_204_NO_CONTENT:
|
2016-05-16 11:22:28 +03:00
|
|
|
response.status_code = status.HTTP_303_SEE_OTHER
|
2015-05-19 18:30:45 +03:00
|
|
|
try:
|
|
|
|
# Attempt to get the parent breadcrumb URL.
|
|
|
|
response['Location'] = self.get_breadcrumbs(request)[-2][1]
|
|
|
|
except KeyError:
|
|
|
|
# Otherwise reload current URL to get a 'Not Found' page.
|
|
|
|
response['Location'] = request.full_path
|
|
|
|
ret = ''
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
2015-05-12 16:49:09 +03:00
|
|
|
def get_context(self, data, accepted_media_type, renderer_context):
|
|
|
|
"""
|
|
|
|
Render the HTML for the browsable API representation.
|
|
|
|
"""
|
|
|
|
context = super(AdminRenderer, self).get_context(
|
|
|
|
data, accepted_media_type, renderer_context
|
|
|
|
)
|
|
|
|
|
|
|
|
paginator = getattr(context['view'], 'paginator', None)
|
2017-01-08 19:09:23 +03:00
|
|
|
if paginator is not None and data is not None:
|
2015-05-19 18:30:45 +03:00
|
|
|
try:
|
|
|
|
results = paginator.get_results(data)
|
2015-08-20 14:28:00 +03:00
|
|
|
except (TypeError, KeyError):
|
2015-05-19 18:30:45 +03:00
|
|
|
results = data
|
|
|
|
else:
|
2015-05-12 16:49:09 +03:00
|
|
|
results = data
|
|
|
|
|
2015-05-19 18:30:45 +03:00
|
|
|
if results is None:
|
|
|
|
header = {}
|
|
|
|
style = 'detail'
|
|
|
|
elif isinstance(results, list):
|
2015-05-12 17:21:49 +03:00
|
|
|
header = results[0] if results else {}
|
2015-05-12 16:49:09 +03:00
|
|
|
style = 'list'
|
|
|
|
else:
|
|
|
|
header = results
|
|
|
|
style = 'detail'
|
|
|
|
|
2018-01-08 12:49:46 +03:00
|
|
|
columns = [key for key in header if key != 'url']
|
|
|
|
details = [key for key in header if key != 'url']
|
2015-05-12 16:49:09 +03:00
|
|
|
|
|
|
|
context['style'] = style
|
|
|
|
context['columns'] = columns
|
|
|
|
context['details'] = details
|
|
|
|
context['results'] = results
|
2015-07-23 18:16:48 +03:00
|
|
|
context['error_form'] = getattr(self, 'error_form', None)
|
|
|
|
context['error_title'] = getattr(self, 'error_title', None)
|
2015-05-12 16:49:09 +03:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
2017-03-03 18:24:37 +03:00
|
|
|
class DocumentationRenderer(BaseRenderer):
|
|
|
|
media_type = 'text/html'
|
|
|
|
format = 'html'
|
|
|
|
charset = 'utf-8'
|
|
|
|
template = 'rest_framework/docs/index.html'
|
2017-11-06 11:04:07 +03:00
|
|
|
error_template = 'rest_framework/docs/error.html'
|
2017-03-03 18:24:37 +03:00
|
|
|
code_style = 'emacs'
|
2017-03-09 17:49:51 +03:00
|
|
|
languages = ['shell', 'javascript', 'python']
|
2017-03-03 18:24:37 +03:00
|
|
|
|
|
|
|
def get_context(self, data, request):
|
|
|
|
return {
|
|
|
|
'document': data,
|
2017-03-09 17:49:51 +03:00
|
|
|
'langs': self.languages,
|
2018-01-25 11:39:03 +03:00
|
|
|
'lang_htmls': ["rest_framework/docs/langs/%s.html" % l for l in self.languages],
|
|
|
|
'lang_intro_htmls': ["rest_framework/docs/langs/%s-intro.html" % l for l in self.languages],
|
2017-03-09 17:49:51 +03:00
|
|
|
'code_style': pygments_css(self.code_style),
|
2017-03-03 18:24:37 +03:00
|
|
|
'request': request
|
|
|
|
}
|
|
|
|
|
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2017-11-06 11:04:07 +03:00
|
|
|
if isinstance(data, coreapi.Document):
|
|
|
|
template = loader.get_template(self.template)
|
|
|
|
context = self.get_context(data, renderer_context['request'])
|
|
|
|
return template.render(context, request=renderer_context['request'])
|
|
|
|
else:
|
|
|
|
template = loader.get_template(self.error_template)
|
|
|
|
context = {
|
|
|
|
"data": data,
|
|
|
|
"request": renderer_context['request'],
|
|
|
|
"response": renderer_context['response'],
|
|
|
|
"debug": settings.DEBUG,
|
|
|
|
}
|
|
|
|
return template.render(context, request=renderer_context['request'])
|
2017-03-03 18:24:37 +03:00
|
|
|
|
|
|
|
|
|
|
|
class SchemaJSRenderer(BaseRenderer):
|
2017-03-13 13:03:13 +03:00
|
|
|
media_type = 'application/javascript'
|
2017-03-03 18:24:37 +03:00
|
|
|
format = 'javascript'
|
|
|
|
charset = 'utf-8'
|
|
|
|
template = 'rest_framework/schema.js'
|
|
|
|
|
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
|
|
|
codec = coreapi.codecs.CoreJSONCodec()
|
2017-11-22 17:47:03 +03:00
|
|
|
schema = base64.b64encode(codec.encode(data)).decode('ascii')
|
2017-03-03 18:24:37 +03:00
|
|
|
|
|
|
|
template = loader.get_template(self.template)
|
|
|
|
context = {'schema': mark_safe(schema)}
|
|
|
|
request = renderer_context['request']
|
2017-10-05 21:41:38 +03:00
|
|
|
return template.render(context, request=request)
|
2017-03-03 18:24:37 +03:00
|
|
|
|
|
|
|
|
2013-06-28 20:17:39 +04:00
|
|
|
class MultiPartRenderer(BaseRenderer):
|
|
|
|
media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
|
2013-06-30 00:02:58 +04:00
|
|
|
format = 'multipart'
|
2013-06-28 20:17:39 +04:00
|
|
|
charset = 'utf-8'
|
2016-02-18 22:35:45 +03:00
|
|
|
BOUNDARY = 'BoUnDaRyStRiNg'
|
2013-06-28 20:17:39 +04:00
|
|
|
|
|
|
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
2015-07-14 16:49:44 +03:00
|
|
|
if hasattr(data, 'items'):
|
|
|
|
for key, value in data.items():
|
|
|
|
assert not isinstance(value, dict), (
|
|
|
|
"Test data contained a dictionary value for key '%s', "
|
|
|
|
"but multipart uploads do not support nested data. "
|
2015-12-11 02:13:47 +03:00
|
|
|
"You may want to consider using format='json' in this "
|
2015-07-14 16:49:44 +03:00
|
|
|
"test case." % key
|
|
|
|
)
|
2013-06-28 20:17:39 +04:00
|
|
|
return encode_multipart(self.BOUNDARY, data)
|
2016-07-04 18:38:17 +03:00
|
|
|
|
|
|
|
|
|
|
|
class CoreJSONRenderer(BaseRenderer):
|
2016-10-10 15:03:46 +03:00
|
|
|
media_type = 'application/coreapi+json'
|
2016-07-04 18:38:17 +03:00
|
|
|
charset = None
|
|
|
|
format = 'corejson'
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
assert coreapi, 'Using CoreJSONRenderer, but `coreapi` is not installed.'
|
|
|
|
|
|
|
|
def render(self, data, media_type=None, renderer_context=None):
|
|
|
|
indent = bool(renderer_context.get('indent', 0))
|
|
|
|
codec = coreapi.codecs.CoreJSONCodec()
|
|
|
|
return codec.dump(data, indent=indent)
|