diff options
| -rw-r--r-- | rest_framework/negotiation.py | 10 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 6 | ||||
| -rw-r--r-- | rest_framework/response.py | 18 | ||||
| -rw-r--r-- | rest_framework/settings.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/htmlrenderer.py | 10 | ||||
| -rw-r--r-- | rest_framework/tests/negotiation.py | 28 | ||||
| -rw-r--r-- | rest_framework/tests/response.py | 50 | ||||
| -rw-r--r-- | rest_framework/views.py | 16 | 
8 files changed, 60 insertions, 80 deletions
| diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index 668c4e5c..4d205c0e 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -58,17 +58,11 @@ class DefaultContentNegotiation(BaseContentNegotiation):                              _MediaType(media_type).precedence):                              # Eg client requests '*/*'                              # Accepted media type is 'application/json' -                            renderer_and_media_type = renderer, renderer.media_type +                            return renderer, renderer.media_type                          else:                              # Eg client requests 'application/json; indent=8'                              # Accepted media type is 'application/json; indent=8' -                            renderer_and_media_type = renderer, media_type -                        if renderer.charset: -                            charset = renderer.charset -                        else: -                            charset = self.__class__.settings.DEFAULT_CHARSET -                        retval = renderer_and_media_type + (charset,) -                        return retval +                            return renderer, media_type          raise exceptions.NotAcceptable(available_renderers=renderers) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 65d8b686..b91e3861 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -41,6 +41,7 @@ class BaseRenderer(object):      def render(self, data, accepted_media_type=None, renderer_context=None):          raise NotImplemented('Renderer class requires .render() to be implemented') +  class JSONRenderer(BaseRenderer):      """      Renderer which serializes to json. @@ -115,6 +116,7 @@ class XMLRenderer(BaseRenderer):      media_type = 'application/xml'      format = 'xml' +    charset = 'utf-8'      def render(self, data, accepted_media_type=None, renderer_context=None):          """ @@ -164,6 +166,7 @@ class YAMLRenderer(BaseRenderer):      media_type = 'application/yaml'      format = 'yaml'      encoder = encoders.SafeDumper +    charset = 'utf-8'      def render(self, data, accepted_media_type=None, renderer_context=None):          """ @@ -204,6 +207,7 @@ class TemplateHTMLRenderer(BaseRenderer):          '%(status_code)s.html',          'api_exception.html'      ] +    charset = 'utf-8'      def render(self, data, accepted_media_type=None, renderer_context=None):          """ @@ -275,6 +279,7 @@ class StaticHTMLRenderer(TemplateHTMLRenderer):      """      media_type = 'text/html'      format = 'html' +    charset = 'utf-8'      def render(self, data, accepted_media_type=None, renderer_context=None):          renderer_context = renderer_context or {} @@ -296,6 +301,7 @@ class BrowsableAPIRenderer(BaseRenderer):      media_type = 'text/html'      format = 'api'      template = 'rest_framework/api.html' +    charset = 'utf-8'      def get_default_renderer(self, view):          """ diff --git a/rest_framework/response.py b/rest_framework/response.py index 40372f22..32e74a45 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): +                 exception=False, charset=None):          """          Alters the init arguments slightly.          For example, drop 'template_name', and instead use 'data'. @@ -30,6 +30,7 @@ class Response(SimpleTemplateResponse):          self.data = data          self.template_name = template_name          self.exception = exception +        self.charset = charset          if headers:              for name, value in six.iteritems(headers): @@ -39,18 +40,21 @@ class Response(SimpleTemplateResponse):      def rendered_content(self):          renderer = getattr(self, 'accepted_renderer', None)          media_type = getattr(self, 'accepted_media_type', None) -        charset = getattr(self, 'charset', None)          context = getattr(self, 'renderer_context', None)          assert renderer, ".accepted_renderer not set on Response"          assert media_type, ".accepted_media_type not set on Response"          assert context, ".renderer_context not set on Response"          context['response'] = self -        if charset is not None: -            ct = "{0}; charset={1}".format(media_type, charset) + +        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: -            ct = media_type -        self['Content-Type'] = ct +            content_type = media_type +        self['Content-Type'] = content_type          return renderer.render(self.data, media_type, context)      @property @@ -71,4 +75,4 @@ class Response(SimpleTemplateResponse):          for key in ('accepted_renderer', 'renderer_context', 'data'):              if key in state:                  del state[key] -        return state
\ No newline at end of file +        return state diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 255a95e2..beb511ac 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -83,8 +83,6 @@ DEFAULTS = {      'FORMAT_SUFFIX_KWARG': 'format',      # Input and output formats -    'DEFAULT_CHARSET': None, -          'DATE_INPUT_FORMATS': (          ISO_8601,      ), diff --git a/rest_framework/tests/htmlrenderer.py b/rest_framework/tests/htmlrenderer.py index 8f2e2b5a..5d18a6e8 100644 --- a/rest_framework/tests/htmlrenderer.py +++ b/rest_framework/tests/htmlrenderer.py @@ -66,19 +66,19 @@ class TemplateHTMLRendererTests(TestCase):      def test_simple_html_view(self):          response = self.client.get('/')          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):          response = self.client.get('/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-Type'], 'text/html') +        self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')      def test_permission_denied_html_view(self):          response = self.client.get('/permission_denied')          self.assertEqual(response.status_code, status.HTTP_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): @@ -109,10 +109,10 @@ class TemplateHTMLRendererExceptionTests(TestCase):          response = self.client.get('/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-Type'], 'text/html') +        self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8')      def test_permission_denied_html_view_with_template(self):          response = self.client.get('/permission_denied')          self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)          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') diff --git a/rest_framework/tests/negotiation.py b/rest_framework/tests/negotiation.py index d7ef6470..7f84827f 100644 --- a/rest_framework/tests/negotiation.py +++ b/rest_framework/tests/negotiation.py @@ -12,15 +12,14 @@ factory = RequestFactory()  class MockJSONRenderer(BaseRenderer):      media_type = 'application/json' +  class MockHTMLRenderer(BaseRenderer):      media_type = 'text/html' +  class NoCharsetSpecifiedRenderer(BaseRenderer):      media_type = 'my/media' -class CharsetSpecifiedRenderer(BaseRenderer): -    media_type = 'my/media' -    charset = 'mycharset'  class TestAcceptedMediaType(TestCase):      def setUp(self): @@ -32,32 +31,15 @@ class TestAcceptedMediaType(TestCase):      def test_client_without_accept_use_renderer(self):          request = Request(factory.get('/')) -        accepted_renderer, accepted_media_type, charset = self.select_renderer(request) +        accepted_renderer, accepted_media_type = self.select_renderer(request)          self.assertEqual(accepted_media_type, 'application/json')      def test_client_underspecifies_accept_use_renderer(self):          request = Request(factory.get('/', HTTP_ACCEPT='*/*')) -        accepted_renderer, accepted_media_type, charset = self.select_renderer(request) +        accepted_renderer, accepted_media_type = self.select_renderer(request)          self.assertEqual(accepted_media_type, 'application/json')      def test_client_overspecifies_accept_use_client(self):          request = Request(factory.get('/', HTTP_ACCEPT='application/json; indent=8')) -        accepted_renderer, accepted_media_type, charset = self.select_renderer(request) +        accepted_renderer, accepted_media_type = self.select_renderer(request)          self.assertEqual(accepted_media_type, 'application/json; indent=8') -         -class TestCharset(TestCase): -    def setUp(self): -        self.renderers = [NoCharsetSpecifiedRenderer()] -        self.negotiator = DefaultContentNegotiation() -     -    def test_returns_none_if_no_charset_set(self): -        request = Request(factory.get('/')) -        renderers = [NoCharsetSpecifiedRenderer()] -        _, _, charset = self.negotiator.select_renderer(request, renderers) -        self.assertIsNone(charset) -     -    def test_returns_attribute_from_renderer_if_charset_is_set(self): -        request = Request(factory.get('/')) -        renderers = [CharsetSpecifiedRenderer()] -        _, _, charset =  self.negotiator.select_renderer(request, renderers) -        self.assertEquals(CharsetSpecifiedRenderer.charset, charset)
\ No newline at end of file diff --git a/rest_framework/tests/response.py b/rest_framework/tests/response.py index f2a1c635..8f1163e8 100644 --- a/rest_framework/tests/response.py +++ b/rest_framework/tests/response.py @@ -12,6 +12,7 @@ from rest_framework.renderers import (  from rest_framework.settings import api_settings  from rest_framework.compat import six +  class MockPickleRenderer(BaseRenderer):      media_type = 'application/pickle' @@ -19,6 +20,7 @@ class MockPickleRenderer(BaseRenderer):  class MockJsonRenderer(BaseRenderer):      media_type = 'application/json' +  class MockTextMediaRenderer(BaseRenderer):      media_type = 'text/html' @@ -44,6 +46,7 @@ class RendererB(BaseRenderer):      def render(self, data, media_type=None, renderer_context=None):          return RENDERER_B_SERIALIZER(data) +  class RendererC(RendererB):      media_type = 'mock/rendererc'      format = 'formatc' @@ -56,6 +59,14 @@ class MockView(APIView):      def get(self, request, **kwargs):          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):      renderer_classes = (BrowsableAPIRenderer, ) @@ -70,6 +81,7 @@ class HTMLView1(APIView):          return Response('text')  urlpatterns = patterns('', +    url(r'^setbyview$', MockViewSettingCharset.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()), @@ -178,43 +190,37 @@ class Issue122Tests(TestCase):          """          self.client.get('/html1') +  class Issue807Testts(TestCase):      """      Covers #807      """ -     +      urls = 'rest_framework.tests.response' -     +      def test_does_not_append_charset_by_default(self):          """ -        For backwards compatibility `REST_FRAMEWORK['DEFAULT_CHARSET']` defaults -        to None, so that all legacy code works as expected. +        Renderers don't include a charset unless set explicitly.          """          headers = {"HTTP_ACCEPT": RendererA.media_type}          resp = self.client.get('/', **headers) -        self.assertEquals(RendererA.media_type, resp['Content-Type']) -     +        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          """ -        resp = self.client.get('/?format=%s' % RendererC.format) +        headers = {"HTTP_ACCEPT": RendererC.media_type} +        resp = self.client.get('/', **headers)          expected = "{0}; charset={1}".format(RendererC.media_type, RendererC.charset) -        self.assertEquals(expected, resp['Content-Type']) -     -    def test_if_there_is_default_charset_specified_it_gets_appended(self): +        self.assertEqual(expected, resp['Content-Type']) + +    def test_charset_set_explictly_on_response(self):          """ -        If user defines `REST_FRAMEWORK['DEFAULT_CHARSET']` it will get appended -        to Content-Type of all responses. +        The charset may be set explictly on the response.          """ -        original_default_charset = api_settings.DEFAULT_CHARSET -        api_settings.DEFAULT_CHARSET = "utf-8" -        headers = {'HTTP_ACCEPT': RendererA.media_type} -        resp = self.client.get('/', **headers) -        expected = "{0}; charset={1}".format( -            RendererA.media_type, -            api_settings.DEFAULT_CHARSET -        ) -        self.assertEquals(expected, resp['Content-Type']) -        api_settings.DEFAULT_CHARSET = original_default_charset
\ No newline at end of file +        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']) diff --git a/rest_framework/views.py b/rest_framework/views.py index 035aa646..555fa2f4 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -183,9 +183,7 @@ class APIView(View):              return conneg.select_renderer(request, renderers, self.format_kwarg)          except Exception:              if force: -                charset = renderers[0].charset -                charset = charset if charset is not None else api_settings.DEFAULT_CHARSET -                return (renderers[0], renderers[0].media_type, renderers[0].charset) +                return (renderers[0], renderers[0].media_type)              raise      def perform_authentication(self, request): @@ -252,10 +250,7 @@ class APIView(View):          # Perform content negotiation and store the accepted info on the request          neg = self.perform_content_negotiation(request) -        renderer, media_type, charset = neg -        request.accepted_renderer = renderer -        request.accepted_media_type = media_type -        request.accepted_charset = charset +        request.accepted_renderer, request.accepted_media_type = neg      def finalize_response(self, request, response, *args, **kwargs):          """ @@ -270,16 +265,11 @@ class APIView(View):          if isinstance(response, Response):              if not getattr(request, 'accepted_renderer', None):                  neg = self.perform_content_negotiation(request, force=True) -                renderer, media_type, charset = neg -                request.accepted_renderer = renderer -                request.accepted_media_type = media_type  +                request.accepted_renderer, request.accepted_media_type = neg              response.accepted_renderer = request.accepted_renderer              response.accepted_media_type = request.accepted_media_type              response.renderer_context = self.get_renderer_context() -            charset = request.accepted_renderer.charset -            charset = charset if charset else api_settings.DEFAULT_CHARSET -            response.charset = charset          for key, value in self.headers.items():              response[key] = value | 
