From 5e39e159ee6aee90755709cfecec7d22c7ea3049 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 12 Sep 2014 11:38:22 +0100 Subject: [PATCH] UNICODE_JSON and COMPACT_JSON settings --- rest_framework/fields.py | 20 ++++++++++---------- rest_framework/parsers.py | 2 +- rest_framework/renderers.py | 33 ++++++++++++--------------------- rest_framework/settings.py | 3 +++ tests/test_renderers.py | 35 +++++++++++++++++++---------------- tests/test_validation.py | 2 +- 6 files changed, 46 insertions(+), 49 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4f06d1868..9d96cf5cc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -137,6 +137,16 @@ class Field(object): messages.update(error_messages or {}) self.error_messages = messages + def __new__(cls, *args, **kwargs): + """ + When a field is instantiated, we store the arguments that were used, + so that we can present a helpful representation of the object. + """ + instance = super(Field, cls).__new__(cls) + instance._args = args + instance._kwargs = kwargs + return instance + def bind(self, field_name, parent, root): """ Setup the context for the field instance. @@ -249,16 +259,6 @@ class Field(object): raise AssertionError(msg) raise ValidationError(msg.format(**kwargs)) - def __new__(cls, *args, **kwargs): - """ - When a field is instantiated, we store the arguments that were used, - so that we can present a helpful representation of the object. - """ - instance = super(Field, cls).__new__(cls) - instance._args = args - instance._kwargs = kwargs - return instance - def __repr__(self): return representation.field_repr(self) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index c287908dc..fa02ecf1e 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -48,7 +48,7 @@ class JSONParser(BaseParser): """ media_type = 'application/json' - renderer_class = renderers.UnicodeJSONRenderer + renderer_class = renderers.JSONRenderer def parse(self, stream, media_type=None, parser_context=None): """ diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index dfc5a39f6..3bf03e62c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -26,6 +26,10 @@ from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework import exceptions, status, VERSION +def zero_as_none(value): + return None if value == 0 else value + + class BaseRenderer(object): """ All renderers should extend this class, setting the `media_type` @@ -44,13 +48,13 @@ class BaseRenderer(object): class JSONRenderer(BaseRenderer): """ Renderer which serializes to JSON. - Applies JSON's backslash-u character escaping for non-ascii characters. """ media_type = 'application/json' format = 'json' encoder_class = encoders.JSONEncoder - ensure_ascii = True + ensure_ascii = not api_settings.UNICODE_JSON + compact = api_settings.COMPACT_JSON # We don't set a charset because JSON is a binary encoding, # that can be encoded as utf-8, utf-16 or utf-32. @@ -62,9 +66,10 @@ class JSONRenderer(BaseRenderer): if accepted_media_type: # If the media type looks like 'application/json; indent=4', # then pretty print the result. + # Note that we coerce `indent=0` into `indent=None`. base_media_type, params = parse_header(accepted_media_type.encode('ascii')) try: - return max(min(int(params['indent']), 8), 0) + return zero_as_none(max(min(int(params['indent']), 8), 0)) except (KeyError, ValueError, TypeError): pass @@ -81,10 +86,12 @@ class JSONRenderer(BaseRenderer): renderer_context = renderer_context or {} indent = self.get_indent(accepted_media_type, renderer_context) + separators = (',', ':') if (indent is None and self.compact) else (', ', ': ') ret = json.dumps( data, cls=self.encoder_class, - indent=indent, ensure_ascii=self.ensure_ascii + indent=indent, ensure_ascii=self.ensure_ascii, + separators=separators ) # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, @@ -96,14 +103,6 @@ class JSONRenderer(BaseRenderer): return ret -class UnicodeJSONRenderer(JSONRenderer): - ensure_ascii = False - """ - Renderer which serializes to JSON. - Does *not* apply JSON's character escaping for non-ascii characters. - """ - - class JSONPRenderer(JSONRenderer): """ Renderer which serializes to json, @@ -196,7 +195,7 @@ class YAMLRenderer(BaseRenderer): format = 'yaml' encoder = encoders.SafeDumper charset = 'utf-8' - ensure_ascii = True + ensure_ascii = False def render(self, data, accepted_media_type=None, renderer_context=None): """ @@ -210,14 +209,6 @@ class YAMLRenderer(BaseRenderer): return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii) -class UnicodeYAMLRenderer(YAMLRenderer): - """ - Renderer which serializes to YAML. - Does *not* apply character escaping for non-ascii characters. - """ - ensure_ascii = False - - class TemplateHTMLRenderer(BaseRenderer): """ An HTML renderer for use with templates. diff --git a/rest_framework/settings.py b/rest_framework/settings.py index f48643b5d..e55610bbc 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -112,6 +112,9 @@ DEFAULTS = { ), 'TIME_FORMAT': None, + # Encoding + 'UNICODE_JSON': True, + 'COMPACT_JSON': True } diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 91244e261..a8fd5f460 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -13,7 +13,7 @@ from rest_framework.compat import yaml, etree, StringIO from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ - XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer, UnicodeYAMLRenderer + XMLRenderer, JSONPRenderer, BrowsableAPIRenderer from rest_framework.parsers import YAMLParser, XMLParser from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory @@ -32,7 +32,7 @@ RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii') expected_results = [ - ((elem for elem in [1, 2, 3]), JSONRenderer, b'[1, 2, 3]') # Generator + ((elem for elem in [1, 2, 3]), JSONRenderer, b'[1,2,3]') # Generator ] @@ -270,7 +270,7 @@ class RendererEndToEndTests(TestCase): self.assertNotContains(resp, '>text/html; charset=utf-8<') -_flat_repr = '{"foo": ["bar", "baz"]}' +_flat_repr = '{"foo":["bar","baz"]}' _indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}' @@ -373,12 +373,6 @@ class JSONRendererTests(TestCase): content = renderer.render(obj, 'application/json; indent=2') self.assertEqual(strip_trailing_whitespace(content.decode('utf-8')), _indented_repr) - def test_check_ascii(self): - obj = {'countries': ['United Kingdom', 'France', 'España']} - renderer = JSONRenderer() - content = renderer.render(obj, 'application/json') - self.assertEqual(content, '{"countries": ["United Kingdom", "France", "Espa\\u00f1a"]}'.encode('utf-8')) - class UnicodeJSONRendererTests(TestCase): """ @@ -386,9 +380,22 @@ class UnicodeJSONRendererTests(TestCase): """ def test_proper_encoding(self): obj = {'countries': ['United Kingdom', 'France', 'España']} - renderer = UnicodeJSONRenderer() + renderer = JSONRenderer() content = renderer.render(obj, 'application/json') - self.assertEqual(content, '{"countries": ["United Kingdom", "France", "España"]}'.encode('utf-8')) + self.assertEqual(content, '{"countries":["United Kingdom","France","España"]}'.encode('utf-8')) + + +class AsciiJSONRendererTests(TestCase): + """ + Tests specific for the Unicode JSON Renderer + """ + def test_proper_encoding(self): + class AsciiJSONRenderer(JSONRenderer): + ensure_ascii = True + obj = {'countries': ['United Kingdom', 'France', 'España']} + renderer = AsciiJSONRenderer() + content = renderer.render(obj, 'application/json') + self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode('utf-8')) class JSONPRendererTests(TestCase): @@ -487,13 +494,9 @@ if yaml: def assertYAMLContains(self, content, string): self.assertTrue(string in content, '%r not in %r' % (string, content)) - class UnicodeYAMLRendererTests(TestCase): - """ - Tests specific for the Unicode YAML Renderer - """ def test_proper_encoding(self): obj = {'countries': ['United Kingdom', 'France', 'España']} - renderer = UnicodeYAMLRenderer() + renderer = YAMLRenderer() content = renderer.render(obj, 'application/yaml') self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8')) diff --git a/tests/test_validation.py b/tests/test_validation.py index 7543d849b..ce39714d1 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -131,7 +131,7 @@ class TestMaxValueValidatorValidation(TestCase): request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() - self.assertEqual(response.content, b'{"number_value": ["Ensure this value is less than or equal to 100."]}') + self.assertEqual(response.content, b'{"number_value":["Ensure this value is less than or equal to 100."]}') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)