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,
|
||||
HttpResponseNotModified, HttpResponseBadRequest, HttpResponseForbidden,
|
||||
HttpResponseNotFound, HttpResponseNotAllowed, HttpResponseGone,
|
||||
HttpResponseServerError, Http404, BadHeaderError)
|
||||
HttpResponseServerError, Http404, BadHeaderError, JsonResponse)
|
||||
from django.http.utils import (fix_location_header,
|
||||
conditional_content_removal, fix_IE_for_attach, fix_IE_for_vary)
|
||||
|
||||
|
@ -16,6 +16,6 @@ __all__ = [
|
|||
'HttpResponsePermanentRedirect', 'HttpResponseNotModified',
|
||||
'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound',
|
||||
'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',
|
||||
]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from email.header import Header
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
|
@ -13,6 +14,7 @@ from django.conf import settings
|
|||
from django.core import signals
|
||||
from django.core import signing
|
||||
from django.core.exceptions import DisallowedRedirect
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.http.cookie import SimpleCookie
|
||||
from django.utils import six, timezone
|
||||
from django.utils.encoding import force_bytes, force_text, iri_to_uri
|
||||
|
@ -456,3 +458,25 @@ class HttpResponseServerError(HttpResponse):
|
|||
|
||||
class Http404(Exception):
|
||||
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
|
||||
|
||||
A string representing the scheme of the request (``http`` or ``https``
|
||||
A string representing the scheme of the request (``http`` or ``https``
|
||||
usually).
|
||||
|
||||
.. attribute:: HttpRequest.body
|
||||
|
@ -823,6 +823,74 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
|||
:class:`~django.template.response.SimpleTemplateResponse`, and the
|
||||
``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:
|
||||
|
||||
StreamingHttpResponse objects
|
||||
|
|
|
@ -671,15 +671,19 @@ Templates
|
|||
* The new :tfilter:`truncatechars_html` filter truncates a string to be no
|
||||
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
|
||||
specifies the scheme of the request (``http`` or ``https`` normally).
|
||||
|
||||
|
||||
* The shortcut :func:`redirect() <django.shortcuts.redirect>` now supports
|
||||
relative URLs.
|
||||
|
||||
* The new :class:`~django.http.JsonResponse` subclass of
|
||||
:class:`~django.http.HttpResponse` helps easily create JSON-encoded responses.
|
||||
|
||||
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.
|
||||
|
||||
.. _serialization-formats-json:
|
||||
|
||||
JSON
|
||||
~~~~
|
||||
|
||||
|
|
|
@ -2,18 +2,20 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.core.signals import request_finished
|
||||
from django.db import close_old_connections
|
||||
from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
|
||||
HttpResponsePermanentRedirect, HttpResponseNotAllowed,
|
||||
HttpResponseNotModified, StreamingHttpResponse,
|
||||
SimpleCookie, BadHeaderError,
|
||||
SimpleCookie, BadHeaderError, JsonResponse,
|
||||
parse_cookie)
|
||||
from django.test import TestCase
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
def test_streaming_response(self):
|
||||
r = StreamingHttpResponse(iter(['hello', 'world']))
|
||||
|
|
|
@ -56,3 +56,8 @@ urlpatterns += patterns('view_tests.views',
|
|||
(r'^shortcuts/render/dirs/$', 'render_with_dirs'),
|
||||
(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
|
||||
|
||||
import datetime
|
||||
import decimal
|
||||
import os
|
||||
import sys
|
||||
|
||||
from django.core.exceptions import PermissionDenied, SuspiciousOperation
|
||||
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.template import Context, RequestContext, TemplateDoesNotExist
|
||||
from django.views.debug import technical_500_response, SafeExceptionReporterFilter
|
||||
|
@ -334,3 +336,13 @@ def multivalue_dict_key_error(request):
|
|||
exc_info = sys.exc_info()
|
||||
send_log(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