aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework')
-rw-r--r--rest_framework/fields.py31
-rw-r--r--rest_framework/mixins.py20
-rw-r--r--rest_framework/renderers.py40
-rw-r--r--rest_framework/response.py22
-rw-r--r--rest_framework/serializers.py6
-rw-r--r--rest_framework/static/rest_framework/css/bootstrap-tweaks.css4
-rw-r--r--rest_framework/templates/rest_framework/form.html2
-rw-r--r--rest_framework/tests/fields.py15
-rw-r--r--rest_framework/tests/models.py10
-rw-r--r--rest_framework/tests/renderers.py36
-rw-r--r--rest_framework/tests/response.py88
-rw-r--r--rest_framework/tests/serializer.py44
12 files changed, 238 insertions, 80 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
index d5a1394d..cb5f9a40 100644
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -123,7 +123,7 @@ class Field(object):
use_files = False
form_field_class = forms.CharField
- def __init__(self, source=None):
+ def __init__(self, source=None, label=None, help_text=None):
self.parent = None
self.creation_counter = Field.creation_counter
@@ -131,6 +131,12 @@ class Field(object):
self.source = source
+ if label is not None:
+ self.label = smart_text(label)
+
+ if help_text is not None:
+ self.help_text = smart_text(help_text)
+
def initialize(self, parent, field_name):
"""
Called to set up a field prior to field_to_native or field_from_native.
@@ -227,7 +233,8 @@ class WritableField(Field):
widget = widgets.TextInput
default = None
- def __init__(self, source=None, read_only=False, required=None,
+ def __init__(self, source=None, label=None, help_text=None,
+ read_only=False, required=None,
validators=[], error_messages=None, widget=None,
default=None, blank=None):
@@ -238,7 +245,7 @@ class WritableField(Field):
DeprecationWarning, stacklevel=2)
required = not(blank)
- super(WritableField, self).__init__(source=source)
+ super(WritableField, self).__init__(source=source, label=label, help_text=help_text)
self.read_only = read_only
if required is None:
@@ -416,11 +423,25 @@ class URLField(CharField):
class SlugField(CharField):
type_name = 'SlugField'
-
+ form_field_class = forms.SlugField
+
+ default_error_messages = {
+ 'invalid': _("Enter a valid 'slug' consisting of letters, numbers,"
+ " underscores or hyphens."),
+ }
+ default_validators = [validators.validate_slug]
+
def __init__(self, *args, **kwargs):
super(SlugField, self).__init__(*args, **kwargs)
-
+ def __deepcopy__(self, memo):
+ result = copy.copy(self)
+ memo[id(self)] = result
+ #result.widget = copy.deepcopy(self.widget, memo)
+ result.validators = self.validators[:]
+ return result
+
+
class ChoiceField(WritableField):
type_name = 'ChoiceField'
form_field_class = forms.ChoiceField
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
index f3cd5868..f11def6d 100644
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -110,16 +110,6 @@ class UpdateModelMixin(object):
"""
Update a model instance.
"""
- def get_object_or_none(self):
- try:
- return self.get_object()
- except Http404:
- # If this is a PUT-as-create operation, we need to ensure that
- # we have relevant permissions, as if this was a POST request.
- # This will either raise a PermissionDenied exception,
- # or simply return None
- self.check_permissions(clone_request(self.request, 'POST'))
-
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
@@ -148,6 +138,16 @@ class UpdateModelMixin(object):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
+ def get_object_or_none(self):
+ try:
+ return self.get_object()
+ except Http404:
+ # If this is a PUT-as-create operation, we need to ensure that
+ # we have relevant permissions, as if this was a POST request.
+ # This will either raise a PermissionDenied exception,
+ # or simply return None
+ self.check_permissions(clone_request(self.request, 'POST'))
+
def pre_save(self, obj):
"""
Set any attributes on the object that are implicit in the request.
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
index c67c8ed6..c42b086f 100644
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -9,7 +9,6 @@ REST framework also provides an HTML renderer the renders the browsable API.
from __future__ import unicode_literals
import copy
-import string
import json
from django import forms
from django.http.multipartparser import parse_header
@@ -36,7 +35,7 @@ class BaseRenderer(object):
media_type = None
format = None
- charset = None
+ charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
raise NotImplemented('Renderer class requires .render() to be implemented')
@@ -44,17 +43,21 @@ class BaseRenderer(object):
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'
format = 'json'
encoder_class = encoders.JSONEncoder
ensure_ascii = True
+ charset = 'utf-8'
+ # Note that JSON encodings must be utf-8, utf-16 or utf-32.
+ # See: http://www.ietf.org/rfc/rfc4627.txt
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
- Render `obj` into json.
+ Render `data` into JSON.
"""
if data is None:
return ''
@@ -74,12 +77,25 @@ class JSONRenderer(BaseRenderer):
except (ValueError, TypeError):
indent = None
- return json.dumps(data, cls=self.encoder_class, indent=indent, ensure_ascii=self.ensure_ascii)
+ ret = json.dumps(data, cls=self.encoder_class,
+ indent=indent, ensure_ascii=self.ensure_ascii)
+
+ # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
+ # but if ensure_ascii=False, the return type is underspecified,
+ # and may (or may not) be unicode.
+ # On python 3.x json.dumps() returns unicode strings.
+ if isinstance(ret, six.text_type):
+ return bytes(ret.encode(self.charset))
+ return ret
class UnicodeJSONRenderer(JSONRenderer):
ensure_ascii = False
charset = 'utf-8'
+ """
+ Renderer which serializes to JSON.
+ Does *not* apply JSON's character escaping for non-ascii characters.
+ """
class JSONPRenderer(JSONRenderer):
@@ -112,7 +128,7 @@ class JSONPRenderer(JSONRenderer):
callback = self.get_callback(renderer_context)
json = super(JSONPRenderer, self).render(data, accepted_media_type,
renderer_context)
- return "%s(%s);" % (callback, json)
+ return callback.encode(self.charset) + b'(' + json + b');'
class XMLRenderer(BaseRenderer):
@@ -133,7 +149,7 @@ class XMLRenderer(BaseRenderer):
stream = StringIO()
- xml = SimplerXMLGenerator(stream, "utf-8")
+ xml = SimplerXMLGenerator(stream, self.charset)
xml.startDocument()
xml.startElement("root", {})
@@ -183,7 +199,7 @@ class YAMLRenderer(BaseRenderer):
if data is None:
return ''
- return yaml.dump(data, stream=None, Dumper=self.encoder)
+ return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder)
class TemplateHTMLRenderer(BaseRenderer):
@@ -332,7 +348,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 renderer.charset is None:
return '[%d bytes of binary content]' % len(content)
return content
@@ -380,7 +396,11 @@ class BrowsableAPIRenderer(BaseRenderer):
if getattr(v, 'default', None) is not None:
kwargs['initial'] = v.default
- kwargs['label'] = k
+ if getattr(v, 'label', None) is not None:
+ kwargs['label'] = v.label
+
+ if getattr(v, 'help_text', None) is not None:
+ kwargs['help_text'] = v.help_text
fields[k] = v.form_field_class(**kwargs)
diff --git a/rest_framework/response.py b/rest_framework/response.py
index 32e74a45..110ccb13 100644
--- a/rest_framework/response.py
+++ b/rest_framework/response.py
@@ -18,7 +18,7 @@ class Response(SimpleTemplateResponse):
def __init__(self, data=None, status=200,
template_name=None, headers=None,
- exception=False, charset=None):
+ exception=False, content_type=None):
"""
Alters the init arguments slightly.
For example, drop 'template_name', and instead use 'data'.
@@ -30,7 +30,7 @@ class Response(SimpleTemplateResponse):
self.data = data
self.template_name = template_name
self.exception = exception
- self.charset = charset
+ self.content_type = content_type
if headers:
for name, value in six.iteritems(headers):
@@ -47,15 +47,21 @@ class Response(SimpleTemplateResponse):
assert context, ".renderer_context not set on Response"
context['response'] = self
- if self.charset is None:
- self.charset = renderer.charset
+ charset = renderer.charset
+ content_type = self.content_type
- if self.charset is not None:
- content_type = "{0}; charset={1}".format(media_type, self.charset)
- else:
+ if content_type is None and charset is not None:
+ content_type = "{0}; charset={1}".format(media_type, charset)
+ elif content_type is None:
content_type = media_type
self['Content-Type'] = content_type
- return renderer.render(self.data, media_type, context)
+
+ ret = renderer.render(self.data, media_type, context)
+ if isinstance(ret, six.text_type):
+ assert charset, 'renderer returned unicode, and did not specify ' \
+ 'a charset value.'
+ return bytes(ret.encode(charset))
+ return ret
@property
def status_text(self):
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 072815df..17da8c25 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -746,6 +746,12 @@ class ModelSerializer(Serializer):
if issubclass(model_field.__class__, models.TextField):
kwargs['widget'] = widgets.Textarea
+ if model_field.verbose_name is not None:
+ kwargs['label'] = model_field.verbose_name
+
+ if model_field.help_text is not None:
+ kwargs['help_text'] = model_field.help_text
+
# TODO: TypedChoiceField?
if model_field.flatchoices: # This ModelField contains choices
kwargs['choices'] = model_field.flatchoices
diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
index 9b520156..6bfb778c 100644
--- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
+++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css
@@ -104,6 +104,10 @@ html, body {
margin-bottom: 0;
}
+.well form .help-block {
+ color: #999;
+}
+
.nav-tabs {
border: 0;
}
diff --git a/rest_framework/templates/rest_framework/form.html b/rest_framework/templates/rest_framework/form.html
index dc7acc70..b27f652e 100644
--- a/rest_framework/templates/rest_framework/form.html
+++ b/rest_framework/templates/rest_framework/form.html
@@ -6,7 +6,7 @@
{{ field.label_tag|add_class:"control-label" }}
<div class="controls">
{{ field }}
- <span class="help-inline">{{ field.help_text }}</span>
+ <span class="help-block">{{ field.help_text }}</span>
<!--{{ field.errors|add_class:"help-block" }}-->
</div>
</div>
diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py
index fd1fe961..22c515a9 100644
--- a/rest_framework/tests/fields.py
+++ b/rest_framework/tests/fields.py
@@ -778,6 +778,21 @@ class SlugFieldTests(TestCase):
self.assertEqual(getattr(serializer.fields['slug_field'],
'max_length'), 20)
+ def test_invalid_slug(self):
+ """
+ Make sure an invalid slug raises ValidationError
+ """
+ class SlugFieldSerializer(serializers.ModelSerializer):
+ slug_field = serializers.SlugField(source='slug_field', max_length=20, required=True)
+
+ class Meta:
+ model = self.SlugFieldModel
+
+ s = SlugFieldSerializer(data={'slug_field': 'a b'})
+
+ self.assertEqual(s.is_valid(), False)
+ self.assertEqual(s.errors, {'slug_field': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]})
+
class URLFieldTests(TestCase):
"""
diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py
index 40e41a64..abf50a2d 100644
--- a/rest_framework/tests/models.py
+++ b/rest_framework/tests/models.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from rest_framework import serializers
def foobar():
@@ -32,7 +34,7 @@ class Anchor(RESTFrameworkModel):
class BasicModel(RESTFrameworkModel):
- text = models.CharField(max_length=100)
+ text = models.CharField(max_length=100, verbose_name=_("Text comes here"), help_text=_("Text description."))
class SlugBasedModel(RESTFrameworkModel):
@@ -159,3 +161,9 @@ class NullableOneToOneSource(RESTFrameworkModel):
name = models.CharField(max_length=100)
target = models.OneToOneField(OneToOneTarget, null=True, blank=True,
related_name='nullable_source')
+
+# Serializer used to test BasicModel
+class BasicModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = BasicModel
+
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/renderers.py
index 739f9184..9096c82d 100644
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from decimal import Decimal
from django.core.cache import cache
from django.test import TestCase
@@ -27,7 +29,7 @@ RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii')
expected_results = [
- ((elem for elem in [1, 2, 3]), JSONRenderer, '[1, 2, 3]') # Generator
+ ((elem for elem in [1, 2, 3]), JSONRenderer, b'[1, 2, 3]') # Generator
]
@@ -135,7 +137,7 @@ class RendererEndToEndTests(TestCase):
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
resp = self.client.get('/')
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -143,13 +145,13 @@ class RendererEndToEndTests(TestCase):
"""No response must be included in HEAD requests."""
resp = self.client.head('/')
self.assertEqual(resp.status_code, DUMMYSTATUS)
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, six.b(''))
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -157,7 +159,7 @@ class RendererEndToEndTests(TestCase):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for the default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -165,7 +167,7 @@ class RendererEndToEndTests(TestCase):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for a non-default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -176,7 +178,7 @@ class RendererEndToEndTests(TestCase):
RendererB.media_type
)
resp = self.client.get('/' + param)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -193,7 +195,7 @@ class RendererEndToEndTests(TestCase):
RendererB.format
)
resp = self.client.get('/' + param)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -201,7 +203,7 @@ class RendererEndToEndTests(TestCase):
"""If a 'format' keyword arg is specified, the renderer with the matching
format attribute should serialize the response."""
resp = self.client.get('/something.formatb')
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -214,7 +216,7 @@ class RendererEndToEndTests(TestCase):
)
resp = self.client.get('/' + param,
HTTP_ACCEPT=RendererB.media_type)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -244,7 +246,7 @@ class JSONRendererTests(TestCase):
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json')
# Fix failing test case which depends on version of JSON library.
- self.assertEqual(content, _flat_repr)
+ self.assertEqual(content.decode('utf-8'), _flat_repr)
def test_with_content_type_args(self):
"""
@@ -253,13 +255,13 @@ class JSONRendererTests(TestCase):
obj = {'foo': ['bar', 'baz']}
renderer = JSONRenderer()
content = renderer.render(obj, 'application/json; indent=2')
- self.assertEqual(strip_trailing_whitespace(content), _indented_repr)
+ 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"]}')
+ self.assertEqual(content, '{"countries": ["United Kingdom", "France", "Espa\\u00f1a"]}'.encode('utf-8'))
class UnicodeJSONRendererTests(TestCase):
@@ -270,7 +272,7 @@ class UnicodeJSONRendererTests(TestCase):
obj = {'countries': ['United Kingdom', 'France', 'España']}
renderer = UnicodeJSONRenderer()
content = renderer.render(obj, 'application/json')
- self.assertEqual(content, '{"countries": ["United Kingdom", "France", "España"]}')
+ self.assertEqual(content, '{"countries": ["United Kingdom", "France", "España"]}'.encode('utf-8'))
class JSONPRendererTests(TestCase):
@@ -287,7 +289,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/jsonrenderer',
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('callback(%s);' % _flat_repr).encode('ascii'))
@@ -298,7 +300,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/nojsonrenderer',
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('callback(%s);' % _flat_repr).encode('ascii'))
@@ -310,7 +312,7 @@ class JSONPRendererTests(TestCase):
resp = self.client.get('/jsonp/nojsonrenderer?callback=' + callback_func,
HTTP_ACCEPT='application/javascript')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
- self.assertEqual(resp['Content-Type'], 'application/javascript')
+ self.assertEqual(resp['Content-Type'], 'application/javascript; charset=utf-8')
self.assertEqual(resp.content,
('%s(%s);' % (callback_func, _flat_repr)).encode('ascii'))
diff --git a/rest_framework/tests/response.py b/rest_framework/tests/response.py
index 8f1163e8..4e04ac5c 100644
--- a/rest_framework/tests/response.py
+++ b/rest_framework/tests/response.py
@@ -1,14 +1,18 @@
from __future__ import unicode_literals
from django.test import TestCase
+from rest_framework.tests.models import BasicModel, BasicModelSerializer
from rest_framework.compat import patterns, url, include
from rest_framework.response import Response
from rest_framework.views import APIView
+from rest_framework import generics
+from rest_framework import routers
from rest_framework import status
from rest_framework.renderers import (
BaseRenderer,
JSONRenderer,
BrowsableAPIRenderer
)
+from rest_framework import viewsets
from rest_framework.settings import api_settings
from rest_framework.compat import six
@@ -60,11 +64,11 @@ class MockView(APIView):
return Response(DUMMYCONTENT, status=DUMMYSTATUS)
-class MockViewSettingCharset(APIView):
+class MockViewSettingContentType(APIView):
renderer_classes = (RendererA, RendererB, RendererC)
def get(self, request, **kwargs):
- return Response(DUMMYCONTENT, status=DUMMYSTATUS, charset='setbyview')
+ return Response(DUMMYCONTENT, status=DUMMYSTATUS, content_type='setbyview')
class HTMLView(APIView):
@@ -80,12 +84,30 @@ class HTMLView1(APIView):
def get(self, request, **kwargs):
return Response('text')
+
+class HTMLNewModelViewSet(viewsets.ModelViewSet):
+ model = BasicModel
+
+
+class HTMLNewModelView(generics.ListCreateAPIView):
+ renderer_classes = (BrowsableAPIRenderer,)
+ permission_classes = []
+ serializer_class = BasicModelSerializer
+ model = BasicModel
+
+
+new_model_viewset_router = routers.DefaultRouter()
+new_model_viewset_router.register(r'', HTMLNewModelViewSet)
+
+
urlpatterns = patterns('',
- url(r'^setbyview$', MockViewSettingCharset.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
+ url(r'^setbyview$', MockViewSettingContentType.as_view(renderer_classes=[RendererA, RendererB, RendererC])),
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'^html1$', HTMLView1.as_view()),
+ url(r'^html_new_model$', HTMLNewModelView.as_view()),
+ url(r'^html_new_model_viewset', include(new_model_viewset_router.urls)),
url(r'^restframework', include('rest_framework.urls', namespace='rest_framework'))
)
@@ -101,7 +123,7 @@ class RendererIntegrationTests(TestCase):
def test_default_renderer_serializes_content(self):
"""If the Accept header is not set the default renderer should serialize the response."""
resp = self.client.get('/')
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -109,13 +131,13 @@ class RendererIntegrationTests(TestCase):
"""No response must be included in HEAD requests."""
resp = self.client.head('/')
self.assertEqual(resp.status_code, DUMMYSTATUS)
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, six.b(''))
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -123,7 +145,7 @@ class RendererIntegrationTests(TestCase):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for the default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
- self.assertEqual(resp['Content-Type'], RendererA.media_type)
+ self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -131,7 +153,7 @@ class RendererIntegrationTests(TestCase):
"""If the Accept header is set the specified renderer should serialize the response.
(In this case we check that works for a non-default renderer)"""
resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -142,7 +164,7 @@ class RendererIntegrationTests(TestCase):
RendererB.media_type
)
resp = self.client.get('/' + param)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -150,7 +172,7 @@ class RendererIntegrationTests(TestCase):
"""If a 'format' query is specified, the renderer with the matching
format attribute should serialize the response."""
resp = self.client.get('/?format=%s' % RendererB.format)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -158,7 +180,7 @@ class RendererIntegrationTests(TestCase):
"""If a 'format' keyword arg is specified, the renderer with the matching
format attribute should serialize the response."""
resp = self.client.get('/something.formatb')
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -167,7 +189,7 @@ class RendererIntegrationTests(TestCase):
the renderer with the matching format attribute should serialize the response."""
resp = self.client.get('/?format=%s' % RendererB.format,
HTTP_ACCEPT=RendererB.media_type)
- self.assertEqual(resp['Content-Type'], RendererB.media_type)
+ self.assertEqual(resp['Content-Type'], RendererB.media_type + '; charset=utf-8')
self.assertEqual(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
self.assertEqual(resp.status_code, DUMMYSTATUS)
@@ -191,7 +213,21 @@ class Issue122Tests(TestCase):
self.client.get('/html1')
-class Issue807Testts(TestCase):
+class Issue467Tests(TestCase):
+ """
+ Tests for #467
+ """
+
+ urls = 'rest_framework.tests.response'
+
+ def test_form_has_label_and_help_text(self):
+ resp = self.client.get('/html_new_model')
+ self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
+ self.assertContains(resp, 'Text comes here')
+ self.assertContains(resp, 'Text description.')
+
+
+class Issue807Tests(TestCase):
"""
Covers #807
"""
@@ -204,7 +240,8 @@ class Issue807Testts(TestCase):
"""
headers = {"HTTP_ACCEPT": RendererA.media_type}
resp = self.client.get('/', **headers)
- self.assertEqual(RendererA.media_type, resp['Content-Type'])
+ expected = "{0}; charset={1}".format(RendererA.media_type, 'utf-8')
+ self.assertEqual(expected, resp['Content-Type'])
def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self):
"""
@@ -216,11 +253,26 @@ class Issue807Testts(TestCase):
expected = "{0}; charset={1}".format(RendererC.media_type, RendererC.charset)
self.assertEqual(expected, resp['Content-Type'])
- def test_charset_set_explictly_on_response(self):
+ def test_content_type_set_explictly_on_response(self):
"""
- The charset may be set explictly on the response.
+ The content type 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'])
+ self.assertEqual('setbyview', resp['Content-Type'])
+
+ def test_viewset_label_help_text(self):
+ param = '?%s=%s' % (
+ api_settings.URL_ACCEPT_OVERRIDE,
+ 'text/html'
+ )
+ resp = self.client.get('/html_new_model_viewset/' + param)
+ self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
+ self.assertContains(resp, 'Text comes here')
+ self.assertContains(resp, 'Text description.')
+
+ def test_form_has_label_and_help_text(self):
+ resp = self.client.get('/html_new_model')
+ self.assertEqual(resp['Content-Type'], 'text/html; charset=utf-8')
+ self.assertContains(resp, 'Text comes here')
+ self.assertContains(resp, 'Text description.')
diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py
index fd6cf6da..f2c31872 100644
--- a/rest_framework/tests/serializer.py
+++ b/rest_framework/tests/serializer.py
@@ -4,10 +4,11 @@ from django.db.models.fields import BLANK_CHOICE_DASH
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, fields, relations
from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel,
BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel,
ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel)
+from rest_framework.tests.models import BasicModelSerializer
import datetime
import pickle
@@ -128,11 +129,6 @@ class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer):
fields = ['some_integer']
-class BrokenModelSerializer(serializers.ModelSerializer):
- class Meta:
- fields = ['some_field']
-
-
class BasicTests(TestCase):
def setUp(self):
self.comment = Comment(
@@ -423,8 +419,12 @@ class ValidationTests(TestCase):
Assert that a meaningful exception message is outputted when the model
field is missing (e.g. when mistyping ``model``).
"""
+ class BrokenModelSerializer(serializers.ModelSerializer):
+ class Meta:
+ fields = ['some_field']
+
try:
- serializer = BrokenModelSerializer()
+ BrokenModelSerializer()
except AssertionError as e:
self.assertEqual(e.args[0], "Serializer class 'BrokenModelSerializer' is missing 'model' Meta option")
except:
@@ -446,7 +446,7 @@ class CustomValidationTests(TestCase):
class CommentSerializerWithFieldValidator(CommentSerializer):
def validate_email(self, attrs, source):
- value = attrs[source]
+ attrs[source]
return attrs
def validate_content(self, attrs, source):
@@ -1324,8 +1324,7 @@ class DeserializeListTestCase(TestCase):
self.assertEqual(serializer.errors, expected)
-# test for issue 747
-
+# Test for issue 747
class LazyStringModel(object):
def __init__(self, lazystring):
@@ -1352,6 +1351,31 @@ class LazyStringsTestCase(TestCase):
type('lazystring'))
+# Test for issue #467
+
+class FieldLabelTest(TestCase):
+ def setUp(self):
+ self.serializer_class = BasicModelSerializer
+
+ def test_label_from_model(self):
+ """
+ Validates that label and help_text are correctly copied from the model class.
+ """
+ serializer = self.serializer_class()
+ text_field = serializer.fields['text']
+
+ self.assertEqual('Text comes here', text_field.label)
+ self.assertEqual('Text description.', text_field.help_text)
+
+ def test_field_ctor(self):
+ """
+ This is check that ctor supports both label and help_text.
+ """
+ self.assertEqual('Label', fields.Field(label='Label', help_text='Help').label)
+ self.assertEqual('Help', fields.CharField(label='Label', help_text='Help').help_text)
+ self.assertEqual('Label', relations.HyperlinkedRelatedField(view_name='fake', label='Label', help_text='Help', many=True).label)
+
+
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
def setUp(self):