Commit cb86f707 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Fixed #12747 -- Made reason phrases customizable.

parent 3129d190
Loading
Loading
Loading
Loading
+4 −63
Original line number Diff line number Diff line
@@ -13,66 +13,11 @@ from django.core.urlresolvers import set_script_prefix
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 @@ class WSGIHandler(base.BaseHandler):

        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=''))))
+67 −3
Original line number Diff line number Diff line
@@ -20,6 +20,65 @@ from django.utils.http import cookie_date
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 @@ class HttpResponseBase(six.Iterator):
            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):
+21 −6
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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 −0

Empty file added.

Loading