Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed #12747 -- Made reason phrases customizable. #1154

Closed
wants to merge 1 commit into from

1 participant

Aymeric Augustin
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 19, 2013
  1. Aymeric Augustin
This page is out of date. Refresh to see the latest.
64 django/core/handlers/wsgi.py
View
@@ -16,64 +16,6 @@
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):
'''
LimitedStream wraps another stream in order to not allow reading from it
@@ -254,11 +196,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=''))))
70 django/http/response.py
View
@@ -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):
27 docs/ref/request-response.txt
View
@@ -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
2  docs/releases/1.6.txt
View
@@ -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
=====================================
0  tests/responses/__init__.py
View
No changes.
0  tests/responses/models.py
View
No changes.
15 tests/responses/tests.py
View
@@ -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)
Something went wrong with that request. Please try again.