mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #27999 -- Added test client support for HTTP 307 and 308 redirects.
This commit is contained in:
		| @@ -5,6 +5,7 @@ import re | ||||
| import sys | ||||
| from copy import copy | ||||
| from functools import partial | ||||
| from http import HTTPStatus | ||||
| from importlib import import_module | ||||
| from io import BytesIO | ||||
| from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit | ||||
| @@ -512,7 +513,7 @@ class Client(RequestFactory): | ||||
|         """Request a response from the server using GET.""" | ||||
|         response = super().get(path, data=data, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, **extra) | ||||
|         return response | ||||
|  | ||||
|     def post(self, path, data=None, content_type=MULTIPART_CONTENT, | ||||
| @@ -520,14 +521,14 @@ class Client(RequestFactory): | ||||
|         """Request a response from the server using POST.""" | ||||
|         response = super().post(path, data=data, content_type=content_type, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, content_type=content_type, **extra) | ||||
|         return response | ||||
|  | ||||
|     def head(self, path, data=None, follow=False, secure=False, **extra): | ||||
|         """Request a response from the server using HEAD.""" | ||||
|         response = super().head(path, data=data, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, **extra) | ||||
|         return response | ||||
|  | ||||
|     def options(self, path, data='', content_type='application/octet-stream', | ||||
| @@ -535,7 +536,7 @@ class Client(RequestFactory): | ||||
|         """Request a response from the server using OPTIONS.""" | ||||
|         response = super().options(path, data=data, content_type=content_type, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, content_type=content_type, **extra) | ||||
|         return response | ||||
|  | ||||
|     def put(self, path, data='', content_type='application/octet-stream', | ||||
| @@ -543,7 +544,7 @@ class Client(RequestFactory): | ||||
|         """Send a resource to the server using PUT.""" | ||||
|         response = super().put(path, data=data, content_type=content_type, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, content_type=content_type, **extra) | ||||
|         return response | ||||
|  | ||||
|     def patch(self, path, data='', content_type='application/octet-stream', | ||||
| @@ -551,7 +552,7 @@ class Client(RequestFactory): | ||||
|         """Send a resource to the server using PATCH.""" | ||||
|         response = super().patch(path, data=data, content_type=content_type, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, content_type=content_type, **extra) | ||||
|         return response | ||||
|  | ||||
|     def delete(self, path, data='', content_type='application/octet-stream', | ||||
| @@ -559,14 +560,14 @@ class Client(RequestFactory): | ||||
|         """Send a DELETE request to the server.""" | ||||
|         response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, content_type=content_type, **extra) | ||||
|         return response | ||||
|  | ||||
|     def trace(self, path, data='', follow=False, secure=False, **extra): | ||||
|         """Send a TRACE request to the server.""" | ||||
|         response = super().trace(path, data=data, secure=secure, **extra) | ||||
|         if follow: | ||||
|             response = self._handle_redirects(response, **extra) | ||||
|             response = self._handle_redirects(response, data=data, **extra) | ||||
|         return response | ||||
|  | ||||
|     def login(self, **credentials): | ||||
| @@ -648,12 +649,19 @@ class Client(RequestFactory): | ||||
|             response._json = json.loads(response.content.decode(), **extra) | ||||
|         return response._json | ||||
|  | ||||
|     def _handle_redirects(self, response, **extra): | ||||
|     def _handle_redirects(self, response, data='', content_type='', **extra): | ||||
|         """ | ||||
|         Follow any redirects by requesting responses from the server using GET. | ||||
|         """ | ||||
|         response.redirect_chain = [] | ||||
|         while response.status_code in (301, 302, 303, 307): | ||||
|         redirect_status_codes = ( | ||||
|             HTTPStatus.MOVED_PERMANENTLY, | ||||
|             HTTPStatus.FOUND, | ||||
|             HTTPStatus.SEE_OTHER, | ||||
|             HTTPStatus.TEMPORARY_REDIRECT, | ||||
|             HTTPStatus.PERMANENT_REDIRECT, | ||||
|         ) | ||||
|         while response.status_code in redirect_status_codes: | ||||
|             response_url = response.url | ||||
|             redirect_chain = response.redirect_chain | ||||
|             redirect_chain.append((response_url, response.status_code)) | ||||
| @@ -671,7 +679,15 @@ class Client(RequestFactory): | ||||
|             if not path.startswith('/'): | ||||
|                 path = urljoin(response.request['PATH_INFO'], path) | ||||
|  | ||||
|             response = self.get(path, QueryDict(url.query), follow=False, **extra) | ||||
|             if response.status_code in (HTTPStatus.TEMPORARY_REDIRECT, HTTPStatus.PERMANENT_REDIRECT): | ||||
|                 # Preserve request method post-redirect for 307/308 responses. | ||||
|                 request_method = getattr(self, response.request['REQUEST_METHOD'].lower()) | ||||
|             else: | ||||
|                 request_method = self.get | ||||
|                 data = QueryDict(url.query) | ||||
|                 content_type = None | ||||
|  | ||||
|             response = request_method(path, data=data, content_type=content_type, follow=False, **extra) | ||||
|             response.redirect_chain = redirect_chain | ||||
|  | ||||
|             if redirect_chain[-1] in redirect_chain[:-1]: | ||||
|   | ||||
| @@ -206,7 +206,7 @@ Templates | ||||
| Tests | ||||
| ~~~~~ | ||||
|  | ||||
| * ... | ||||
| * Added test :class:`~django.test.Client` support for 307 and 308 redirects. | ||||
|  | ||||
| URLs | ||||
| ~~~~ | ||||
|   | ||||
| @@ -19,6 +19,7 @@ testing against the contexts and templates produced by a view, | ||||
| rather than the HTML rendered to the end-user. | ||||
|  | ||||
| """ | ||||
| import itertools | ||||
| import tempfile | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
| @@ -202,6 +203,39 @@ class ClientTest(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(response.request['PATH_INFO'], '/accounts/login/') | ||||
|  | ||||
|     def test_follow_307_and_308_redirect(self): | ||||
|         """ | ||||
|         A 307 or 308 redirect preserves the request method after the redirect. | ||||
|         """ | ||||
|         methods = ('get', 'post', 'head', 'options', 'put', 'patch', 'delete', 'trace') | ||||
|         codes = (307, 308) | ||||
|         for method, code in itertools.product(methods, codes): | ||||
|             with self.subTest(method=method, code=code): | ||||
|                 req_method = getattr(self.client, method) | ||||
|                 response = req_method('/redirect_view_%s/' % code, data={'value': 'test'}, follow=True) | ||||
|                 self.assertEqual(response.status_code, 200) | ||||
|                 self.assertEqual(response.request['PATH_INFO'], '/post_view/') | ||||
|                 self.assertEqual(response.request['REQUEST_METHOD'], method.upper()) | ||||
|  | ||||
|     def test_follow_307_and_308_preserves_post_data(self): | ||||
|         for code in (307, 308): | ||||
|             with self.subTest(code=code): | ||||
|                 response = self.client.post('/redirect_view_%s/' % code, data={'value': 'test'}, follow=True) | ||||
|                 self.assertContains(response, 'test is the value') | ||||
|  | ||||
|     def test_follow_307_and_308_preserves_put_body(self): | ||||
|         for code in (307, 308): | ||||
|             with self.subTest(code=code): | ||||
|                 response = self.client.put('/redirect_view_%s/?to=/put_view/' % code, data='a=b', follow=True) | ||||
|                 self.assertContains(response, 'a=b is the body') | ||||
|  | ||||
|     def test_follow_307_and_308_preserves_get_params(self): | ||||
|         data = {'var': 30, 'to': '/get_view/'} | ||||
|         for code in (307, 308): | ||||
|             with self.subTest(code=code): | ||||
|                 response = self.client.get('/redirect_view_%s/' % code, data=data, follow=True) | ||||
|                 self.assertContains(response, '30 is the value') | ||||
|  | ||||
|     def test_redirect_http(self): | ||||
|         "GET a URL that redirects to an http URI" | ||||
|         response = self.client.get('/http_redirect_view/', follow=True) | ||||
|   | ||||
| @@ -8,10 +8,13 @@ urlpatterns = [ | ||||
|     url(r'^upload_view/$', views.upload_view, name='upload_view'), | ||||
|     url(r'^get_view/$', views.get_view, name='get_view'), | ||||
|     url(r'^post_view/$', views.post_view), | ||||
|     url(r'^put_view/$', views.put_view), | ||||
|     url(r'^trace_view/$', views.trace_view), | ||||
|     url(r'^header_view/$', views.view_with_header), | ||||
|     url(r'^raw_post_view/$', views.raw_post_view), | ||||
|     url(r'^redirect_view/$', views.redirect_view), | ||||
|     url(r'^redirect_view_307/$', views.method_saving_307_redirect_view), | ||||
|     url(r'^redirect_view_308/$', views.method_saving_308_redirect_view), | ||||
|     url(r'^secure_view/$', views.view_with_secure), | ||||
|     url(r'^permanent_redirect_view/$', RedirectView.as_view(url='/get_view/', permanent=True)), | ||||
|     url(r'^temporary_redirect_view/$', RedirectView.as_view(url='/get_view/', permanent=False)), | ||||
|   | ||||
| @@ -49,6 +49,16 @@ def trace_view(request): | ||||
|         return HttpResponse(t.render(c)) | ||||
|  | ||||
|  | ||||
| def put_view(request): | ||||
|     if request.method == 'PUT': | ||||
|         t = Template('Data received: {{ data }} is the body.', name='PUT Template') | ||||
|         c = Context({'data': request.body.decode()}) | ||||
|     else: | ||||
|         t = Template('Viewing GET page.', name='Empty GET Template') | ||||
|         c = Context() | ||||
|     return HttpResponse(t.render(c)) | ||||
|  | ||||
|  | ||||
| def post_view(request): | ||||
|     """A view that expects a POST, and returns a different template depending | ||||
|     on whether any POST data is available | ||||
| @@ -99,6 +109,20 @@ def redirect_view(request): | ||||
|     return HttpResponseRedirect('/get_view/' + query) | ||||
|  | ||||
|  | ||||
| def _post_view_redirect(request, status_code): | ||||
|     """Redirect to /post_view/ using the status code.""" | ||||
|     redirect_to = request.GET.get('to', '/post_view/') | ||||
|     return HttpResponseRedirect(redirect_to, status=status_code) | ||||
|  | ||||
|  | ||||
| def method_saving_307_redirect_view(request): | ||||
|     return _post_view_redirect(request, 307) | ||||
|  | ||||
|  | ||||
| def method_saving_308_redirect_view(request): | ||||
|     return _post_view_redirect(request, 308) | ||||
|  | ||||
|  | ||||
| def view_with_secure(request): | ||||
|     "A view that indicates if the request was secure" | ||||
|     response = HttpResponse() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user