Commit 5e39e159 by Tom Christie

UNICODE_JSON and COMPACT_JSON settings

parent 250755de
...@@ -137,6 +137,16 @@ class Field(object): ...@@ -137,6 +137,16 @@ class Field(object):
messages.update(error_messages or {}) messages.update(error_messages or {})
self.error_messages = messages 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): def bind(self, field_name, parent, root):
""" """
Setup the context for the field instance. Setup the context for the field instance.
...@@ -249,16 +259,6 @@ class Field(object): ...@@ -249,16 +259,6 @@ class Field(object):
raise AssertionError(msg) raise AssertionError(msg)
raise ValidationError(msg.format(**kwargs)) 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): def __repr__(self):
return representation.field_repr(self) return representation.field_repr(self)
......
...@@ -48,7 +48,7 @@ class JSONParser(BaseParser): ...@@ -48,7 +48,7 @@ class JSONParser(BaseParser):
""" """
media_type = 'application/json' media_type = 'application/json'
renderer_class = renderers.UnicodeJSONRenderer renderer_class = renderers.JSONRenderer
def parse(self, stream, media_type=None, parser_context=None): def parse(self, stream, media_type=None, parser_context=None):
""" """
......
...@@ -26,6 +26,10 @@ from rest_framework.utils.breadcrumbs import get_breadcrumbs ...@@ -26,6 +26,10 @@ from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework import exceptions, status, VERSION from rest_framework import exceptions, status, VERSION
def zero_as_none(value):
return None if value == 0 else value
class BaseRenderer(object): class BaseRenderer(object):
""" """
All renderers should extend this class, setting the `media_type` All renderers should extend this class, setting the `media_type`
...@@ -44,13 +48,13 @@ class BaseRenderer(object): ...@@ -44,13 +48,13 @@ class BaseRenderer(object):
class JSONRenderer(BaseRenderer): class JSONRenderer(BaseRenderer):
""" """
Renderer which serializes to JSON. Renderer which serializes to JSON.
Applies JSON's backslash-u character escaping for non-ascii characters.
""" """
media_type = 'application/json' media_type = 'application/json'
format = 'json' format = 'json'
encoder_class = encoders.JSONEncoder 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, # We don't set a charset because JSON is a binary encoding,
# that can be encoded as utf-8, utf-16 or utf-32. # that can be encoded as utf-8, utf-16 or utf-32.
...@@ -62,9 +66,10 @@ class JSONRenderer(BaseRenderer): ...@@ -62,9 +66,10 @@ class JSONRenderer(BaseRenderer):
if accepted_media_type: if accepted_media_type:
# If the media type looks like 'application/json; indent=4', # If the media type looks like 'application/json; indent=4',
# then pretty print the result. # 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')) base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
try: try:
return max(min(int(params['indent']), 8), 0) return zero_as_none(max(min(int(params['indent']), 8), 0))
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
pass pass
...@@ -81,10 +86,12 @@ class JSONRenderer(BaseRenderer): ...@@ -81,10 +86,12 @@ class JSONRenderer(BaseRenderer):
renderer_context = renderer_context or {} renderer_context = renderer_context or {}
indent = self.get_indent(accepted_media_type, renderer_context) indent = self.get_indent(accepted_media_type, renderer_context)
separators = (',', ':') if (indent is None and self.compact) else (', ', ': ')
ret = json.dumps( ret = json.dumps(
data, cls=self.encoder_class, 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, # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
...@@ -96,14 +103,6 @@ class JSONRenderer(BaseRenderer): ...@@ -96,14 +103,6 @@ class JSONRenderer(BaseRenderer):
return ret 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): class JSONPRenderer(JSONRenderer):
""" """
Renderer which serializes to json, Renderer which serializes to json,
...@@ -196,7 +195,7 @@ class YAMLRenderer(BaseRenderer): ...@@ -196,7 +195,7 @@ class YAMLRenderer(BaseRenderer):
format = 'yaml' format = 'yaml'
encoder = encoders.SafeDumper encoder = encoders.SafeDumper
charset = 'utf-8' charset = 'utf-8'
ensure_ascii = True ensure_ascii = False
def render(self, data, accepted_media_type=None, renderer_context=None): def render(self, data, accepted_media_type=None, renderer_context=None):
""" """
...@@ -210,14 +209,6 @@ class YAMLRenderer(BaseRenderer): ...@@ -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) 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): class TemplateHTMLRenderer(BaseRenderer):
""" """
An HTML renderer for use with templates. An HTML renderer for use with templates.
......
...@@ -112,6 +112,9 @@ DEFAULTS = { ...@@ -112,6 +112,9 @@ DEFAULTS = {
), ),
'TIME_FORMAT': None, 'TIME_FORMAT': None,
# Encoding
'UNICODE_JSON': True,
'COMPACT_JSON': True
} }
......
...@@ -13,7 +13,7 @@ from rest_framework.compat import yaml, etree, StringIO ...@@ -13,7 +13,7 @@ from rest_framework.compat import yaml, etree, StringIO
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ 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.parsers import YAMLParser, XMLParser
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
...@@ -32,7 +32,7 @@ RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii') ...@@ -32,7 +32,7 @@ RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
expected_results = [ 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): ...@@ -270,7 +270,7 @@ class RendererEndToEndTests(TestCase):
self.assertNotContains(resp, '>text/html; charset=utf-8<') 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}' _indented_repr = '{\n "foo": [\n "bar",\n "baz"\n ]\n}'
...@@ -373,22 +373,29 @@ class JSONRendererTests(TestCase): ...@@ -373,22 +373,29 @@ class JSONRendererTests(TestCase):
content = renderer.render(obj, 'application/json; indent=2') content = renderer.render(obj, 'application/json; indent=2')
self.assertEqual(strip_trailing_whitespace(content.decode('utf-8')), _indented_repr) self.assertEqual(strip_trailing_whitespace(content.decode('utf-8')), _indented_repr)
def test_check_ascii(self):
class UnicodeJSONRendererTests(TestCase):
"""
Tests specific for the Unicode JSON Renderer
"""
def test_proper_encoding(self):
obj = {'countries': ['United Kingdom', 'France', 'España']} obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = JSONRenderer() renderer = JSONRenderer()
content = renderer.render(obj, 'application/json') content = renderer.render(obj, 'application/json')
self.assertEqual(content, '{"countries": ["United Kingdom", "France", "Espa\\u00f1a"]}'.encode('utf-8')) self.assertEqual(content, '{"countries":["United Kingdom","France","España"]}'.encode('utf-8'))
class UnicodeJSONRendererTests(TestCase): class AsciiJSONRendererTests(TestCase):
""" """
Tests specific for the Unicode JSON Renderer Tests specific for the Unicode JSON Renderer
""" """
def test_proper_encoding(self): def test_proper_encoding(self):
class AsciiJSONRenderer(JSONRenderer):
ensure_ascii = True
obj = {'countries': ['United Kingdom', 'France', 'España']} obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = UnicodeJSONRenderer() renderer = AsciiJSONRenderer()
content = renderer.render(obj, 'application/json') 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\\u00f1a"]}'.encode('utf-8'))
class JSONPRendererTests(TestCase): class JSONPRendererTests(TestCase):
...@@ -487,13 +494,9 @@ if yaml: ...@@ -487,13 +494,9 @@ if yaml:
def assertYAMLContains(self, content, string): def assertYAMLContains(self, content, string):
self.assertTrue(string in content, '%r not in %r' % (string, content)) 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): def test_proper_encoding(self):
obj = {'countries': ['United Kingdom', 'France', 'España']} obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = UnicodeYAMLRenderer() renderer = YAMLRenderer()
content = renderer.render(obj, 'application/yaml') content = renderer.render(obj, 'application/yaml')
self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8')) self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8'))
......
...@@ -131,7 +131,7 @@ class TestMaxValueValidatorValidation(TestCase): ...@@ -131,7 +131,7 @@ class TestMaxValueValidatorValidation(TestCase):
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json') request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json')
view = UpdateMaxValueValidationModel().as_view() view = UpdateMaxValueValidationModel().as_view()
response = view(request, pk=obj.pk).render() 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) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment