mirror of
https://github.com/django/django.git
synced 2024-11-24 20:07:01 +01: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):
|
class HttpResponseRedirectBase(HttpResponse):
|
||||||
allowed_schemes = ["http", "https", "ftp"]
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
self["Location"] = iri_to_uri(redirect_to)
|
self["Location"] = iri_to_uri(redirect_to)
|
||||||
parsed = urlsplit(str(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:
|
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
|
||||||
raise DisallowedRedirect(
|
raise DisallowedRedirect(
|
||||||
"Unsafe redirect to URL with protocol '%s'" % parsed.scheme
|
"Unsafe redirect to URL with protocol '%s'" % parsed.scheme
|
||||||
@ -652,10 +654,12 @@ class HttpResponseRedirectBase(HttpResponse):
|
|||||||
|
|
||||||
class HttpResponseRedirect(HttpResponseRedirectBase):
|
class HttpResponseRedirect(HttpResponseRedirectBase):
|
||||||
status_code = 302
|
status_code = 302
|
||||||
|
status_code_preserve_request = 307
|
||||||
|
|
||||||
|
|
||||||
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
|
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
|
||||||
status_code = 301
|
status_code = 301
|
||||||
|
status_code_preserve_request = 308
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseNotModified(HttpResponse):
|
class HttpResponseNotModified(HttpResponse):
|
||||||
|
@ -26,7 +26,7 @@ def render(
|
|||||||
return HttpResponse(content, content_type, status)
|
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
|
Return an HttpResponseRedirect to the appropriate URL for the arguments
|
||||||
passed.
|
passed.
|
||||||
@ -40,13 +40,17 @@ def redirect(to, *args, permanent=False, **kwargs):
|
|||||||
|
|
||||||
* A URL, which will be used as-is for the redirect location.
|
* A URL, which will be used as-is for the redirect location.
|
||||||
|
|
||||||
Issues a temporary redirect by default; pass permanent=True to issue a
|
Issues a temporary redirect by default. Set permanent=True to issue a
|
||||||
permanent redirect.
|
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 = (
|
redirect_class = (
|
||||||
HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
|
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):
|
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. ``'https://www.yahoo.com/search/'``), an absolute path with no domain
|
||||||
(e.g. ``'/search/'``), or even a relative path (e.g. ``'search/'``). In that
|
(e.g. ``'/search/'``), or even a relative path (e.g. ``'search/'``). In that
|
||||||
last case, the client browser will reconstruct the full URL itself
|
last case, the client browser will reconstruct the full URL itself
|
||||||
according to the current path. See :class:`HttpResponse` for other optional
|
according to the current path.
|
||||||
constructor arguments. Note that this returns an HTTP status code 302.
|
|
||||||
|
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
|
.. attribute:: HttpResponseRedirect.url
|
||||||
|
|
||||||
This read-only attribute represents the URL the response will redirect
|
This read-only attribute represents the URL the response will redirect
|
||||||
to (equivalent to the ``Location`` response header).
|
to (equivalent to the ``Location`` response header).
|
||||||
|
|
||||||
|
.. versionchanged:: 5.2
|
||||||
|
|
||||||
|
The ``preserve_request`` argument was added.
|
||||||
|
|
||||||
.. class:: HttpResponsePermanentRedirect
|
.. class:: HttpResponsePermanentRedirect
|
||||||
|
|
||||||
Like :class:`HttpResponseRedirect`, but it returns a permanent redirect
|
Like :class:`HttpResponseRedirect`, but it returns a permanent redirect
|
||||||
(HTTP status code 301) instead of a "found" redirect (status code 302).
|
(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
|
.. class:: HttpResponseNotModified
|
||||||
|
|
||||||
|
@ -294,6 +294,16 @@ Requests and Responses
|
|||||||
* The new :meth:`.HttpRequest.get_preferred_type` method can be used to query
|
* The new :meth:`.HttpRequest.get_preferred_type` method can be used to query
|
||||||
the preferred media type the client accepts.
|
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
|
Security
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ This example is equivalent to::
|
|||||||
``redirect()``
|
``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
|
Returns an :class:`~django.http.HttpResponseRedirect` to the appropriate URL
|
||||||
for the arguments passed.
|
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
|
* An absolute or relative URL, which will be used as-is for the redirect
|
||||||
location.
|
location.
|
||||||
|
|
||||||
By default issues a temporary redirect; pass ``permanent=True`` to issue a
|
By default, a temporary redirect is issued with a 302 status code. If
|
||||||
permanent redirect.
|
``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
|
Examples
|
||||||
--------
|
--------
|
||||||
@ -158,6 +177,17 @@ will be returned::
|
|||||||
obj = MyModel.objects.get(...)
|
obj = MyModel.objects.get(...)
|
||||||
return redirect(obj, permanent=True)
|
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()``
|
``get_object_or_404()``
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
@ -566,6 +566,27 @@ class HttpResponseSubclassesTests(SimpleTestCase):
|
|||||||
r = HttpResponseRedirect(lazystr("/redirected/"))
|
r = HttpResponseRedirect(lazystr("/redirected/"))
|
||||||
self.assertEqual(r.url, "/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):
|
def test_redirect_repr(self):
|
||||||
response = HttpResponseRedirect("/redirected/")
|
response = HttpResponseRedirect("/redirected/")
|
||||||
expected = (
|
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 import SimpleTestCase, override_settings
|
||||||
from django.test.utils import require_jinja2
|
from django.test.utils import require_jinja2
|
||||||
|
|
||||||
@ -35,3 +37,22 @@ class RenderTests(SimpleTestCase):
|
|||||||
self.assertEqual(response.content, b"DTL\n")
|
self.assertEqual(response.content, b"DTL\n")
|
||||||
response = self.client.get("/render/using/?using=jinja2")
|
response = self.client.get("/render/using/?using=jinja2")
|
||||||
self.assertEqual(response.content, b"Jinja2\n")
|
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