Make sure JSON output in Browseable API is nicely indented

This commit is contained in:
Tom Christie 2012-10-10 12:15:18 +01:00
parent ccd2b0117d
commit 648d2be29b
7 changed files with 157 additions and 107 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, '')

View File

@ -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()

View File

@ -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):

View File

@ -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)