diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/http/response.py | 33 | ||||
| -rw-r--r-- | django/template/response.py | 9 | ||||
| -rw-r--r-- | django/test/testcases.py | 4 |
3 files changed, 33 insertions, 13 deletions
diff --git a/django/http/response.py b/django/http/response.py index 866b3480e7..0cc14d1346 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import datetime import json +import re import sys import time from email.header import Header @@ -83,6 +84,9 @@ REASON_PHRASES = { } +_charset_from_content_type_re = re.compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I) + + class BadHeaderError(ValueError): pass @@ -98,19 +102,15 @@ class HttpResponseBase(six.Iterator): status_code = 200 reason_phrase = None # Use default reason phrase for status code. - def __init__(self, content_type=None, status=None, reason=None): + def __init__(self, content_type=None, status=None, reason=None, charset=None): # _headers is a mapping of the lower-case name to the original case of # the header (required for working with legacy systems) and the header # value. Both the name of the header and its value are ASCII strings. self._headers = {} - self._charset = settings.DEFAULT_CHARSET self._closable_objects = [] # This parameter is set by the handler. It's necessary to preserve the # historical behavior of request_finished. self._handler_class = None - if not content_type: - content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, - self._charset) self.cookies = SimpleCookie() if status is not None: self.status_code = status @@ -119,8 +119,27 @@ class HttpResponseBase(six.Iterator): elif self.reason_phrase is None: self.reason_phrase = REASON_PHRASES.get(self.status_code, 'UNKNOWN STATUS CODE') + self._charset = charset + if content_type is None: + content_type = '%s; charset=%s' % (settings.DEFAULT_CONTENT_TYPE, + self.charset) self['Content-Type'] = content_type + @property + def charset(self): + if self._charset is not None: + return self._charset + content_type = self.get('Content-Type', '') + matched = _charset_from_content_type_re.search(content_type) + if matched: + # Extract the charset and strip its double quotes + return matched.group('charset').replace('"', '') + return settings.DEFAULT_CHARSET + + @charset.setter + def charset(self, value): + self._charset = value + def serialize_headers(self): """HTTP headers as a bytestring.""" def to_bytes(val, encoding): @@ -278,10 +297,10 @@ class HttpResponseBase(six.Iterator): if isinstance(value, bytes): return bytes(value) if isinstance(value, six.text_type): - return bytes(value.encode(self._charset)) + return bytes(value.encode(self.charset)) # Handle non-string types (#16494) - return force_bytes(value, self._charset) + return force_bytes(value, self.charset) # These methods partially implement the file-like object interface. # See http://docs.python.org/lib/bltin-file-objects.html diff --git a/django/template/response.py b/django/template/response.py index f673c5f46a..f65c3adb8b 100644 --- a/django/template/response.py +++ b/django/template/response.py @@ -10,7 +10,8 @@ class ContentNotRenderedError(Exception): class SimpleTemplateResponse(HttpResponse): rendering_attrs = ['template_name', 'context_data', '_post_render_callbacks'] - def __init__(self, template, context=None, content_type=None, status=None): + def __init__(self, template, context=None, content_type=None, status=None, + charset=None): # It would seem obvious to call these next two members 'template' and # 'context', but those names are reserved as part of the test Client # API. To avoid the name collision, we use tricky-to-debug problems @@ -22,7 +23,7 @@ class SimpleTemplateResponse(HttpResponse): # content argument doesn't make sense here because it will be replaced # with rendered template so we always pass empty string in order to # prevent errors and provide shorter signature. - super(SimpleTemplateResponse, self).__init__('', content_type, status) + super(SimpleTemplateResponse, self).__init__('', content_type, status, charset) # _is_rendered tracks whether the template and context has been baked # into a final response. @@ -136,7 +137,7 @@ class TemplateResponse(SimpleTemplateResponse): rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_request', '_current_app'] def __init__(self, request, template, context=None, content_type=None, - status=None, current_app=None): + status=None, current_app=None, charset=None): # self.request gets over-written by django.test.client.Client - and # unlike context_data and template_name the _request should not # be considered part of the public API. @@ -145,7 +146,7 @@ class TemplateResponse(SimpleTemplateResponse): # having to avoid needing to create the RequestContext directly self._current_app = current_app super(TemplateResponse, self).__init__( - template, context, content_type, status) + template, context, content_type, status, charset) def resolve_context(self, context): """Convert context data into a full RequestContext object diff --git a/django/test/testcases.py b/django/test/testcases.py index d46f672391..e982706784 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -327,8 +327,8 @@ class SimpleTestCase(unittest.TestCase): else: content = response.content if not isinstance(text, bytes) or html: - text = force_text(text, encoding=response._charset) - content = content.decode(response._charset) + text = force_text(text, encoding=response.charset) + content = content.decode(response.charset) text_repr = "'%s'" % text else: text_repr = repr(text) |
