mirror of
https://github.com/django/django.git
synced 2024-12-22 00:55:44 +00:00
Fixed #35784 -- Added support for preserving the HTTP request method in HttpResponseRedirectBase.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
This commit is contained in:
parent
8590d05d44
commit
91c879eda5
@ -627,10 +627,12 @@ class FileResponse(StreamingHttpResponse):
|
||||
class HttpResponseRedirectBase(HttpResponse):
|
||||
allowed_schemes = ["http", "https", "ftp"]
|
||||
|
||||
def __init__(self, redirect_to, *args, **kwargs):
|
||||
def __init__(self, redirect_to, preserve_request=False, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self["Location"] = iri_to_uri(redirect_to)
|
||||
parsed = urlsplit(str(redirect_to))
|
||||
if preserve_request:
|
||||
self.status_code = self.status_code_preserve_request
|
||||
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
|
||||
raise DisallowedRedirect(
|
||||
"Unsafe redirect to URL with protocol '%s'" % parsed.scheme
|
||||
@ -652,10 +654,12 @@ class HttpResponseRedirectBase(HttpResponse):
|
||||
|
||||
class HttpResponseRedirect(HttpResponseRedirectBase):
|
||||
status_code = 302
|
||||
status_code_preserve_request = 307
|
||||
|
||||
|
||||
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
|
||||
status_code = 301
|
||||
status_code_preserve_request = 308
|
||||
|
||||
|
||||
class HttpResponseNotModified(HttpResponse):
|
||||
|
@ -26,7 +26,7 @@ def render(
|
||||
return HttpResponse(content, content_type, status)
|
||||
|
||||
|
||||
def redirect(to, *args, permanent=False, **kwargs):
|
||||
def redirect(to, *args, permanent=False, preserve_request=False, **kwargs):
|
||||
"""
|
||||
Return an HttpResponseRedirect to the appropriate URL for the arguments
|
||||
passed.
|
||||
@ -40,13 +40,17 @@ def redirect(to, *args, permanent=False, **kwargs):
|
||||
|
||||
* A URL, which will be used as-is for the redirect location.
|
||||
|
||||
Issues a temporary redirect by default; pass permanent=True to issue a
|
||||
permanent redirect.
|
||||
Issues a temporary redirect by default. Set permanent=True to issue a
|
||||
permanent redirect. Set preserve_request=True to instruct the user agent
|
||||
to preserve the original HTTP method and body when following the redirect.
|
||||
"""
|
||||
redirect_class = (
|
||||
HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
|
||||
)
|
||||
return redirect_class(resolve_url(to, *args, **kwargs))
|
||||
return redirect_class(
|
||||
resolve_url(to, *args, **kwargs),
|
||||
preserve_request=preserve_request,
|
||||
)
|
||||
|
||||
|
||||
def _get_queryset(klass):
|
||||
|
@ -1070,18 +1070,32 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
|
||||
(e.g. ``'https://www.yahoo.com/search/'``), an absolute path with no domain
|
||||
(e.g. ``'/search/'``), or even a relative path (e.g. ``'search/'``). In that
|
||||
last case, the client browser will reconstruct the full URL itself
|
||||
according to the current path. See :class:`HttpResponse` for other optional
|
||||
constructor arguments. Note that this returns an HTTP status code 302.
|
||||
according to the current path.
|
||||
|
||||
The constructor accepts an optional ``preserve_request`` keyword argument
|
||||
that defaults to ``False``, producing a response with a 302 status code. If
|
||||
``preserve_request`` is ``True``, the status code will be 307 instead.
|
||||
|
||||
See :class:`HttpResponse` for other optional constructor arguments.
|
||||
|
||||
.. attribute:: HttpResponseRedirect.url
|
||||
|
||||
This read-only attribute represents the URL the response will redirect
|
||||
to (equivalent to the ``Location`` response header).
|
||||
|
||||
.. versionchanged:: 5.2
|
||||
|
||||
The ``preserve_request`` argument was added.
|
||||
|
||||
.. class:: HttpResponsePermanentRedirect
|
||||
|
||||
Like :class:`HttpResponseRedirect`, but it returns a permanent redirect
|
||||
(HTTP status code 301) instead of a "found" redirect (status code 302).
|
||||
When ``preserve_request=True``, the response's status code is 308.
|
||||
|
||||
.. versionchanged:: 5.2
|
||||
|
||||
The ``preserve_request`` argument was added.
|
||||
|
||||
.. class:: HttpResponseNotModified
|
||||
|
||||
|
@ -294,6 +294,16 @@ Requests and Responses
|
||||
* The new :meth:`.HttpRequest.get_preferred_type` method can be used to query
|
||||
the preferred media type the client accepts.
|
||||
|
||||
* The new ``preserve_request`` argument for
|
||||
:class:`~django.http.HttpResponseRedirect` and
|
||||
:class:`~django.http.HttpResponsePermanentRedirect`
|
||||
determines whether the HTTP status codes 302/307 or 301/308 are used,
|
||||
respectively.
|
||||
|
||||
* The new ``preserve_request`` argument for
|
||||
:func:`~django.shortcuts.redirect` allows to instruct the user agent to reuse
|
||||
the HTTP method and body during redirection using specific status codes.
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
|
@ -91,7 +91,7 @@ This example is equivalent to::
|
||||
``redirect()``
|
||||
==============
|
||||
|
||||
.. function:: redirect(to, *args, permanent=False, **kwargs)
|
||||
.. function:: redirect(to, *args, permanent=False, preserve_request=False, **kwargs)
|
||||
|
||||
Returns an :class:`~django.http.HttpResponseRedirect` to the appropriate URL
|
||||
for the arguments passed.
|
||||
@ -107,8 +107,27 @@ This example is equivalent to::
|
||||
* An absolute or relative URL, which will be used as-is for the redirect
|
||||
location.
|
||||
|
||||
By default issues a temporary redirect; pass ``permanent=True`` to issue a
|
||||
permanent redirect.
|
||||
By default, a temporary redirect is issued with a 302 status code. If
|
||||
``permanent=True``, a permanent redirect is issued with a 301 status code.
|
||||
|
||||
If ``preserve_request=True``, the response instructs the user agent to
|
||||
preserve the method and body of the original request when issuing the
|
||||
redirect. In this case, temporary redirects use a 307 status code, and
|
||||
permanent redirects use a 308 status code. This is better illustrated in the
|
||||
following table:
|
||||
|
||||
========= ================ ================
|
||||
permanent preserve_request HTTP status code
|
||||
========= ================ ================
|
||||
``True`` ``False`` 301
|
||||
``False`` ``False`` 302
|
||||
``False`` ``True`` 307
|
||||
``True`` ``True`` 308
|
||||
========= ================ ================
|
||||
|
||||
.. versionchanged:: 5.2
|
||||
|
||||
The argument ``preserve_request`` was added.
|
||||
|
||||
Examples
|
||||
--------
|
||||
@ -158,6 +177,17 @@ will be returned::
|
||||
obj = MyModel.objects.get(...)
|
||||
return redirect(obj, permanent=True)
|
||||
|
||||
Additionally, the ``preserve_request`` argument can be used to preserve the
|
||||
original HTTP method::
|
||||
|
||||
def my_view(request):
|
||||
# ...
|
||||
obj = MyModel.objects.get(...)
|
||||
if request.method in ("POST", "PUT"):
|
||||
# Redirection preserves the original request method.
|
||||
return redirect(obj, preserve_request=True)
|
||||
# ...
|
||||
|
||||
``get_object_or_404()``
|
||||
=======================
|
||||
|
||||
|
@ -566,6 +566,27 @@ class HttpResponseSubclassesTests(SimpleTestCase):
|
||||
r = HttpResponseRedirect(lazystr("/redirected/"))
|
||||
self.assertEqual(r.url, "/redirected/")
|
||||
|
||||
def test_redirect_modifiers(self):
|
||||
cases = [
|
||||
(HttpResponseRedirect, "Moved temporarily", False, 302),
|
||||
(HttpResponseRedirect, "Moved temporarily preserve method", True, 307),
|
||||
(HttpResponsePermanentRedirect, "Moved permanently", False, 301),
|
||||
(
|
||||
HttpResponsePermanentRedirect,
|
||||
"Moved permanently preserve method",
|
||||
True,
|
||||
308,
|
||||
),
|
||||
]
|
||||
for response_class, content, preserve_request, expected_status_code in cases:
|
||||
with self.subTest(status_code=expected_status_code):
|
||||
response = response_class(
|
||||
"/redirected/", content=content, preserve_request=preserve_request
|
||||
)
|
||||
self.assertEqual(response.status_code, expected_status_code)
|
||||
self.assertEqual(response.content.decode(), content)
|
||||
self.assertEqual(response.url, response.headers["Location"])
|
||||
|
||||
def test_redirect_repr(self):
|
||||
response = HttpResponseRedirect("/redirected/")
|
||||
expected = (
|
||||
|
@ -1,3 +1,5 @@
|
||||
from django.http.response import HttpResponseRedirectBase
|
||||
from django.shortcuts import redirect
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.test.utils import require_jinja2
|
||||
|
||||
@ -35,3 +37,22 @@ class RenderTests(SimpleTestCase):
|
||||
self.assertEqual(response.content, b"DTL\n")
|
||||
response = self.client.get("/render/using/?using=jinja2")
|
||||
self.assertEqual(response.content, b"Jinja2\n")
|
||||
|
||||
|
||||
class RedirectTests(SimpleTestCase):
|
||||
def test_redirect_response_status_code(self):
|
||||
tests = [
|
||||
(True, False, 301),
|
||||
(False, False, 302),
|
||||
(False, True, 307),
|
||||
(True, True, 308),
|
||||
]
|
||||
for permanent, preserve_request, expected_status_code in tests:
|
||||
with self.subTest(permanent=permanent, preserve_request=preserve_request):
|
||||
response = redirect(
|
||||
"/path/is/irrelevant/",
|
||||
permanent=permanent,
|
||||
preserve_request=preserve_request,
|
||||
)
|
||||
self.assertIsInstance(response, HttpResponseRedirectBase)
|
||||
self.assertEqual(response.status_code, expected_status_code)
|
||||
|
Loading…
Reference in New Issue
Block a user