Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit 0242134d32aa99a54442211ed05576b7061866d1 1 parent e3d0790
@lukaszb lukaszb authored timgraham committed
View
4 django/http/__init__.py
@@ -5,7 +5,7 @@
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 @@
'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',
]
View
26 django/http/response.py
@@ -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.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)
View
70 docs/ref/request-response.txt
@@ -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
View
8 docs/releases/1.7.txt
@@ -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
^^^^^
View
2  docs/topics/serialization.txt
@@ -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
~~~~
View
33 tests/httpwrappers/tests.py
@@ -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 @@ def test_not_allowed(self):
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']))
View
5 tests/view_tests/generic_urls.py
@@ -56,3 +56,8 @@
(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'),
+)
View
22 tests/view_tests/tests/test_json.py
@@ -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',
+ })
View
14 tests/view_tests/views.py
@@ -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'),
+ })
Please sign in to comment.
Something went wrong with that request. Please try again.