Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12747 -- Made reason phrases customizable.

  • Loading branch information...
commit cb86f707a04e5635817d5f37a1443f9bf7d6af21 1 parent 3129d19
@aaugustin aaugustin authored
View
67 django/core/handlers/wsgi.py
@@ -13,66 +13,11 @@
from django.utils import datastructures
from django.utils.encoding import force_str, force_text, iri_to_uri
-logger = logging.getLogger('django.request')
+# For backwards compatibility -- lots of code uses this in the wild!
+from django.http.response import REASON_PHRASES as STATUS_CODE_TEXT
+logger = logging.getLogger('django.request')
-# See http://www.iana.org/assignments/http-status-codes
-STATUS_CODE_TEXT = {
- 100: 'CONTINUE',
- 101: 'SWITCHING PROTOCOLS',
- 102: 'PROCESSING',
- 200: 'OK',
- 201: 'CREATED',
- 202: 'ACCEPTED',
- 203: 'NON-AUTHORITATIVE INFORMATION',
- 204: 'NO CONTENT',
- 205: 'RESET CONTENT',
- 206: 'PARTIAL CONTENT',
- 207: 'MULTI-STATUS',
- 208: 'ALREADY REPORTED',
- 226: 'IM USED',
- 300: 'MULTIPLE CHOICES',
- 301: 'MOVED PERMANENTLY',
- 302: 'FOUND',
- 303: 'SEE OTHER',
- 304: 'NOT MODIFIED',
- 305: 'USE PROXY',
- 306: 'RESERVED',
- 307: 'TEMPORARY REDIRECT',
- 400: 'BAD REQUEST',
- 401: 'UNAUTHORIZED',
- 402: 'PAYMENT REQUIRED',
- 403: 'FORBIDDEN',
- 404: 'NOT FOUND',
- 405: 'METHOD NOT ALLOWED',
- 406: 'NOT ACCEPTABLE',
- 407: 'PROXY AUTHENTICATION REQUIRED',
- 408: 'REQUEST TIMEOUT',
- 409: 'CONFLICT',
- 410: 'GONE',
- 411: 'LENGTH REQUIRED',
- 412: 'PRECONDITION FAILED',
- 413: 'REQUEST ENTITY TOO LARGE',
- 414: 'REQUEST-URI TOO LONG',
- 415: 'UNSUPPORTED MEDIA TYPE',
- 416: 'REQUESTED RANGE NOT SATISFIABLE',
- 417: 'EXPECTATION FAILED',
- 418: "I'M A TEAPOT",
- 422: 'UNPROCESSABLE ENTITY',
- 423: 'LOCKED',
- 424: 'FAILED DEPENDENCY',
- 426: 'UPGRADE REQUIRED',
- 500: 'INTERNAL SERVER ERROR',
- 501: 'NOT IMPLEMENTED',
- 502: 'BAD GATEWAY',
- 503: 'SERVICE UNAVAILABLE',
- 504: 'GATEWAY TIMEOUT',
- 505: 'HTTP VERSION NOT SUPPORTED',
- 506: 'VARIANT ALSO NEGOTIATES',
- 507: 'INSUFFICIENT STORAGE',
- 508: 'LOOP DETECTED',
- 510: 'NOT EXTENDED',
-}
class LimitedStream(object):
'''
@@ -254,11 +199,7 @@ def __call__(self, environ, start_response):
response._handler_class = self.__class__
- try:
- status_text = STATUS_CODE_TEXT[response.status_code]
- except KeyError:
- status_text = 'UNKNOWN STATUS CODE'
- status = '%s %s' % (response.status_code, status_text)
+ status = '%s %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
View
70 django/http/response.py
@@ -20,6 +20,65 @@
from django.utils.six.moves import map
+# See http://www.iana.org/assignments/http-status-codes
+REASON_PHRASES = {
+ 100: 'CONTINUE',
+ 101: 'SWITCHING PROTOCOLS',
+ 102: 'PROCESSING',
+ 200: 'OK',
+ 201: 'CREATED',
+ 202: 'ACCEPTED',
+ 203: 'NON-AUTHORITATIVE INFORMATION',
+ 204: 'NO CONTENT',
+ 205: 'RESET CONTENT',
+ 206: 'PARTIAL CONTENT',
+ 207: 'MULTI-STATUS',
+ 208: 'ALREADY REPORTED',
+ 226: 'IM USED',
+ 300: 'MULTIPLE CHOICES',
+ 301: 'MOVED PERMANENTLY',
+ 302: 'FOUND',
+ 303: 'SEE OTHER',
+ 304: 'NOT MODIFIED',
+ 305: 'USE PROXY',
+ 306: 'RESERVED',
+ 307: 'TEMPORARY REDIRECT',
+ 400: 'BAD REQUEST',
+ 401: 'UNAUTHORIZED',
+ 402: 'PAYMENT REQUIRED',
+ 403: 'FORBIDDEN',
+ 404: 'NOT FOUND',
+ 405: 'METHOD NOT ALLOWED',
+ 406: 'NOT ACCEPTABLE',
+ 407: 'PROXY AUTHENTICATION REQUIRED',
+ 408: 'REQUEST TIMEOUT',
+ 409: 'CONFLICT',
+ 410: 'GONE',
+ 411: 'LENGTH REQUIRED',
+ 412: 'PRECONDITION FAILED',
+ 413: 'REQUEST ENTITY TOO LARGE',
+ 414: 'REQUEST-URI TOO LONG',
+ 415: 'UNSUPPORTED MEDIA TYPE',
+ 416: 'REQUESTED RANGE NOT SATISFIABLE',
+ 417: 'EXPECTATION FAILED',
+ 418: "I'M A TEAPOT",
+ 422: 'UNPROCESSABLE ENTITY',
+ 423: 'LOCKED',
+ 424: 'FAILED DEPENDENCY',
+ 426: 'UPGRADE REQUIRED',
+ 500: 'INTERNAL SERVER ERROR',
+ 501: 'NOT IMPLEMENTED',
+ 502: 'BAD GATEWAY',
+ 503: 'SERVICE UNAVAILABLE',
+ 504: 'GATEWAY TIMEOUT',
+ 505: 'HTTP VERSION NOT SUPPORTED',
+ 506: 'VARIANT ALSO NEGOTIATES',
+ 507: 'INSUFFICIENT STORAGE',
+ 508: 'LOOP DETECTED',
+ 510: 'NOT EXTENDED',
+}
+
+
class BadHeaderError(ValueError):
pass
@@ -33,8 +92,9 @@ 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, mimetype=None):
+ def __init__(self, content_type=None, status=None, reason=None, mimetype=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.
@@ -53,9 +113,13 @@ def __init__(self, content_type=None, status=None, mimetype=None):
content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
self._charset)
self.cookies = SimpleCookie()
- if status:
+ if status is not None:
self.status_code = status
-
+ if reason is not None:
+ self.reason_phrase = reason
+ elif self.reason_phrase is None:
+ self.reason_phrase = REASON_PHRASES.get(self.status_code,
+ 'UNKNOWN STATUS CODE')
self['Content-Type'] = content_type
def serialize_headers(self):
View
27 docs/ref/request-response.txt
@@ -616,7 +616,13 @@ Attributes
.. attribute:: HttpResponse.status_code
- The `HTTP Status code`_ for the response.
+ The `HTTP status code`_ for the response.
+
+.. attribute:: HttpResponse.reason_phrase
+
+ .. versionadded:: 1.6
+
+ The HTTP reason phrase for the response.
.. attribute:: HttpResponse.streaming
@@ -628,7 +634,7 @@ Attributes
Methods
-------
-.. method:: HttpResponse.__init__(content='', content_type=None, status=200)
+.. method:: HttpResponse.__init__(content='', content_type=None, status=200, reason=None)
Instantiates an ``HttpResponse`` object with the given page content and
content type.
@@ -646,8 +652,12 @@ Methods
Historically, this parameter was called ``mimetype`` (now deprecated).
- ``status`` is the `HTTP Status code`_ for the response.
+ ``status`` is the `HTTP status code`_ for the response.
+
+ .. versionadded:: 1.6
+ ``reason`` is the HTTP response phrase. If not provided, a default phrase
+ will be used.
.. method:: HttpResponse.__setitem__(header, value)
@@ -727,8 +737,7 @@ Methods
This method makes an :class:`HttpResponse` instance a file-like object.
-.. _HTTP Status code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
-
+.. _HTTP status code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
.. _ref-httpresponse-subclasses:
@@ -851,7 +860,13 @@ Attributes
.. attribute:: HttpResponse.status_code
- The `HTTP Status code`_ for the response.
+ The `HTTP status code`_ for the response.
+
+.. attribute:: HttpResponse.reason_phrase
+
+ .. versionadded:: 1.6
+
+ The HTTP reason phrase for the response.
.. attribute:: HttpResponse.streaming
View
2  docs/releases/1.6.txt
@@ -241,6 +241,8 @@ Minor features
* The ``choices`` argument to model fields now accepts an iterable of iterables
instead of requiring an iterable of lists or tuples.
+* The reason phrase can be customized in HTTP responses.
+
Backwards incompatible changes in 1.6
=====================================
View
0  tests/responses/__init__.py
No changes.
View
0  tests/responses/models.py
No changes.
View
15 tests/responses/tests.py
@@ -0,0 +1,15 @@
+from django.http import HttpResponse
+import unittest
+
+class HttpResponseTests(unittest.TestCase):
+
+ def test_status_code(self):
+ resp = HttpResponse(status=418)
+ self.assertEqual(resp.status_code, 418)
+ self.assertEqual(resp.reason_phrase, "I'M A TEAPOT")
+
+ def test_reason_phrase(self):
+ reason = "I'm an anarchist coffee pot on crack."
+ resp = HttpResponse(status=814, reason=reason)
+ self.assertEqual(resp.status_code, 814)
+ self.assertEqual(resp.reason_phrase, reason)
Please sign in to comment.
Something went wrong with that request. Please try again.