mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
Fixed #34757 -- Added support for following redirects to AsyncClient.
This commit is contained in:
parent
1ac397674b
commit
3f8dbe267d
@ -705,9 +705,6 @@ class AsyncRequestFactory(RequestFactory):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
s["_body_file"] = FakePayload(data)
|
s["_body_file"] = FakePayload(data)
|
||||||
follow = extra.pop("follow", None)
|
|
||||||
if follow is not None:
|
|
||||||
s["follow"] = follow
|
|
||||||
if query_string := extra.pop("QUERY_STRING", None):
|
if query_string := extra.pop("QUERY_STRING", None):
|
||||||
s["query_string"] = query_string
|
s["query_string"] = query_string
|
||||||
if headers:
|
if headers:
|
||||||
@ -1296,10 +1293,6 @@ class AsyncClient(ClientMixin, AsyncRequestFactory):
|
|||||||
query environment, which can be overridden using the arguments to the
|
query environment, which can be overridden using the arguments to the
|
||||||
request.
|
request.
|
||||||
"""
|
"""
|
||||||
if "follow" in request:
|
|
||||||
raise NotImplementedError(
|
|
||||||
"AsyncClient request methods do not accept the follow parameter."
|
|
||||||
)
|
|
||||||
scope = self._base_scope(**request)
|
scope = self._base_scope(**request)
|
||||||
# Curry a data dictionary into an instance of the template renderer
|
# Curry a data dictionary into an instance of the template renderer
|
||||||
# callback function.
|
# callback function.
|
||||||
@ -1338,3 +1331,234 @@ class AsyncClient(ClientMixin, AsyncRequestFactory):
|
|||||||
if response.cookies:
|
if response.cookies:
|
||||||
self.cookies.update(response.cookies)
|
self.cookies.update(response.cookies)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
async def get(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data=None,
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Request a response from the server using GET."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().get(
|
||||||
|
path, data=data, secure=secure, headers=headers, **extra
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def post(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data=None,
|
||||||
|
content_type=MULTIPART_CONTENT,
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Request a response from the server using POST."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().post(
|
||||||
|
path,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
secure=secure,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, content_type=content_type, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def head(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data=None,
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Request a response from the server using HEAD."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().head(
|
||||||
|
path, data=data, secure=secure, headers=headers, **extra
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def options(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data="",
|
||||||
|
content_type="application/octet-stream",
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Request a response from the server using OPTIONS."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().options(
|
||||||
|
path,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
secure=secure,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, content_type=content_type, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def put(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data="",
|
||||||
|
content_type="application/octet-stream",
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Send a resource to the server using PUT."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().put(
|
||||||
|
path,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
secure=secure,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, content_type=content_type, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def patch(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data="",
|
||||||
|
content_type="application/octet-stream",
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Send a resource to the server using PATCH."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().patch(
|
||||||
|
path,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
secure=secure,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, content_type=content_type, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def delete(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data="",
|
||||||
|
content_type="application/octet-stream",
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Send a DELETE request to the server."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().delete(
|
||||||
|
path,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
secure=secure,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, content_type=content_type, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def trace(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
data="",
|
||||||
|
follow=False,
|
||||||
|
secure=False,
|
||||||
|
*,
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""Send a TRACE request to the server."""
|
||||||
|
self.extra = extra
|
||||||
|
self.headers = headers
|
||||||
|
response = await super().trace(
|
||||||
|
path, data=data, secure=secure, headers=headers, **extra
|
||||||
|
)
|
||||||
|
if follow:
|
||||||
|
response = await self._ahandle_redirects(
|
||||||
|
response, data=data, headers=headers, **extra
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def _ahandle_redirects(
|
||||||
|
self,
|
||||||
|
response,
|
||||||
|
data="",
|
||||||
|
content_type="",
|
||||||
|
headers=None,
|
||||||
|
**extra,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Follow any redirects by requesting responses from the server using GET.
|
||||||
|
"""
|
||||||
|
response.redirect_chain = []
|
||||||
|
while response.status_code in REDIRECT_STATUS_CODES:
|
||||||
|
redirect_chain = response.redirect_chain
|
||||||
|
response = await self._follow_redirect(
|
||||||
|
response,
|
||||||
|
data=data,
|
||||||
|
content_type=content_type,
|
||||||
|
headers=headers,
|
||||||
|
**extra,
|
||||||
|
)
|
||||||
|
response.redirect_chain = redirect_chain
|
||||||
|
self._ensure_redirects_not_cyclic(response)
|
||||||
|
return response
|
||||||
|
@ -433,6 +433,8 @@ Tests
|
|||||||
:meth:`~django.test.Client.aforce_login`, and
|
:meth:`~django.test.Client.aforce_login`, and
|
||||||
:meth:`~django.test.Client.alogout`.
|
:meth:`~django.test.Client.alogout`.
|
||||||
|
|
||||||
|
* :class:`~django.test.AsyncClient` now supports the ``follow`` parameter.
|
||||||
|
|
||||||
URLs
|
URLs
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
|
@ -2032,7 +2032,6 @@ test client, with the following exceptions:
|
|||||||
|
|
||||||
* In the initialization, arbitrary keyword arguments in ``defaults`` are added
|
* In the initialization, arbitrary keyword arguments in ``defaults`` are added
|
||||||
directly into the ASGI scope.
|
directly into the ASGI scope.
|
||||||
* The ``follow`` parameter is not supported.
|
|
||||||
* Headers passed as ``extra`` keyword arguments should not have the ``HTTP_``
|
* Headers passed as ``extra`` keyword arguments should not have the ``HTTP_``
|
||||||
prefix required by the synchronous client (see :meth:`Client.get`). For
|
prefix required by the synchronous client (see :meth:`Client.get`). For
|
||||||
example, here is how to set an HTTP ``Accept`` header:
|
example, here is how to set an HTTP ``Accept`` header:
|
||||||
@ -2046,6 +2045,10 @@ test client, with the following exceptions:
|
|||||||
|
|
||||||
The ``headers`` parameter was added.
|
The ``headers`` parameter was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 5.0
|
||||||
|
|
||||||
|
Support for the ``follow`` parameter was added to the ``AsyncClient``.
|
||||||
|
|
||||||
Using ``AsyncClient`` any method that makes a request must be awaited::
|
Using ``AsyncClient`` any method that makes a request must be awaited::
|
||||||
|
|
||||||
async def test_my_thing(self):
|
async def test_my_thing(self):
|
||||||
|
@ -1135,8 +1135,11 @@ class AsyncClientTest(TestCase):
|
|||||||
response = await self.async_client.get("/middleware_urlconf_view/")
|
response = await self.async_client.get("/middleware_urlconf_view/")
|
||||||
self.assertEqual(response.resolver_match.url_name, "middleware_urlconf_view")
|
self.assertEqual(response.resolver_match.url_name, "middleware_urlconf_view")
|
||||||
|
|
||||||
async def test_follow_parameter_not_implemented(self):
|
async def test_redirect(self):
|
||||||
msg = "AsyncClient request methods do not accept the follow parameter."
|
response = await self.async_client.get("/redirect_view/")
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
async def test_follow_redirect(self):
|
||||||
tests = (
|
tests = (
|
||||||
"get",
|
"get",
|
||||||
"post",
|
"post",
|
||||||
@ -1150,8 +1153,16 @@ class AsyncClientTest(TestCase):
|
|||||||
for method_name in tests:
|
for method_name in tests:
|
||||||
with self.subTest(method=method_name):
|
with self.subTest(method=method_name):
|
||||||
method = getattr(self.async_client, method_name)
|
method = getattr(self.async_client, method_name)
|
||||||
with self.assertRaisesMessage(NotImplementedError, msg):
|
response = await method("/redirect_view/", follow=True)
|
||||||
await method("/redirect_view/", follow=True)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.resolver_match.url_name, "get_view")
|
||||||
|
|
||||||
|
async def test_follow_double_redirect(self):
|
||||||
|
response = await self.async_client.get("/double_redirect_view/", follow=True)
|
||||||
|
self.assertRedirects(
|
||||||
|
response, "/get_view/", status_code=302, target_status_code=200
|
||||||
|
)
|
||||||
|
self.assertEqual(len(response.redirect_chain), 2)
|
||||||
|
|
||||||
async def test_get_data(self):
|
async def test_get_data(self):
|
||||||
response = await self.async_client.get("/get_view/", {"var": "val"})
|
response = await self.async_client.get("/get_view/", {"var": "val"})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user