mirror of
https://github.com/django/django.git
synced 2025-06-05 11:39:13 +00:00
Fixed #27999 -- Added test client support for HTTP 307 and 308 redirects.
This commit is contained in:
parent
0f0a07ac27
commit
272f685794
@ -5,6 +5,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from http import HTTPStatus
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from urllib.parse import unquote_to_bytes, urljoin, urlparse, urlsplit
|
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."""
|
"""Request a response from the server using GET."""
|
||||||
response = super().get(path, data=data, secure=secure, **extra)
|
response = super().get(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def post(self, path, data=None, content_type=MULTIPART_CONTENT,
|
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."""
|
"""Request a response from the server using POST."""
|
||||||
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def head(self, path, data=None, follow=False, secure=False, **extra):
|
def head(self, path, data=None, follow=False, secure=False, **extra):
|
||||||
"""Request a response from the server using HEAD."""
|
"""Request a response from the server using HEAD."""
|
||||||
response = super().head(path, data=data, secure=secure, **extra)
|
response = super().head(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def options(self, path, data='', content_type='application/octet-stream',
|
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."""
|
"""Request a response from the server using OPTIONS."""
|
||||||
response = super().options(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().options(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def put(self, path, data='', content_type='application/octet-stream',
|
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."""
|
"""Send a resource to the server using PUT."""
|
||||||
response = super().put(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().put(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def patch(self, path, data='', content_type='application/octet-stream',
|
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."""
|
"""Send a resource to the server using PATCH."""
|
||||||
response = super().patch(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().patch(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def delete(self, path, data='', content_type='application/octet-stream',
|
def delete(self, path, data='', content_type='application/octet-stream',
|
||||||
@ -559,14 +560,14 @@ class Client(RequestFactory):
|
|||||||
"""Send a DELETE request to the server."""
|
"""Send a DELETE request to the server."""
|
||||||
response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra)
|
response = super().delete(path, data=data, content_type=content_type, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, content_type=content_type, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def trace(self, path, data='', follow=False, secure=False, **extra):
|
def trace(self, path, data='', follow=False, secure=False, **extra):
|
||||||
"""Send a TRACE request to the server."""
|
"""Send a TRACE request to the server."""
|
||||||
response = super().trace(path, data=data, secure=secure, **extra)
|
response = super().trace(path, data=data, secure=secure, **extra)
|
||||||
if follow:
|
if follow:
|
||||||
response = self._handle_redirects(response, **extra)
|
response = self._handle_redirects(response, data=data, **extra)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def login(self, **credentials):
|
def login(self, **credentials):
|
||||||
@ -648,12 +649,19 @@ class Client(RequestFactory):
|
|||||||
response._json = json.loads(response.content.decode(), **extra)
|
response._json = json.loads(response.content.decode(), **extra)
|
||||||
return response._json
|
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.
|
Follow any redirects by requesting responses from the server using GET.
|
||||||
"""
|
"""
|
||||||
response.redirect_chain = []
|
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
|
response_url = response.url
|
||||||
redirect_chain = response.redirect_chain
|
redirect_chain = response.redirect_chain
|
||||||
redirect_chain.append((response_url, response.status_code))
|
redirect_chain.append((response_url, response.status_code))
|
||||||
@ -671,7 +679,15 @@ class Client(RequestFactory):
|
|||||||
if not path.startswith('/'):
|
if not path.startswith('/'):
|
||||||
path = urljoin(response.request['PATH_INFO'], path)
|
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
|
response.redirect_chain = redirect_chain
|
||||||
|
|
||||||
if redirect_chain[-1] in redirect_chain[:-1]:
|
if redirect_chain[-1] in redirect_chain[:-1]:
|
||||||
|
@ -206,7 +206,7 @@ Templates
|
|||||||
Tests
|
Tests
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
* ...
|
* Added test :class:`~django.test.Client` support for 307 and 308 redirects.
|
||||||
|
|
||||||
URLs
|
URLs
|
||||||
~~~~
|
~~~~
|
||||||
|
@ -19,6 +19,7 @@ testing against the contexts and templates produced by a view,
|
|||||||
rather than the HTML rendered to the end-user.
|
rather than the HTML rendered to the end-user.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import itertools
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
@ -202,6 +203,39 @@ class ClientTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.request['PATH_INFO'], '/accounts/login/')
|
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):
|
def test_redirect_http(self):
|
||||||
"GET a URL that redirects to an http URI"
|
"GET a URL that redirects to an http URI"
|
||||||
response = self.client.get('/http_redirect_view/', follow=True)
|
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'^upload_view/$', views.upload_view, name='upload_view'),
|
||||||
url(r'^get_view/$', views.get_view, name='get_view'),
|
url(r'^get_view/$', views.get_view, name='get_view'),
|
||||||
url(r'^post_view/$', views.post_view),
|
url(r'^post_view/$', views.post_view),
|
||||||
|
url(r'^put_view/$', views.put_view),
|
||||||
url(r'^trace_view/$', views.trace_view),
|
url(r'^trace_view/$', views.trace_view),
|
||||||
url(r'^header_view/$', views.view_with_header),
|
url(r'^header_view/$', views.view_with_header),
|
||||||
url(r'^raw_post_view/$', views.raw_post_view),
|
url(r'^raw_post_view/$', views.raw_post_view),
|
||||||
url(r'^redirect_view/$', views.redirect_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'^secure_view/$', views.view_with_secure),
|
||||||
url(r'^permanent_redirect_view/$', RedirectView.as_view(url='/get_view/', permanent=True)),
|
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)),
|
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))
|
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):
|
def post_view(request):
|
||||||
"""A view that expects a POST, and returns a different template depending
|
"""A view that expects a POST, and returns a different template depending
|
||||||
on whether any POST data is available
|
on whether any POST data is available
|
||||||
@ -99,6 +109,20 @@ def redirect_view(request):
|
|||||||
return HttpResponseRedirect('/get_view/' + query)
|
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):
|
def view_with_secure(request):
|
||||||
"A view that indicates if the request was secure"
|
"A view that indicates if the request was secure"
|
||||||
response = HttpResponse()
|
response = HttpResponse()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user