mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-11 04:07:39 +03:00
Merge branch 'master' of https://github.com/tomchristie/django-rest-framework
This commit is contained in:
commit
bac4bf6e95
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__')
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue
Block a user