Fixed #17942 -- Added a JsonResponse class to more easily create JSON encoded responses.
Thanks leahculver for the suggestion and Erik Romijn, Simon Charette, and Marc Tamlyn for the reviews.
This commit is contained in:
parent
e3d0790bd0
commit
0242134d32
|
@ -5,7 +5,7 @@ from django.http.response import (HttpResponse, StreamingHttpResponse,
|
||||||
HttpResponseRedirect, HttpResponsePermanentRedirect,
|
HttpResponseRedirect, HttpResponsePermanentRedirect,
|
||||||
HttpResponseNotModified, HttpResponseBadRequest, HttpResponseForbidden,
|
HttpResponseNotModified, HttpResponseBadRequest, HttpResponseForbidden,
|
||||||
HttpResponseNotFound, HttpResponseNotAllowed, HttpResponseGone,
|
HttpResponseNotFound, HttpResponseNotAllowed, HttpResponseGone,
|
||||||
HttpResponseServerError, Http404, BadHeaderError)
|
HttpResponseServerError, Http404, BadHeaderError, JsonResponse)
|
||||||
from django.http.utils import (fix_location_header,
|
from django.http.utils import (fix_location_header,
|
||||||
conditional_content_removal, fix_IE_for_attach, fix_IE_for_vary)
|
conditional_content_removal, fix_IE_for_attach, fix_IE_for_vary)
|
||||||
|
|
||||||
|
@ -16,6 +16,6 @@ __all__ = [
|
||||||
'HttpResponsePermanentRedirect', 'HttpResponseNotModified',
|
'HttpResponsePermanentRedirect', 'HttpResponseNotModified',
|
||||||
'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound',
|
'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound',
|
||||||
'HttpResponseNotAllowed', 'HttpResponseGone', 'HttpResponseServerError',
|
'HttpResponseNotAllowed', 'HttpResponseGone', 'HttpResponseServerError',
|
||||||
'Http404', 'BadHeaderError', 'fix_location_header',
|
'Http404', 'BadHeaderError', 'fix_location_header', 'JsonResponse',
|
||||||
'conditional_content_removal', 'fix_IE_for_attach', 'fix_IE_for_vary',
|
'conditional_content_removal', 'fix_IE_for_attach', 'fix_IE_for_vary',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
try:
|
try:
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
@ -13,6 +14,7 @@ from django.conf import settings
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.exceptions import DisallowedRedirect
|
from django.core.exceptions import DisallowedRedirect
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.http.cookie import SimpleCookie
|
from django.http.cookie import SimpleCookie
|
||||||
from django.utils import six, timezone
|
from django.utils import six, timezone
|
||||||
from django.utils.encoding import force_bytes, force_text, iri_to_uri
|
from django.utils.encoding import force_bytes, force_text, iri_to_uri
|
||||||
|
@ -456,3 +458,25 @@ class HttpResponseServerError(HttpResponse):
|
||||||
|
|
||||||
class Http404(Exception):
|
class Http404(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JsonResponse(HttpResponse):
|
||||||
|
"""
|
||||||
|
An HTTP response class that consumes data to be serialized to JSON.
|
||||||
|
|
||||||
|
:param data: Data to be dumped into json. By default only ``dict`` objects
|
||||||
|
are allowed to be passed due to a security flaw before EcmaScript 5. See
|
||||||
|
the ``safe`` parameter for more information.
|
||||||
|
:param encoder: Should be an json encoder class. Defaults to
|
||||||
|
``django.core.serializers.json.DjangoJSONEncoder``.
|
||||||
|
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
|
||||||
|
to ``True``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):
|
||||||
|
if safe and not isinstance(data, dict):
|
||||||
|
raise TypeError('In order to allow non-dict objects to be '
|
||||||
|
'serialized set the safe parameter to False')
|
||||||
|
kwargs.setdefault('content_type', 'application/json')
|
||||||
|
data = json.dumps(data, cls=encoder)
|
||||||
|
super(JsonResponse, self).__init__(content=data, **kwargs)
|
||||||
|
|
|
@ -36,7 +36,7 @@ All attributes should be considered read-only, unless stated otherwise below.
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
A string representing the scheme of the request (``http`` or ``https``
|
A string representing the scheme of the request (``http`` or ``https``
|
||||||
usually).
|
usually).
|
||||||
|
|
||||||
.. attribute:: HttpRequest.body
|
.. attribute:: HttpRequest.body
|
||||||
|
@ -823,6 +823,74 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
||||||
:class:`~django.template.response.SimpleTemplateResponse`, and the
|
:class:`~django.template.response.SimpleTemplateResponse`, and the
|
||||||
``render`` method must itself return a valid response object.
|
``render`` method must itself return a valid response object.
|
||||||
|
|
||||||
|
JsonResponse objects
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
.. class:: JsonResponse
|
||||||
|
|
||||||
|
.. method:: JsonResponse.__init__(data, encoder=DjangoJSONEncoder, safe=True, **kwargs)
|
||||||
|
|
||||||
|
An :class:`HttpResponse` subclass that helps to create a JSON-encoded
|
||||||
|
response. It inherits most behavior from its superclass with a couple
|
||||||
|
differences:
|
||||||
|
|
||||||
|
Its default ``Content-Type`` header is set to ``application/json``.
|
||||||
|
|
||||||
|
The first parameter, ``data``, should be a ``dict`` instance. If the ``safe``
|
||||||
|
parameter is set to ``False`` (see below) it can be any JSON-serializable
|
||||||
|
object.
|
||||||
|
|
||||||
|
The ``encoder``, which defaults to
|
||||||
|
``django.core.serializers.json.DjangoJSONEncoder``, will be used to
|
||||||
|
serialize the data. See :ref:`JSON serialization
|
||||||
|
<serialization-formats-json>` for more details about this serializer.
|
||||||
|
|
||||||
|
The ``safe`` boolean parameter defaults to ``True``. If it's set to ``False``,
|
||||||
|
any object can be passed for serialization (otherwise only ``dict`` instances
|
||||||
|
are allowed). If ``safe`` is ``True`` and a non-``dict`` object is passed as
|
||||||
|
the first argument, a :exc:`~exceptions.TypeError` will be raised.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Typical usage could look like::
|
||||||
|
|
||||||
|
>>> from django.http import JsonResponse
|
||||||
|
>>> response = JsonResponse({'foo': 'bar'})
|
||||||
|
>>> response.content
|
||||||
|
'{"foo": "bar"}'
|
||||||
|
|
||||||
|
|
||||||
|
Serializing non-dictionary objects
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In order to serialize objects other than ``dict`` you must set the ``safe``
|
||||||
|
parameter to ``False``::
|
||||||
|
|
||||||
|
>>> response = JsonResponse([1, 2, 3], safe=False)
|
||||||
|
|
||||||
|
Without passing ``safe=False``, a :exc:`~exceptions.TypeError` will be raised.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Before the `5th edition of EcmaScript
|
||||||
|
<http://www.ecma-international.org/publications/standards/Ecma-262.htm>`_
|
||||||
|
it was possible to poison the JavaScript ``Array`` constructor. For this
|
||||||
|
reason, Django does not allow passing non-dict objects to the
|
||||||
|
:class:`~django.http.JsonResponse` constructor by default. However, most
|
||||||
|
modern browsers implement EcmaScript 5 which removes this attack vector.
|
||||||
|
Therefore it is possible to disable this security precaution.
|
||||||
|
|
||||||
|
Changing the default JSON encoder
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you need to use a differ JSON encoder class you can pass the ``encoder``
|
||||||
|
parameter to the constructor method::
|
||||||
|
|
||||||
|
>>> response = JsonResponse(data, encoder=MyJSONEncoder)
|
||||||
|
|
||||||
.. _httpresponse-streaming:
|
.. _httpresponse-streaming:
|
||||||
|
|
||||||
StreamingHttpResponse objects
|
StreamingHttpResponse objects
|
||||||
|
|
|
@ -671,15 +671,19 @@ Templates
|
||||||
* The new :tfilter:`truncatechars_html` filter truncates a string to be no
|
* The new :tfilter:`truncatechars_html` filter truncates a string to be no
|
||||||
longer than the specified number of characters, taking HTML into account.
|
longer than the specified number of characters, taking HTML into account.
|
||||||
|
|
||||||
Requests
|
Requests and Responses
|
||||||
^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* The new :attr:`HttpRequest.scheme <django.http.HttpRequest.scheme>` attribute
|
* The new :attr:`HttpRequest.scheme <django.http.HttpRequest.scheme>` attribute
|
||||||
specifies the scheme of the request (``http`` or ``https`` normally).
|
specifies the scheme of the request (``http`` or ``https`` normally).
|
||||||
|
|
||||||
|
|
||||||
* The shortcut :func:`redirect() <django.shortcuts.redirect>` now supports
|
* The shortcut :func:`redirect() <django.shortcuts.redirect>` now supports
|
||||||
relative URLs.
|
relative URLs.
|
||||||
|
|
||||||
|
* The new :class:`~django.http.JsonResponse` subclass of
|
||||||
|
:class:`~django.http.HttpResponse` helps easily create JSON-encoded responses.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,8 @@ the auth.User model has such a relation to the auth.Permission model::
|
||||||
|
|
||||||
This example links the given user with the permission models with PKs 46 and 47.
|
This example links the given user with the permission models with PKs 46 and 47.
|
||||||
|
|
||||||
|
.. _serialization-formats-json:
|
||||||
|
|
||||||
JSON
|
JSON
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,20 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.core.signals import request_finished
|
from django.core.signals import request_finished
|
||||||
from django.db import close_old_connections
|
from django.db import close_old_connections
|
||||||
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
|
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
|
||||||
HttpResponsePermanentRedirect, HttpResponseNotAllowed,
|
HttpResponsePermanentRedirect, HttpResponseNotAllowed,
|
||||||
HttpResponseNotModified, StreamingHttpResponse,
|
HttpResponseNotModified, StreamingHttpResponse,
|
||||||
SimpleCookie, BadHeaderError,
|
SimpleCookie, BadHeaderError, JsonResponse,
|
||||||
parse_cookie)
|
parse_cookie)
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils.encoding import smart_str, force_text
|
from django.utils.encoding import smart_str, force_text
|
||||||
|
@ -451,6 +453,35 @@ class HttpResponseSubclassesTests(TestCase):
|
||||||
self.assertContains(response, 'Only the GET method is allowed', status_code=405)
|
self.assertContains(response, 'Only the GET method is allowed', status_code=405)
|
||||||
|
|
||||||
|
|
||||||
|
class JsonResponseTests(TestCase):
|
||||||
|
def test_json_response_non_ascii(self):
|
||||||
|
data = {'key': 'łóżko'}
|
||||||
|
response = JsonResponse(data)
|
||||||
|
self.assertEqual(json.loads(response.content.decode()), data)
|
||||||
|
|
||||||
|
def test_json_response_raises_type_error_with_default_setting(self):
|
||||||
|
with self.assertRaisesMessage(TypeError,
|
||||||
|
'In order to allow non-dict objects to be serialized set the '
|
||||||
|
'safe parameter to False'):
|
||||||
|
JsonResponse([1, 2, 3])
|
||||||
|
|
||||||
|
def test_json_response_text(self):
|
||||||
|
response = JsonResponse('foobar', safe=False)
|
||||||
|
self.assertEqual(json.loads(response.content.decode()), 'foobar')
|
||||||
|
|
||||||
|
def test_json_response_list(self):
|
||||||
|
response = JsonResponse(['foo', 'bar'], safe=False)
|
||||||
|
self.assertEqual(json.loads(response.content.decode()), ['foo', 'bar'])
|
||||||
|
|
||||||
|
def test_json_response_custom_encoder(self):
|
||||||
|
class CustomDjangoJSONEncoder(DjangoJSONEncoder):
|
||||||
|
def encode(self, o):
|
||||||
|
return json.dumps({'foo': 'bar'})
|
||||||
|
|
||||||
|
response = JsonResponse({}, encoder=CustomDjangoJSONEncoder)
|
||||||
|
self.assertEqual(json.loads(response.content.decode()), {'foo': 'bar'})
|
||||||
|
|
||||||
|
|
||||||
class StreamingHttpResponseTests(TestCase):
|
class StreamingHttpResponseTests(TestCase):
|
||||||
def test_streaming_response(self):
|
def test_streaming_response(self):
|
||||||
r = StreamingHttpResponse(iter(['hello', 'world']))
|
r = StreamingHttpResponse(iter(['hello', 'world']))
|
||||||
|
|
|
@ -56,3 +56,8 @@ urlpatterns += patterns('view_tests.views',
|
||||||
(r'^shortcuts/render/dirs/$', 'render_with_dirs'),
|
(r'^shortcuts/render/dirs/$', 'render_with_dirs'),
|
||||||
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
|
(r'^shortcuts/render/current_app_conflict/$', 'render_view_with_current_app_conflict'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# json response
|
||||||
|
urlpatterns += patterns('view_tests.views',
|
||||||
|
(r'^json/response/$', 'json_response_view'),
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class JsonResponseTests(TestCase):
|
||||||
|
urls = 'view_tests.generic_urls'
|
||||||
|
|
||||||
|
def test_json_response(self):
|
||||||
|
response = self.client.get('/json/response/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
response['content-type'], 'application/json')
|
||||||
|
self.assertEqual(json.loads(response.content.decode()), {
|
||||||
|
'a': [1, 2, 3],
|
||||||
|
'foo': {'bar': 'baz'},
|
||||||
|
'timestamp': '2013-05-19T20:00:00',
|
||||||
|
'value': '3.14',
|
||||||
|
})
|
|
@ -1,11 +1,13 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
||||||
from django.core.urlresolvers import get_resolver
|
from django.core.urlresolvers import get_resolver
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
|
||||||
from django.shortcuts import render_to_response, render
|
from django.shortcuts import render_to_response, render
|
||||||
from django.template import Context, RequestContext, TemplateDoesNotExist
|
from django.template import Context, RequestContext, TemplateDoesNotExist
|
||||||
from django.views.debug import technical_500_response, SafeExceptionReporterFilter
|
from django.views.debug import technical_500_response, SafeExceptionReporterFilter
|
||||||
|
@ -334,3 +336,13 @@ def multivalue_dict_key_error(request):
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
send_log(request, exc_info)
|
send_log(request, exc_info)
|
||||||
return technical_500_response(request, *exc_info)
|
return technical_500_response(request, *exc_info)
|
||||||
|
|
||||||
|
|
||||||
|
def json_response_view(request):
|
||||||
|
return JsonResponse({
|
||||||
|
'a': [1, 2, 3],
|
||||||
|
'foo': {'bar': 'baz'},
|
||||||
|
# Make sure datetime and Decimal objects would be serialized properly
|
||||||
|
'timestamp': datetime.datetime(2013, 5, 19, 20),
|
||||||
|
'value': decimal.Decimal('3.14'),
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue