aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2013-05-18 17:21:43 +0100
committerTom Christie2013-05-18 17:21:43 +0100
commit0d3d66cb0232e1067600ef22fcf88937ac6bee9d (patch)
treee39f1750d919026607ba2d99610207e5fecb2e37
parent5bebd29f11dd9268b9a23c27cf58c8440664f5e9 (diff)
downloaddjango-rest-framework-0d3d66cb0232e1067600ef22fcf88937ac6bee9d.tar.bz2
Added proper charset support
-rw-r--r--rest_framework/negotiation.py10
-rw-r--r--rest_framework/renderers.py6
-rw-r--r--rest_framework/response.py18
-rw-r--r--rest_framework/settings.py2
-rw-r--r--rest_framework/tests/htmlrenderer.py10
-rw-r--r--rest_framework/tests/negotiation.py28
-rw-r--r--rest_framework/tests/response.py50
-rw-r--r--rest_framework/views.py16
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