diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/fields.py | 31 | ||||
| -rw-r--r-- | rest_framework/mixins.py | 20 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 40 | ||||
| -rw-r--r-- | rest_framework/response.py | 22 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 6 | ||||
| -rw-r--r-- | rest_framework/static/rest_framework/css/bootstrap-tweaks.css | 4 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/form.html | 2 | ||||
| -rw-r--r-- | rest_framework/tests/fields.py | 15 | ||||
| -rw-r--r-- | rest_framework/tests/models.py | 10 | ||||
| -rw-r--r-- | rest_framework/tests/renderers.py | 36 | ||||
| -rw-r--r-- | rest_framework/tests/response.py | 88 | ||||
| -rw-r--r-- | rest_framework/tests/serializer.py | 44 |
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): |
