mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 08:14:16 +03:00
Merge branch 'master' of git://github.com/tomchristie/django-rest-framework into issue-192-expose-fields-for-options
This commit is contained in:
commit
9454e23aa9
|
@ -67,7 +67,7 @@ If your API includes views that can serve both regular webpages and API response
|
||||||
|
|
||||||
## JSONRenderer
|
## 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`.
|
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'`
|
**.format**: `'.json'`
|
||||||
|
|
||||||
|
## UnicodeJSONRenderer
|
||||||
|
|
||||||
|
Same as `JSONRenderer` but doesn't enforce ASCII encoding
|
||||||
|
|
||||||
## JSONPRenderer
|
## 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.
|
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,8 @@ The following people have helped make REST framework great.
|
||||||
* Òscar Vilaplana - [grimborg]
|
* Òscar Vilaplana - [grimborg]
|
||||||
* Ryan Kaskel - [ryankask]
|
* Ryan Kaskel - [ryankask]
|
||||||
* Andy McKay - [andymckay]
|
* Andy McKay - [andymckay]
|
||||||
|
* Matteo Suppo - [matteosuppo]
|
||||||
|
* Karol Majta - [lolek09]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
Many thanks to everyone who's contributed to the project.
|
||||||
|
|
||||||
|
@ -296,3 +298,5 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
[grimborg]: https://github.com/grimborg
|
[grimborg]: https://github.com/grimborg
|
||||||
[ryankask]: https://github.com/ryankask
|
[ryankask]: https://github.com/ryankask
|
||||||
[andymckay]: https://github.com/andymckay
|
[andymckay]: https://github.com/andymckay
|
||||||
|
[matteosuppo]: https://github.com/matteosuppo
|
||||||
|
[lolek09]: https://github.com/lolek09
|
||||||
|
|
|
@ -495,3 +495,16 @@ except ImportError:
|
||||||
oauth2_provider_forms = None
|
oauth2_provider_forms = None
|
||||||
oauth2_provider_scope = None
|
oauth2_provider_scope = None
|
||||||
oauth2_constants = 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__')
|
||||||
|
|
|
@ -27,7 +27,7 @@ from rest_framework.compat import (timezone, parse_date, parse_datetime,
|
||||||
parse_time)
|
parse_time)
|
||||||
from rest_framework.compat import BytesIO
|
from rest_framework.compat import BytesIO
|
||||||
from rest_framework.compat import six
|
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
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +76,6 @@ def is_simple_callable(obj):
|
||||||
len_defaults = len(defaults) if defaults else 0
|
len_defaults = len(defaults) if defaults else 0
|
||||||
return len_args <= len_defaults
|
return len_args <= len_defaults
|
||||||
|
|
||||||
|
|
||||||
def get_component(obj, attr_name):
|
def get_component(obj, attr_name):
|
||||||
"""
|
"""
|
||||||
Given an object, and an attribute name,
|
Given an object, and an attribute name,
|
||||||
|
@ -256,7 +255,8 @@ class Field(object):
|
||||||
|
|
||||||
if is_protected_type(value):
|
if is_protected_type(value):
|
||||||
return 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]
|
return [self.to_native(item) for item in value]
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
# Make sure we preserve field ordering, if it exists
|
# Make sure we preserve field ordering, if it exists
|
||||||
|
@ -264,7 +264,7 @@ class Field(object):
|
||||||
for key, val in value.items():
|
for key, val in value.items():
|
||||||
ret[key] = self.to_native(val)
|
ret[key] = self.to_native(val)
|
||||||
return ret
|
return ret
|
||||||
return smart_text(value)
|
return force_text(value)
|
||||||
|
|
||||||
def attributes(self):
|
def attributes(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -36,6 +36,7 @@ class BaseRenderer(object):
|
||||||
|
|
||||||
media_type = None
|
media_type = None
|
||||||
format = None
|
format = None
|
||||||
|
charset = None
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
raise NotImplemented('Renderer class requires .render() to be implemented')
|
raise NotImplemented('Renderer class requires .render() to be implemented')
|
||||||
|
@ -49,6 +50,7 @@ class JSONRenderer(BaseRenderer):
|
||||||
media_type = 'application/json'
|
media_type = 'application/json'
|
||||||
format = 'json'
|
format = 'json'
|
||||||
encoder_class = encoders.JSONEncoder
|
encoder_class = encoders.JSONEncoder
|
||||||
|
ensure_ascii = True
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
|
@ -72,7 +74,12 @@ class JSONRenderer(BaseRenderer):
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
indent = None
|
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
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
class JSONPRenderer(JSONRenderer):
|
class JSONPRenderer(JSONRenderer):
|
||||||
|
@ -115,6 +122,7 @@ class XMLRenderer(BaseRenderer):
|
||||||
|
|
||||||
media_type = 'application/xml'
|
media_type = 'application/xml'
|
||||||
format = 'xml'
|
format = 'xml'
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
|
@ -164,6 +172,7 @@ class YAMLRenderer(BaseRenderer):
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
format = 'yaml'
|
format = 'yaml'
|
||||||
encoder = encoders.SafeDumper
|
encoder = encoders.SafeDumper
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
|
@ -204,6 +213,7 @@ class TemplateHTMLRenderer(BaseRenderer):
|
||||||
'%(status_code)s.html',
|
'%(status_code)s.html',
|
||||||
'api_exception.html'
|
'api_exception.html'
|
||||||
]
|
]
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
"""
|
"""
|
||||||
|
@ -275,6 +285,7 @@ class StaticHTMLRenderer(TemplateHTMLRenderer):
|
||||||
"""
|
"""
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
format = 'html'
|
format = 'html'
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
renderer_context = renderer_context or {}
|
renderer_context = renderer_context or {}
|
||||||
|
@ -296,6 +307,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
format = 'api'
|
format = 'api'
|
||||||
template = 'rest_framework/api.html'
|
template = 'rest_framework/api.html'
|
||||||
|
charset = 'utf-8'
|
||||||
|
|
||||||
def get_default_renderer(self, view):
|
def get_default_renderer(self, view):
|
||||||
"""
|
"""
|
||||||
|
@ -321,7 +333,7 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
content = renderer.render(data, accepted_media_type, renderer_context)
|
content = renderer.render(data, accepted_media_type, renderer_context)
|
||||||
|
|
||||||
if not all(char in string.printable for char in content):
|
if not all(char in string.printable for char in content):
|
||||||
return '[%d bytes of binary content]'
|
return '[%d bytes of binary content]' % len(content)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
@ -337,6 +349,8 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
view.check_permissions(request)
|
view.check_permissions(request)
|
||||||
|
if obj is not None:
|
||||||
|
view.check_object_permissions(request, obj)
|
||||||
except exceptions.APIException:
|
except exceptions.APIException:
|
||||||
return False # Doesn't have permissions
|
return False # Doesn't have permissions
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Response(SimpleTemplateResponse):
|
||||||
|
|
||||||
def __init__(self, data=None, status=200,
|
def __init__(self, data=None, status=200,
|
||||||
template_name=None, headers=None,
|
template_name=None, headers=None,
|
||||||
exception=False):
|
exception=False, charset=None):
|
||||||
"""
|
"""
|
||||||
Alters the init arguments slightly.
|
Alters the init arguments slightly.
|
||||||
For example, drop 'template_name', and instead use 'data'.
|
For example, drop 'template_name', and instead use 'data'.
|
||||||
|
@ -30,6 +30,7 @@ class Response(SimpleTemplateResponse):
|
||||||
self.data = data
|
self.data = data
|
||||||
self.template_name = template_name
|
self.template_name = template_name
|
||||||
self.exception = exception
|
self.exception = exception
|
||||||
|
self.charset = charset
|
||||||
|
|
||||||
if headers:
|
if headers:
|
||||||
for name, value in six.iteritems(headers):
|
for name, value in six.iteritems(headers):
|
||||||
|
@ -46,7 +47,14 @@ class Response(SimpleTemplateResponse):
|
||||||
assert context, ".renderer_context not set on Response"
|
assert context, ".renderer_context not set on Response"
|
||||||
context['response'] = self
|
context['response'] = self
|
||||||
|
|
||||||
self['Content-Type'] = media_type
|
if self.charset is None:
|
||||||
|
self.charset = renderer.charset
|
||||||
|
|
||||||
|
if self.charset is not None:
|
||||||
|
content_type = "{0}; charset={1}".format(media_type, self.charset)
|
||||||
|
else:
|
||||||
|
content_type = media_type
|
||||||
|
self['Content-Type'] = content_type
|
||||||
return renderer.render(self.data, media_type, context)
|
return renderer.render(self.data, media_type, context)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -744,6 +744,11 @@ class ModelSerializer(Serializer):
|
||||||
kwargs['choices'] = model_field.flatchoices
|
kwargs['choices'] = model_field.flatchoices
|
||||||
return ChoiceField(**kwargs)
|
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 = {
|
attribute_dict = {
|
||||||
models.CharField: ['max_length'],
|
models.CharField: ['max_length'],
|
||||||
models.CommaSeparatedIntegerField: ['max_length'],
|
models.CommaSeparatedIntegerField: ['max_length'],
|
||||||
|
|
|
@ -66,19 +66,19 @@ class TemplateHTMLRendererTests(TestCase):
|
||||||
def test_simple_html_view(self):
|
def test_simple_html_view(self):
|
||||||
response = self.client.get('/')
|
response = self.client.get('/')
|
||||||
self.assertContains(response, "example: foobar")
|
self.assertContains(response, "example: foobar")
|
||||||
self.assertEqual(response['Content-Type'], 'text/html')
|
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
|
||||||
|
|
||||||
def test_not_found_html_view(self):
|
def test_not_found_html_view(self):
|
||||||
response = self.client.get('/not_found')
|
response = self.client.get('/not_found')
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
self.assertEqual(response.content, six.b("404 Not Found"))
|
self.assertEqual(response.content, six.b("404 Not Found"))
|
||||||
self.assertEqual(response['Content-Type'], 'text/html')
|
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
|
||||||
|
|
||||||
def test_permission_denied_html_view(self):
|
def test_permission_denied_html_view(self):
|
||||||
response = self.client.get('/permission_denied')
|
response = self.client.get('/permission_denied')
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertEqual(response.content, six.b("403 Forbidden"))
|
self.assertEqual(response.content, six.b("403 Forbidden"))
|
||||||
self.assertEqual(response['Content-Type'], 'text/html')
|
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
|
||||||
|
|
||||||
|
|
||||||
class TemplateHTMLRendererExceptionTests(TestCase):
|
class TemplateHTMLRendererExceptionTests(TestCase):
|
||||||
|
@ -109,10 +109,10 @@ class TemplateHTMLRendererExceptionTests(TestCase):
|
||||||
response = self.client.get('/not_found')
|
response = self.client.get('/not_found')
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
self.assertEqual(response.content, six.b("404: Not found"))
|
self.assertEqual(response.content, six.b("404: Not found"))
|
||||||
self.assertEqual(response['Content-Type'], 'text/html')
|
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
|
||||||
|
|
||||||
def test_permission_denied_html_view_with_template(self):
|
def test_permission_denied_html_view_with_template(self):
|
||||||
response = self.client.get('/permission_denied')
|
response = self.client.get('/permission_denied')
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertEqual(response.content, six.b("403: Permission denied"))
|
self.assertEqual(response.content, six.b("403: Permission denied"))
|
||||||
self.assertEqual(response['Content-Type'], 'text/html')
|
self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')
|
||||||
|
|
|
@ -3,19 +3,24 @@ from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from rest_framework.negotiation import DefaultContentNegotiation
|
from rest_framework.negotiation import DefaultContentNegotiation
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.renderers import BaseRenderer
|
||||||
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class MockJSONRenderer(object):
|
class MockJSONRenderer(BaseRenderer):
|
||||||
media_type = 'application/json'
|
media_type = 'application/json'
|
||||||
|
|
||||||
|
|
||||||
class MockHTMLRenderer(object):
|
class MockHTMLRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
|
|
||||||
|
|
||||||
|
class NoCharsetSpecifiedRenderer(BaseRenderer):
|
||||||
|
media_type = 'my/media'
|
||||||
|
|
||||||
|
|
||||||
class TestAcceptedMediaType(TestCase):
|
class TestAcceptedMediaType(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.renderers = [MockJSONRenderer(), MockHTMLRenderer()]
|
self.renderers = [MockJSONRenderer(), MockHTMLRenderer()]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
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.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
|
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer
|
||||||
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.compat import StringIO
|
from rest_framework.compat import StringIO
|
||||||
|
@ -254,6 +255,23 @@ 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), _indented_repr)
|
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):
|
class JSONPRendererTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,6 +21,9 @@ class MockJsonRenderer(BaseRenderer):
|
||||||
media_type = 'application/json'
|
media_type = 'application/json'
|
||||||
|
|
||||||
|
|
||||||
|
class MockTextMediaRenderer(BaseRenderer):
|
||||||
|
media_type = 'text/html'
|
||||||
|
|
||||||
DUMMYSTATUS = status.HTTP_200_OK
|
DUMMYSTATUS = status.HTTP_200_OK
|
||||||
DUMMYCONTENT = 'dummycontent'
|
DUMMYCONTENT = 'dummycontent'
|
||||||
|
|
||||||
|
@ -44,13 +47,26 @@ class RendererB(BaseRenderer):
|
||||||
return RENDERER_B_SERIALIZER(data)
|
return RENDERER_B_SERIALIZER(data)
|
||||||
|
|
||||||
|
|
||||||
|
class RendererC(RendererB):
|
||||||
|
media_type = 'mock/rendererc'
|
||||||
|
format = 'formatc'
|
||||||
|
charset = "rendererc"
|
||||||
|
|
||||||
|
|
||||||
class MockView(APIView):
|
class MockView(APIView):
|
||||||
renderer_classes = (RendererA, RendererB)
|
renderer_classes = (RendererA, RendererB, RendererC)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
return Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||||
|
|
||||||
|
|
||||||
|
class MockViewSettingCharset(APIView):
|
||||||
|
renderer_classes = (RendererA, RendererB, RendererC)
|
||||||
|
|
||||||
|
def get(self, request, **kwargs):
|
||||||
|
return Response(DUMMYCONTENT, status=DUMMYSTATUS, charset='setbyview')
|
||||||
|
|
||||||
|
|
||||||
class HTMLView(APIView):
|
class HTMLView(APIView):
|
||||||
renderer_classes = (BrowsableAPIRenderer, )
|
renderer_classes = (BrowsableAPIRenderer, )
|
||||||
|
|
||||||
|
@ -64,10 +80,10 @@ class HTMLView1(APIView):
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return Response('text')
|
return Response('text')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
url(r'^setbyview$', MockViewSettingCharset.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
|
||||||
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])),
|
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
|
||||||
|
url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
|
||||||
url(r'^html$', HTMLView.as_view()),
|
url(r'^html$', HTMLView.as_view()),
|
||||||
url(r'^html1$', HTMLView1.as_view()),
|
url(r'^html1$', HTMLView1.as_view()),
|
||||||
url(r'^restframework', include('rest_framework.urls', namespace='rest_framework'))
|
url(r'^restframework', include('rest_framework.urls', namespace='rest_framework'))
|
||||||
|
@ -173,3 +189,38 @@ class Issue122Tests(TestCase):
|
||||||
Test if no infinite recursion occurs.
|
Test if no infinite recursion occurs.
|
||||||
"""
|
"""
|
||||||
self.client.get('/html1')
|
self.client.get('/html1')
|
||||||
|
|
||||||
|
|
||||||
|
class Issue807Testts(TestCase):
|
||||||
|
"""
|
||||||
|
Covers #807
|
||||||
|
"""
|
||||||
|
|
||||||
|
urls = 'rest_framework.tests.response'
|
||||||
|
|
||||||
|
def test_does_not_append_charset_by_default(self):
|
||||||
|
"""
|
||||||
|
Renderers don't include a charset unless set explicitly.
|
||||||
|
"""
|
||||||
|
headers = {"HTTP_ACCEPT": RendererA.media_type}
|
||||||
|
resp = self.client.get('/', **headers)
|
||||||
|
self.assertEqual(RendererA.media_type, resp['Content-Type'])
|
||||||
|
|
||||||
|
def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self):
|
||||||
|
"""
|
||||||
|
If renderer class has charset attribute declared, it gets appended
|
||||||
|
to Response's Content-Type
|
||||||
|
"""
|
||||||
|
headers = {"HTTP_ACCEPT": RendererC.media_type}
|
||||||
|
resp = self.client.get('/', **headers)
|
||||||
|
expected = "{0}; charset={1}".format(RendererC.media_type, RendererC.charset)
|
||||||
|
self.assertEqual(expected, resp['Content-Type'])
|
||||||
|
|
||||||
|
def test_charset_set_explictly_on_response(self):
|
||||||
|
"""
|
||||||
|
The charset may be set explictly on the response.
|
||||||
|
"""
|
||||||
|
headers = {"HTTP_ACCEPT": RendererC.media_type}
|
||||||
|
resp = self.client.get('/setbyview', **headers)
|
||||||
|
expected = "{0}; charset={1}".format(RendererC.media_type, 'setbyview')
|
||||||
|
self.assertEqual(expected, resp['Content-Type'])
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields import BLANK_CHOICE_DASH
|
from django.db.models.fields import BLANK_CHOICE_DASH
|
||||||
from django.utils.datastructures import MultiValueDict
|
|
||||||
from django.test import TestCase
|
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 import serializers
|
||||||
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
|
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
|
||||||
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
|
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
|
||||||
|
@ -1323,6 +1324,34 @@ class DeserializeListTestCase(TestCase):
|
||||||
self.assertEqual(serializer.errors, expected)
|
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):
|
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1402,3 +1431,76 @@ class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
||||||
|
|
||||||
def test_url_field(self):
|
def test_url_field(self):
|
||||||
self.field_test('url_field')
|
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