mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Make sure JSON output in Browseable API is nicely indented
This commit is contained in:
parent
ccd2b0117d
commit
648d2be29b
|
@ -132,7 +132,7 @@ Renders data into HTML for the Browseable API. This renderer will determine whi
|
||||||
|
|
||||||
## Custom renderers
|
## Custom renderers
|
||||||
|
|
||||||
To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type)` method.
|
To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type=None, renderer_context=None)` method.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
@ -144,11 +144,26 @@ For example:
|
||||||
media_type = 'text/plain'
|
media_type = 'text/plain'
|
||||||
format = 'txt'
|
format = 'txt'
|
||||||
|
|
||||||
def render(self, data, media_type):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
if isinstance(data, basestring):
|
if isinstance(data, basestring):
|
||||||
return data
|
return data
|
||||||
return smart_unicode(data)
|
return smart_unicode(data)
|
||||||
|
|
||||||
|
The arguments passed to the `.render()` method are:
|
||||||
|
|
||||||
|
#### `data`
|
||||||
|
|
||||||
|
The request data, as set by the `Response()` instantiation.
|
||||||
|
|
||||||
|
#### `media_type=None`
|
||||||
|
|
||||||
|
Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. Depending on the client's `Accept:` header, this may be more specific than the renderer's `media_type` attribute, and may include media type parameters. For example `"application/json; nested=true"`.
|
||||||
|
|
||||||
|
#### `renderer_context=None`
|
||||||
|
|
||||||
|
Optional. If provided, this is a dictionary of contextual information provided by the view.
|
||||||
|
By default this will include the following keys: `view`, `request`, `response`, `args`, `kwargs`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Advanced renderer usage
|
# Advanced renderer usage
|
||||||
|
|
|
@ -82,6 +82,11 @@ The media type that was selected by the content negotiation stage.
|
||||||
|
|
||||||
Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
|
Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
|
||||||
|
|
||||||
|
## .renderer_context
|
||||||
|
|
||||||
|
A dictionary of additional context information that will be passed to the renderer's `.render()` method.
|
||||||
|
|
||||||
|
Set automatically by the `APIView` or `@api_view` immediately before the response is returned from the view.
|
||||||
|
|
||||||
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
|
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
|
||||||
[statuscodes]: status-codes.md
|
[statuscodes]: status-codes.md
|
||||||
|
|
|
@ -16,7 +16,7 @@ from rest_framework.request import clone_request
|
||||||
from rest_framework.utils import dict2xml
|
from rest_framework.utils import dict2xml
|
||||||
from rest_framework.utils import encoders
|
from rest_framework.utils import encoders
|
||||||
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
from rest_framework.utils.breadcrumbs import get_breadcrumbs
|
||||||
from rest_framework.utils.mediatypes import get_media_type_params, add_media_type_param
|
from rest_framework.utils.mediatypes import get_media_type_params
|
||||||
from rest_framework import VERSION
|
from rest_framework import VERSION
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
@ -30,10 +30,7 @@ class BaseRenderer(object):
|
||||||
media_type = None
|
media_type = None
|
||||||
format = None
|
format = None
|
||||||
|
|
||||||
def __init__(self, view=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
self.view = view
|
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
|
||||||
raise NotImplemented('Renderer class requires .render() to be implemented')
|
raise NotImplemented('Renderer class requires .render() to be implemented')
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,22 +43,29 @@ class JSONRenderer(BaseRenderer):
|
||||||
format = 'json'
|
format = 'json'
|
||||||
encoder_class = encoders.JSONEncoder
|
encoder_class = encoders.JSONEncoder
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Render `obj` into json.
|
Render `obj` into json.
|
||||||
"""
|
"""
|
||||||
if data is None:
|
if data is None:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# If the media type looks like 'application/json; indent=4', then
|
# If 'indent' is provided in the context, then pretty print the result.
|
||||||
# pretty print the result.
|
# E.g. If we're being called by the BrowseableAPIRenderer.
|
||||||
indent = get_media_type_params(accepted_media_type).get('indent', None)
|
renderer_context = renderer_context or {}
|
||||||
sort_keys = False
|
indent = renderer_context.get('indent', None)
|
||||||
try:
|
sort_keys = indent and True or False
|
||||||
indent = max(min(int(indent), 8), 0)
|
|
||||||
sort_keys = True
|
if accepted_media_type:
|
||||||
except (ValueError, TypeError):
|
# If the media type looks like 'application/json; indent=4',
|
||||||
indent = None
|
# then pretty print the result.
|
||||||
|
params = get_media_type_params(accepted_media_type)
|
||||||
|
indent = params.get('indent', indent)
|
||||||
|
try:
|
||||||
|
indent = max(min(int(indent), 8), 0)
|
||||||
|
sort_keys = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
indent = None
|
||||||
|
|
||||||
return json.dumps(data, cls=self.encoder_class,
|
return json.dumps(data, cls=self.encoder_class,
|
||||||
indent=indent, sort_keys=sort_keys)
|
indent=indent, sort_keys=sort_keys)
|
||||||
|
@ -78,22 +82,25 @@ class JSONPRenderer(JSONRenderer):
|
||||||
callback_parameter = 'callback'
|
callback_parameter = 'callback'
|
||||||
default_callback = 'callback'
|
default_callback = 'callback'
|
||||||
|
|
||||||
def get_callback(self):
|
def get_callback(self, renderer_context):
|
||||||
"""
|
"""
|
||||||
Determine the name of the callback to wrap around the json output.
|
Determine the name of the callback to wrap around the json output.
|
||||||
"""
|
"""
|
||||||
params = self.view.request.GET
|
request = renderer_context.get('request', None)
|
||||||
|
params = request and request.GET or {}
|
||||||
return params.get(self.callback_parameter, self.default_callback)
|
return params.get(self.callback_parameter, self.default_callback)
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Renders into jsonp, wrapping the json output in a callback function.
|
Renders into jsonp, wrapping the json output in a callback function.
|
||||||
|
|
||||||
Clients may set the callback function name using a query parameter
|
Clients may set the callback function name using a query parameter
|
||||||
on the URL, for example: ?callback=exampleCallbackName
|
on the URL, for example: ?callback=exampleCallbackName
|
||||||
"""
|
"""
|
||||||
callback = self.get_callback()
|
renderer_context = renderer_context or {}
|
||||||
json = super(JSONPRenderer, self).render(data, accepted_media_type)
|
callback = self.get_callback(renderer_context)
|
||||||
|
json = super(JSONPRenderer, self).render(data, accepted_media_type,
|
||||||
|
renderer_context)
|
||||||
return "%s(%s);" % (callback, json)
|
return "%s(%s);" % (callback, json)
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,7 +112,7 @@ class XMLRenderer(BaseRenderer):
|
||||||
media_type = 'application/xml'
|
media_type = 'application/xml'
|
||||||
format = 'xml'
|
format = 'xml'
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Renders *obj* into serialized XML.
|
Renders *obj* into serialized XML.
|
||||||
"""
|
"""
|
||||||
|
@ -122,7 +129,7 @@ class YAMLRenderer(BaseRenderer):
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
format = 'yaml'
|
format = 'yaml'
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Renders *obj* into serialized YAML.
|
Renders *obj* into serialized YAML.
|
||||||
"""
|
"""
|
||||||
|
@ -145,7 +152,7 @@ class HTMLRenderer(BaseRenderer):
|
||||||
format = 'html'
|
format = 'html'
|
||||||
template_name = None
|
template_name = None
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Renders data to HTML, using Django's standard template rendering.
|
Renders data to HTML, using Django's standard template rendering.
|
||||||
|
|
||||||
|
@ -155,8 +162,10 @@ class HTMLRenderer(BaseRenderer):
|
||||||
2. An explicit .template_name set on this class.
|
2. An explicit .template_name set on this class.
|
||||||
3. The return result of calling view.get_template_names().
|
3. The return result of calling view.get_template_names().
|
||||||
"""
|
"""
|
||||||
view = self.view
|
renderer_context = renderer_context or {}
|
||||||
request, response = view.request, view.response
|
view = renderer_context['view']
|
||||||
|
request = renderer_context['request']
|
||||||
|
response = renderer_context['response']
|
||||||
|
|
||||||
template_names = self.get_template_names(response, view)
|
template_names = self.get_template_names(response, view)
|
||||||
template = self.resolve_template(template_names)
|
template = self.resolve_template(template_names)
|
||||||
|
@ -187,22 +196,29 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
format = 'api'
|
format = 'api'
|
||||||
template = 'rest_framework/api.html'
|
template = 'rest_framework/api.html'
|
||||||
|
|
||||||
def get_content(self, view, request, data, accepted_media_type):
|
def get_default_renderer(self, view):
|
||||||
"""
|
"""
|
||||||
Get the content as if it had been rendered by a non-documenting renderer.
|
Return an instance of the first valid renderer.
|
||||||
|
(Don't use another documenting renderer.)
|
||||||
(Typically this will be the content as it would have been if the Resource had been
|
|
||||||
requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Find the first valid renderer and render the content. (Don't use another documenting renderer.)
|
|
||||||
renderers = [renderer for renderer in view.renderer_classes
|
renderers = [renderer for renderer in view.renderer_classes
|
||||||
if not issubclass(renderer, BrowsableAPIRenderer)]
|
if not issubclass(renderer, BrowsableAPIRenderer)]
|
||||||
if not renderers:
|
if not renderers:
|
||||||
|
return None
|
||||||
|
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:
|
||||||
return '[No renderers were found]'
|
return '[No renderers were found]'
|
||||||
|
|
||||||
accepted_media_type = add_media_type_param(accepted_media_type, 'indent', '4')
|
renderer_context['indent'] = 4
|
||||||
content = renderers[0](view).render(data, accepted_media_type)
|
content = renderer.render(data, accepted_media_type, renderer_context)
|
||||||
|
|
||||||
if not all(char in string.printable for char in content):
|
if not all(char in string.printable for char in content):
|
||||||
return '[%d bytes of binary content]'
|
return '[%d bytes of binary content]'
|
||||||
|
|
||||||
|
@ -228,7 +244,8 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
return True # Don't actually need to return a form
|
return True # Don't actually need to return a form
|
||||||
|
|
||||||
if not getattr(view, 'get_serializer', None):
|
if not getattr(view, 'get_serializer', None):
|
||||||
return self.get_generic_content_form(view)
|
media_types = [parser.media_type for parser in view.parser_classes]
|
||||||
|
return self.get_generic_content_form(media_types)
|
||||||
|
|
||||||
#####
|
#####
|
||||||
# TODO: This is a little bit of a hack. Actually we'd like to remove
|
# TODO: This is a little bit of a hack. Actually we'd like to remove
|
||||||
|
@ -273,9 +290,10 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
form_instance = OnTheFlyForm(data)
|
form_instance = OnTheFlyForm(data)
|
||||||
return form_instance
|
return form_instance
|
||||||
|
|
||||||
def get_generic_content_form(self, view):
|
def get_generic_content_form(self, media_types):
|
||||||
"""
|
"""
|
||||||
Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
|
Returns a form that allows for arbitrary content types to be tunneled
|
||||||
|
via standard HTML forms.
|
||||||
(Which are typically application/x-www-form-urlencoded)
|
(Which are typically application/x-www-form-urlencoded)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -285,74 +303,68 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
and api_settings.FORM_CONTENTTYPE_OVERRIDE):
|
and api_settings.FORM_CONTENTTYPE_OVERRIDE):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
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/
|
# NB. http://jacobian.org/writing/dynamic-form-generation/
|
||||||
class GenericContentForm(forms.Form):
|
class GenericContentForm(forms.Form):
|
||||||
def __init__(self, view, request):
|
def __init__(self):
|
||||||
"""We don't know the names of the fields we want to set until the point the form is instantiated,
|
|
||||||
as they are determined by the Resource the form is being created against.
|
|
||||||
Add the fields dynamically."""
|
|
||||||
super(GenericContentForm, self).__init__()
|
super(GenericContentForm, self).__init__()
|
||||||
|
|
||||||
parsed_media_types = [parser.media_type for parser in view.parser_classes]
|
self.fields[content_type_field] = forms.ChoiceField(
|
||||||
contenttype_choices = [(media_type, media_type) for media_type in parsed_media_types]
|
|
||||||
initial_contenttype = parsed_media_types[0]
|
|
||||||
|
|
||||||
self.fields[api_settings.FORM_CONTENTTYPE_OVERRIDE] = forms.ChoiceField(
|
|
||||||
label='Content Type',
|
label='Content Type',
|
||||||
choices=contenttype_choices,
|
choices=choices,
|
||||||
initial=initial_contenttype
|
initial=initial
|
||||||
)
|
)
|
||||||
self.fields[api_settings.FORM_CONTENT_OVERRIDE] = forms.CharField(
|
self.fields[content_field] = forms.CharField(
|
||||||
label='Content',
|
label='Content',
|
||||||
widget=forms.Textarea
|
widget=forms.Textarea
|
||||||
)
|
)
|
||||||
|
|
||||||
# If either of these reserved parameters are turned off then content tunneling is not possible
|
return GenericContentForm()
|
||||||
if self.view.request._CONTENTTYPE_PARAM is None or self.view.request._CONTENT_PARAM is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Okey doke, let's do it
|
def get_name(self, view):
|
||||||
return GenericContentForm(view, view.request)
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
try:
|
try:
|
||||||
return self.view.get_name()
|
return view.get_name()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self.view.__doc__
|
return view.__doc__
|
||||||
|
|
||||||
def get_description(self, html=None):
|
def get_description(self, view):
|
||||||
if html is None:
|
|
||||||
html = bool('html' in self.format)
|
|
||||||
try:
|
try:
|
||||||
return self.view.get_description(html)
|
return view.get_description(html=True)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self.view.__doc__
|
return view.__doc__
|
||||||
|
|
||||||
def render(self, data=None, accepted_media_type=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
Renders *obj* using the :attr:`template` set on the class.
|
Renders *obj* using the :attr:`template` set on the class.
|
||||||
|
|
||||||
The context used in the template contains all the information
|
The context used in the template contains all the information
|
||||||
needed to self-document the response to this request.
|
needed to self-document the response to this request.
|
||||||
"""
|
"""
|
||||||
view = self.view
|
accepted_media_type = accepted_media_type or ''
|
||||||
request = view.request
|
renderer_context = renderer_context or {}
|
||||||
response = view.response
|
|
||||||
|
|
||||||
content = self.get_content(view, request, data, accepted_media_type)
|
view = renderer_context['view']
|
||||||
|
request = renderer_context['request']
|
||||||
|
response = renderer_context['response']
|
||||||
|
|
||||||
|
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)
|
put_form = self.get_form(view, 'PUT', request)
|
||||||
post_form = self.get_form(view, 'POST', request)
|
post_form = self.get_form(view, 'POST', request)
|
||||||
delete_form = self.get_form(view, 'DELETE', request)
|
delete_form = self.get_form(view, 'DELETE', request)
|
||||||
options_form = self.get_form(view, 'OPTIONS', request)
|
options_form = self.get_form(view, 'OPTIONS', request)
|
||||||
|
|
||||||
name = self.get_name()
|
name = self.get_name(view)
|
||||||
description = self.get_description()
|
description = self.get_description(view)
|
||||||
|
breadcrumb_list = get_breadcrumbs(request.path)
|
||||||
breadcrumb_list = get_breadcrumbs(self.view.request.path)
|
|
||||||
|
|
||||||
template = loader.get_template(self.template)
|
template = loader.get_template(self.template)
|
||||||
context = RequestContext(self.view.request, {
|
context = RequestContext(request, {
|
||||||
'content': content,
|
'content': content,
|
||||||
'view': view,
|
'view': view,
|
||||||
'request': request,
|
'request': request,
|
||||||
|
@ -375,7 +387,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
# Munge DELETE Response code to allow us to return content
|
# Munge DELETE Response code to allow us to return content
|
||||||
# (Do this *after* we've rendered the template so that we include
|
# (Do this *after* we've rendered the template so that we include
|
||||||
# the normal deletion response code in the output)
|
# the normal deletion response code in the output)
|
||||||
if self.view.response.status_code == 204:
|
if response.status_code == 204:
|
||||||
self.view.response.status_code = 200
|
response.status_code = 200
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -24,17 +24,17 @@ class Response(SimpleTemplateResponse):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rendered_content(self):
|
def rendered_content(self):
|
||||||
renderer = self.accepted_renderer
|
renderer = getattr(self, 'accepted_renderer', None)
|
||||||
media_type = self.accepted_media_type
|
media_type = getattr(self, 'accepted_media_type', None)
|
||||||
|
context = getattr(self, 'renderer_context', None)
|
||||||
|
|
||||||
assert renderer, "No accepted renderer set on Response"
|
assert renderer, ".accepted_renderer not set on Response"
|
||||||
assert media_type, "No accepted media type set on Response"
|
assert media_type, ".accepted_media_type not set on Response"
|
||||||
|
assert context, ".renderer_context not set on Response"
|
||||||
|
context['response'] = self
|
||||||
|
|
||||||
self['Content-Type'] = media_type
|
self['Content-Type'] = media_type
|
||||||
if self.data is None:
|
return renderer.render(self.data, media_type, context)
|
||||||
return renderer.render()
|
|
||||||
|
|
||||||
return renderer.render(self.data, media_type)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status_text(self):
|
def status_text(self):
|
||||||
|
@ -42,4 +42,6 @@ class Response(SimpleTemplateResponse):
|
||||||
Returns reason text corresponding to our HTTP response status code.
|
Returns reason text corresponding to our HTTP response status code.
|
||||||
Provided for convenience.
|
Provided for convenience.
|
||||||
"""
|
"""
|
||||||
|
# TODO: Deprecate and use a template tag instead
|
||||||
|
# TODO: Status code text for RFC 6585 status codes
|
||||||
return STATUS_CODE_TEXT.get(self.status_code, '')
|
return STATUS_CODE_TEXT.get(self.status_code, '')
|
||||||
|
|
|
@ -41,16 +41,16 @@ class RendererA(BaseRenderer):
|
||||||
media_type = 'mock/renderera'
|
media_type = 'mock/renderera'
|
||||||
format = "formata"
|
format = "formata"
|
||||||
|
|
||||||
def render(self, obj=None, media_type=None):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
return RENDERER_A_SERIALIZER(obj)
|
return RENDERER_A_SERIALIZER(data)
|
||||||
|
|
||||||
|
|
||||||
class RendererB(BaseRenderer):
|
class RendererB(BaseRenderer):
|
||||||
media_type = 'mock/rendererb'
|
media_type = 'mock/rendererb'
|
||||||
format = "formatb"
|
format = "formatb"
|
||||||
|
|
||||||
def render(self, obj=None, media_type=None):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
return RENDERER_B_SERIALIZER(obj)
|
return RENDERER_B_SERIALIZER(data)
|
||||||
|
|
||||||
|
|
||||||
class MockView(APIView):
|
class MockView(APIView):
|
||||||
|
@ -235,7 +235,7 @@ class JSONRendererTests(TestCase):
|
||||||
Test basic JSON rendering.
|
Test basic JSON rendering.
|
||||||
"""
|
"""
|
||||||
obj = {'foo': ['bar', 'baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
renderer = JSONRenderer(None)
|
renderer = JSONRenderer()
|
||||||
content = renderer.render(obj, 'application/json')
|
content = renderer.render(obj, 'application/json')
|
||||||
# Fix failing test case which depends on version of JSON library.
|
# Fix failing test case which depends on version of JSON library.
|
||||||
self.assertEquals(content, _flat_repr)
|
self.assertEquals(content, _flat_repr)
|
||||||
|
@ -245,7 +245,7 @@ class JSONRendererTests(TestCase):
|
||||||
Test JSON rendering with additional content type arguments supplied.
|
Test JSON rendering with additional content type arguments supplied.
|
||||||
"""
|
"""
|
||||||
obj = {'foo': ['bar', 'baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
renderer = JSONRenderer(None)
|
renderer = JSONRenderer()
|
||||||
content = renderer.render(obj, 'application/json; indent=2')
|
content = renderer.render(obj, 'application/json; indent=2')
|
||||||
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
|
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ if yaml:
|
||||||
Test basic YAML rendering.
|
Test basic YAML rendering.
|
||||||
"""
|
"""
|
||||||
obj = {'foo': ['bar', 'baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
renderer = YAMLRenderer(None)
|
renderer = YAMLRenderer()
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
self.assertEquals(content, _yaml_repr)
|
self.assertEquals(content, _yaml_repr)
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ if yaml:
|
||||||
"""
|
"""
|
||||||
obj = {'foo': ['bar', 'baz']}
|
obj = {'foo': ['bar', 'baz']}
|
||||||
|
|
||||||
renderer = YAMLRenderer(None)
|
renderer = YAMLRenderer()
|
||||||
parser = YAMLParser()
|
parser = YAMLParser()
|
||||||
|
|
||||||
content = renderer.render(obj, 'application/yaml')
|
content = renderer.render(obj, 'application/yaml')
|
||||||
|
@ -345,7 +345,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({'field': 'astring'}, 'application/xml')
|
content = renderer.render({'field': 'astring'}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field>astring</field>')
|
self.assertXMLContains(content, '<field>astring</field>')
|
||||||
|
|
||||||
|
@ -353,7 +353,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({'field': 111}, 'application/xml')
|
content = renderer.render({'field': 111}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field>111</field>')
|
self.assertXMLContains(content, '<field>111</field>')
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({
|
content = renderer.render({
|
||||||
'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
|
||||||
}, 'application/xml')
|
}, 'application/xml')
|
||||||
|
@ -371,7 +371,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({'field': 123.4}, 'application/xml')
|
content = renderer.render({'field': 123.4}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field>123.4</field>')
|
self.assertXMLContains(content, '<field>123.4</field>')
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
|
content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field>111.2</field>')
|
self.assertXMLContains(content, '<field>111.2</field>')
|
||||||
|
|
||||||
|
@ -387,7 +387,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render({'field': None}, 'application/xml')
|
content = renderer.render({'field': None}, 'application/xml')
|
||||||
self.assertXMLContains(content, '<field></field>')
|
self.assertXMLContains(content, '<field></field>')
|
||||||
|
|
||||||
|
@ -395,7 +395,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = renderer.render(self._complex_data, 'application/xml')
|
content = renderer.render(self._complex_data, 'application/xml')
|
||||||
self.assertXMLContains(content, '<sub_name>first</sub_name>')
|
self.assertXMLContains(content, '<sub_name>first</sub_name>')
|
||||||
self.assertXMLContains(content, '<sub_name>second</sub_name>')
|
self.assertXMLContains(content, '<sub_name>second</sub_name>')
|
||||||
|
@ -404,7 +404,7 @@ class XMLRendererTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test XML rendering.
|
Test XML rendering.
|
||||||
"""
|
"""
|
||||||
renderer = XMLRenderer(None)
|
renderer = XMLRenderer()
|
||||||
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
|
||||||
|
|
||||||
parser = XMLParser()
|
parser = XMLParser()
|
||||||
|
|
|
@ -33,16 +33,16 @@ class RendererA(BaseRenderer):
|
||||||
media_type = 'mock/renderera'
|
media_type = 'mock/renderera'
|
||||||
format = "formata"
|
format = "formata"
|
||||||
|
|
||||||
def render(self, obj=None, media_type=None):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
return RENDERER_A_SERIALIZER(obj)
|
return RENDERER_A_SERIALIZER(data)
|
||||||
|
|
||||||
|
|
||||||
class RendererB(BaseRenderer):
|
class RendererB(BaseRenderer):
|
||||||
media_type = 'mock/rendererb'
|
media_type = 'mock/rendererb'
|
||||||
format = "formatb"
|
format = "formatb"
|
||||||
|
|
||||||
def render(self, obj=None, media_type=None):
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
return RENDERER_B_SERIALIZER(obj)
|
return RENDERER_B_SERIALIZER(data)
|
||||||
|
|
||||||
|
|
||||||
class MockView(APIView):
|
class MockView(APIView):
|
||||||
|
|
|
@ -86,6 +86,7 @@ class APIView(View):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_response_headers(self):
|
def default_response_headers(self):
|
||||||
|
# TODO: deprecate?
|
||||||
# TODO: Only vary by accept if multiple renderers
|
# TODO: Only vary by accept if multiple renderers
|
||||||
return {
|
return {
|
||||||
'Allow': ', '.join(self.allowed_methods),
|
'Allow': ', '.join(self.allowed_methods),
|
||||||
|
@ -158,6 +159,20 @@ class APIView(View):
|
||||||
"""
|
"""
|
||||||
raise exceptions.Throttled(wait)
|
raise exceptions.Throttled(wait)
|
||||||
|
|
||||||
|
def get_renderer_context(self):
|
||||||
|
"""
|
||||||
|
Returns a dict that is passed through to the Renderer.render(),
|
||||||
|
as the `renderer_context` keyword argument.
|
||||||
|
"""
|
||||||
|
# Note: Additionally 'response' will also be set on the context,
|
||||||
|
# by the Response object.
|
||||||
|
return {
|
||||||
|
'view': self,
|
||||||
|
'request': self.request,
|
||||||
|
'args': self.args,
|
||||||
|
'kwargs': self.kwargs
|
||||||
|
}
|
||||||
|
|
||||||
# API policy instantiation methods
|
# API policy instantiation methods
|
||||||
|
|
||||||
def get_format_suffix(self, **kwargs):
|
def get_format_suffix(self, **kwargs):
|
||||||
|
@ -171,7 +186,7 @@ class APIView(View):
|
||||||
"""
|
"""
|
||||||
Instantiates and returns the list of renderers that this view can use.
|
Instantiates and returns the list of renderers that this view can use.
|
||||||
"""
|
"""
|
||||||
return [renderer(self) for renderer in self.renderer_classes]
|
return [renderer() for renderer in self.renderer_classes]
|
||||||
|
|
||||||
def get_parsers(self):
|
def get_parsers(self):
|
||||||
"""
|
"""
|
||||||
|
@ -269,6 +284,7 @@ class APIView(View):
|
||||||
|
|
||||||
response.accepted_renderer = request.accepted_renderer
|
response.accepted_renderer = request.accepted_renderer
|
||||||
response.accepted_media_type = request.accepted_media_type
|
response.accepted_media_type = request.accepted_media_type
|
||||||
|
response.renderer_context = self.get_renderer_context()
|
||||||
|
|
||||||
for key, value in self.headers.items():
|
for key, value in self.headers.items():
|
||||||
response[key] = value
|
response[key] = value
|
||||||
|
@ -306,7 +322,7 @@ class APIView(View):
|
||||||
self.request = request
|
self.request = request
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.headers = self.default_response_headers
|
self.headers = self.default_response_headers # deprecate?
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.initial(request, *args, **kwargs)
|
self.initial(request, *args, **kwargs)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user