aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/api-guide/renderers.md6
-rw-r--r--docs/topics/credits.md2
-rw-r--r--rest_framework/compat.py13
-rw-r--r--rest_framework/fields.py8
-rw-r--r--rest_framework/renderers.py9
-rw-r--r--rest_framework/serializers.py5
-rw-r--r--rest_framework/tests/renderers.py20
-rw-r--r--rest_framework/tests/serializer.py104
8 files changed, 158 insertions, 9 deletions
diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
index b9a9fd7a..1661ceec 100644
--- a/docs/api-guide/renderers.md
+++ b/docs/api-guide/renderers.md
@@ -67,7 +67,7 @@ If your API includes views that can serve both regular webpages and API response
## JSONRenderer
-Renders the request data into `JSON`.
+Renders the request data into `JSON` enforcing ASCII encoding
The client may additionally include an `'indent'` media type parameter, in which case the returned `JSON` will be indented. For example `Accept: application/json; indent=4`.
@@ -75,6 +75,10 @@ The client may additionally include an `'indent'` media type parameter, in which
**.format**: `'.json'`
+## UnicodeJSONRenderer
+
+Same as `JSONRenderer` but doesn't enforce ASCII encoding
+
## JSONPRenderer
Renders the request data into `JSONP`. The `JSONP` media type provides a mechanism of allowing cross-domain AJAX requests, by wrapping a `JSON` response in a javascript callback.
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index d805c0c1..0e206de8 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -130,6 +130,7 @@ The following people have helped make REST framework great.
* Òscar Vilaplana - [grimborg]
* Ryan Kaskel - [ryankask]
* Andy McKay - [andymckay]
+* Matteo Suppo - [matteosuppo]
Many thanks to everyone who's contributed to the project.
@@ -296,3 +297,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
[grimborg]: https://github.com/grimborg
[ryankask]: https://github.com/ryankask
[andymckay]: https://github.com/andymckay
+[matteosuppo]: https://github.com/matteosuppo
diff --git a/rest_framework/compat.py b/rest_framework/compat.py
index cd39f544..76dc0052 100644
--- a/rest_framework/compat.py
+++ b/rest_framework/compat.py
@@ -495,3 +495,16 @@ except ImportError:
oauth2_provider_forms = None
oauth2_provider_scope = None
oauth2_constants = None
+
+# Handle lazy strings
+from django.utils.functional import Promise
+
+if six.PY3:
+ def is_non_str_iterable(obj):
+ if (isinstance(obj, str) or
+ (isinstance(obj, Promise) and obj._delegate_text)):
+ return False
+ return hasattr(obj, '__iter__')
+else:
+ def is_non_str_iterable(obj):
+ return hasattr(obj, '__iter__')
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index fc14184c..b5f99823 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -26,7 +26,7 @@ from rest_framework import ISO_8601
from rest_framework.compat import timezone, parse_date, parse_datetime, parse_time
from rest_framework.compat import BytesIO
from rest_framework.compat import six
-from rest_framework.compat import smart_text
+from rest_framework.compat import smart_text, force_text, is_non_str_iterable
from rest_framework.settings import api_settings
@@ -45,7 +45,6 @@ def is_simple_callable(obj):
len_defaults = len(defaults) if defaults else 0
return len_args <= len_defaults
-
def get_component(obj, attr_name):
"""
Given an object, and an attribute name,
@@ -169,7 +168,8 @@ class Field(object):
if is_protected_type(value):
return value
- elif hasattr(value, '__iter__') and not isinstance(value, (dict, six.string_types)):
+ elif (is_non_str_iterable(value) and
+ not isinstance(value, (dict, six.string_types))):
return [self.to_native(item) for item in value]
elif isinstance(value, dict):
# Make sure we preserve field ordering, if it exists
@@ -177,7 +177,7 @@ class Field(object):
for key, val in value.items():
ret[key] = self.to_native(val)
return ret
- return smart_text(value)
+ return force_text(value)
def attributes(self):
"""
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index b91e3861..a67c5eea 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -50,6 +50,7 @@ class JSONRenderer(BaseRenderer):
media_type = 'application/json'
format = 'json'
encoder_class = encoders.JSONEncoder
+ ensure_ascii = True
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
@@ -73,7 +74,11 @@ class JSONRenderer(BaseRenderer):
except (ValueError, TypeError):
indent = None
- return json.dumps(data, cls=self.encoder_class, indent=indent)
+ return json.dumps(data, cls=self.encoder_class, indent=indent, ensure_ascii=self.ensure_ascii)
+
+
+class UnicodeJSONRenderer(JSONRenderer):
+ ensure_ascii = False
class JSONPRenderer(JSONRenderer):
@@ -326,7 +331,7 @@ class BrowsableAPIRenderer(BaseRenderer):
renderer_context['indent'] = 4
content = renderer.render(data, accepted_media_type, renderer_context)
- if not all(char in string.printable for char in content):
+ if not isinstance(content, six.text_type):
return '[%d bytes of binary content]'
return content
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index ff5eb873..943fba6b 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -744,6 +744,11 @@ class ModelSerializer(Serializer):
kwargs['choices'] = model_field.flatchoices
return ChoiceField(**kwargs)
+ # put this below the ChoiceField because min_value isn't a valid initializer
+ if issubclass(model_field.__class__, models.PositiveIntegerField) or\
+ issubclass(model_field.__class__, models.PositiveSmallIntegerField):
+ kwargs['min_value'] = 0
+
attribute_dict = {
models.CharField: ['max_length'],
models.CommaSeparatedIntegerField: ['max_length'],
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/renderers.py
index 40bac9cb..739f9184 100644
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
from decimal import Decimal
from django.core.cache import cache
from django.test import TestCase
@@ -8,7 +9,7 @@ from rest_framework.compat import yaml, etree, patterns, url, include
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
- XMLRenderer, JSONPRenderer, BrowsableAPIRenderer
+ XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer
from rest_framework.parsers import YAMLParser, XMLParser
from rest_framework.settings import api_settings
from rest_framework.compat import StringIO
@@ -254,6 +255,23 @@ class JSONRendererTests(TestCase):
content = renderer.render(obj, 'application/json; indent=2')
self.assertEqual(strip_trailing_whitespace(content), _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"]}')
+
+
+class UnicodeJSONRendererTests(TestCase):
+ """
+ Tests specific for the Unicode JSON Renderer
+ """
+ def test_proper_encoding(self):
+ obj = {'countries': ['United Kingdom', 'France', 'España']}
+ renderer = UnicodeJSONRenderer()
+ content = renderer.render(obj, 'application/json')
+ self.assertEqual(content, '{"countries": ["United Kingdom", "France", "España"]}')
+
class JSONPRendererTests(TestCase):
"""
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index 4f188c3e..fd6cf6da 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -1,8 +1,9 @@
from __future__ import unicode_literals
from django.db import models
from django.db.models.fields import BLANK_CHOICE_DASH
-from django.utils.datastructures import MultiValueDict
from django.test import TestCase
+from django.utils.datastructures import MultiValueDict
+from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
@@ -1323,6 +1324,34 @@ class DeserializeListTestCase(TestCase):
self.assertEqual(serializer.errors, expected)
+# test for issue 747
+
+
+class LazyStringModel(object):
+ def __init__(self, lazystring):
+ self.lazystring = lazystring
+
+
+class LazyStringSerializer(serializers.Serializer):
+ lazystring = serializers.Field()
+
+ def restore_object(self, attrs, instance=None):
+ if instance is not None:
+ instance.lazystring = attrs.get('lazystring', instance.lazystring)
+ return instance
+ return LazyStringModel(**attrs)
+
+
+class LazyStringsTestCase(TestCase):
+ def setUp(self):
+ self.model = LazyStringModel(lazystring=_('lazystring'))
+
+ def test_lazy_strings_are_translated(self):
+ serializer = LazyStringSerializer(self.model)
+ self.assertEqual(type(serializer.data['lazystring']),
+ type('lazystring'))
+
+
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def setUp(self):
@@ -1402,3 +1431,76 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def test_url_field(self):
self.field_test('url_field')
+
+
+class DefaultValuesOnAutogeneratedFieldsTests(TestCase):
+
+ def setUp(self):
+ class DVOAFModel(RESTFrameworkModel):
+ positive_integer_field = models.PositiveIntegerField(blank=True)
+ positive_small_integer_field = models.PositiveSmallIntegerField(blank=True)
+ email_field = models.EmailField(blank=True)
+ file_field = models.FileField(blank=True)
+ image_field = models.ImageField(blank=True)
+ slug_field = models.SlugField(blank=True)
+ url_field = models.URLField(blank=True)
+
+ class DVOAFSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = DVOAFModel
+
+ self.serializer_class = DVOAFSerializer
+ self.fields_attributes = {
+ 'positive_integer_field': [
+ ('min_value', 0),
+ ],
+ 'positive_small_integer_field': [
+ ('min_value', 0),
+ ],
+ 'email_field': [
+ ('max_length', 75),
+ ],
+ 'file_field': [
+ ('max_length', 100),
+ ],
+ 'image_field': [
+ ('max_length', 100),
+ ],
+ 'slug_field': [
+ ('max_length', 50),
+ ],
+ 'url_field': [
+ ('max_length', 200),
+ ],
+ }
+
+ def field_test(self, field):
+ serializer = self.serializer_class(data={})
+ self.assertEqual(serializer.is_valid(), True)
+
+ for attribute in self.fields_attributes[field]:
+ self.assertEqual(
+ getattr(serializer.fields[field], attribute[0]),
+ attribute[1]
+ )
+
+ def test_positive_integer_field(self):
+ self.field_test('positive_integer_field')
+
+ def test_positive_small_integer_field(self):
+ self.field_test('positive_small_integer_field')
+
+ def test_email_field(self):
+ self.field_test('email_field')
+
+ def test_file_field(self):
+ self.field_test('file_field')
+
+ def test_image_field(self):
+ self.field_test('image_field')
+
+ def test_slug_field(self):
+ self.field_test('slug_field')
+
+ def test_url_field(self):
+ self.field_test('url_field')