Commit 648d2be2 by Tom Christie

Make sure JSON output in Browseable API is nicely indented

parent ccd2b011
......@@ -132,7 +132,7 @@ Renders data into HTML for the Browseable API. This renderer will determine whi
## 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:
......@@ -144,11 +144,26 @@ For example:
media_type = 'text/plain'
format = 'txt'
def render(self, data, media_type):
def render(self, data, media_type=None, renderer_context=None):
if isinstance(data, basestring):
return 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
......
......@@ -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.
## .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/
[statuscodes]: status-codes.md
......@@ -24,17 +24,17 @@ class Response(SimpleTemplateResponse):
@property
def rendered_content(self):
renderer = self.accepted_renderer
media_type = self.accepted_media_type
renderer = getattr(self, 'accepted_renderer', None)
media_type = getattr(self, 'accepted_media_type', None)
context = getattr(self, 'renderer_context', None)
assert renderer, "No accepted renderer set on Response"
assert media_type, "No accepted media type set on Response"
assert renderer, ".accepted_renderer not 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
if self.data is None:
return renderer.render()
return renderer.render(self.data, media_type)
return renderer.render(self.data, media_type, context)
@property
def status_text(self):
......@@ -42,4 +42,6 @@ class Response(SimpleTemplateResponse):
Returns reason text corresponding to our HTTP response status code.
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, '')
......@@ -41,16 +41,16 @@ class RendererA(BaseRenderer):
media_type = 'mock/renderera'
format = "formata"
def render(self, obj=None, media_type=None):
return RENDERER_A_SERIALIZER(obj)
def render(self, data, media_type=None, renderer_context=None):
return RENDERER_A_SERIALIZER(data)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
format = "formatb"
def render(self, obj=None, media_type=None):
return RENDERER_B_SERIALIZER(obj)
def render(self, data, media_type=None, renderer_context=None):
return RENDERER_B_SERIALIZER(data)
class MockView(APIView):
......@@ -235,7 +235,7 @@ class JSONRendererTests(TestCase):
Test basic JSON rendering.
"""
obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None)
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json')
# Fix failing test case which depends on version of JSON library.
self.assertEquals(content, _flat_repr)
......@@ -245,7 +245,7 @@ class JSONRendererTests(TestCase):
Test JSON rendering with additional content type arguments supplied.
"""
obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer(None)
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json; indent=2')
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
......@@ -302,7 +302,7 @@ if yaml:
Test basic YAML rendering.
"""
obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer(None)
renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml')
self.assertEquals(content, _yaml_repr)
......@@ -313,7 +313,7 @@ if yaml:
"""
obj = {'foo': ['bar', 'baz']}
renderer = YAMLRenderer(None)
renderer = YAMLRenderer()
parser = YAMLParser()
content = renderer.render(obj, 'application/yaml')
......@@ -345,7 +345,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({'field': 'astring'}, 'application/xml')
self.assertXMLContains(content, '<field>astring</field>')
......@@ -353,7 +353,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({'field': 111}, 'application/xml')
self.assertXMLContains(content, '<field>111</field>')
......@@ -361,7 +361,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({
'field': datetime.datetime(2011, 12, 25, 12, 45, 00)
}, 'application/xml')
......@@ -371,7 +371,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({'field': 123.4}, 'application/xml')
self.assertXMLContains(content, '<field>123.4</field>')
......@@ -379,7 +379,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({'field': Decimal('111.2')}, 'application/xml')
self.assertXMLContains(content, '<field>111.2</field>')
......@@ -387,7 +387,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render({'field': None}, 'application/xml')
self.assertXMLContains(content, '<field></field>')
......@@ -395,7 +395,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = renderer.render(self._complex_data, 'application/xml')
self.assertXMLContains(content, '<sub_name>first</sub_name>')
self.assertXMLContains(content, '<sub_name>second</sub_name>')
......@@ -404,7 +404,7 @@ class XMLRendererTestCase(TestCase):
"""
Test XML rendering.
"""
renderer = XMLRenderer(None)
renderer = XMLRenderer()
content = StringIO(renderer.render(self._complex_data, 'application/xml'))
parser = XMLParser()
......
......@@ -33,16 +33,16 @@ class RendererA(BaseRenderer):
media_type = 'mock/renderera'
format = "formata"
def render(self, obj=None, media_type=None):
return RENDERER_A_SERIALIZER(obj)
def render(self, data, media_type=None, renderer_context=None):
return RENDERER_A_SERIALIZER(data)
class RendererB(BaseRenderer):
media_type = 'mock/rendererb'
format = "formatb"
def render(self, obj=None, media_type=None):
return RENDERER_B_SERIALIZER(obj)
def render(self, data, media_type=None, renderer_context=None):
return RENDERER_B_SERIALIZER(data)
class MockView(APIView):
......
......@@ -86,6 +86,7 @@ class APIView(View):
@property
def default_response_headers(self):
# TODO: deprecate?
# TODO: Only vary by accept if multiple renderers
return {
'Allow': ', '.join(self.allowed_methods),
......@@ -158,6 +159,20 @@ class APIView(View):
"""
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
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.
"""
return [renderer(self) for renderer in self.renderer_classes]
return [renderer() for renderer in self.renderer_classes]
def get_parsers(self):
"""
......@@ -269,6 +284,7 @@ class APIView(View):
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
for key, value in self.headers.items():
response[key] = value
......@@ -306,7 +322,7 @@ class APIView(View):
self.request = request
self.args = args
self.kwargs = kwargs
self.headers = self.default_response_headers
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment