diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index e40eb2fa59..e85f839710 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -23,7 +23,6 @@ logger = logging.getLogger('django.request') class BaseHandler(object): # Changes that are always applied to a response (in this order). response_fixes = [ - http.fix_location_header, http.conditional_content_removal, ] diff --git a/django/http/__init__.py b/django/http/__init__.py index cf1e5d3950..6f433b39b8 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -8,7 +8,7 @@ from django.http.response import ( HttpResponseNotFound, HttpResponseNotAllowed, HttpResponseGone, HttpResponseServerError, Http404, BadHeaderError, JsonResponse, ) -from django.http.utils import fix_location_header, conditional_content_removal +from django.http.utils import conditional_content_removal __all__ = [ 'SimpleCookie', 'parse_cookie', 'HttpRequest', 'QueryDict', @@ -17,6 +17,6 @@ __all__ = [ 'HttpResponsePermanentRedirect', 'HttpResponseNotModified', 'HttpResponseBadRequest', 'HttpResponseForbidden', 'HttpResponseNotFound', 'HttpResponseNotAllowed', 'HttpResponseGone', 'HttpResponseServerError', - 'Http404', 'BadHeaderError', 'fix_location_header', 'JsonResponse', - 'FileResponse', 'conditional_content_removal', + 'Http404', 'BadHeaderError', 'JsonResponse', 'FileResponse', + 'conditional_content_removal', ] diff --git a/django/http/utils.py b/django/http/utils.py index 5dcac06dcf..3ea71cb892 100644 --- a/django/http/utils.py +++ b/django/http/utils.py @@ -9,19 +9,6 @@ Functions that modify an HTTP request or response in some way. # universally applicable. -def fix_location_header(request, response): - """ - Ensures that we always use an absolute URI in any location header in the - response. This is required by RFC 2616, section 14.30. - - Code constructing response objects is free to insert relative paths, as - this function converts them to absolute paths. - """ - if 'Location' in response: - response['Location'] = request.build_absolute_uri(response['Location']) - return response - - def conditional_content_removal(request, response): """ Removes the content of responses for HEAD requests, 1xx, 204 and 304 diff --git a/django/middleware/common.py b/django/middleware/common.py index c1414db375..6c5843220f 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -86,7 +86,7 @@ class CommonMiddleware(object): if new_url == old_url: # No redirects required. return - if new_url[0]: + if new_url[0] != old_url[0]: newurl = "%s://%s%s" % ( request.scheme, new_url[0], urlquote(new_url[1])) diff --git a/django/middleware/locale.py b/django/middleware/locale.py index 647f4f9e62..1a5b086148 100644 --- a/django/middleware/locale.py +++ b/django/middleware/locale.py @@ -40,16 +40,12 @@ class LocaleMiddleware(object): if path_valid: script_prefix = get_script_prefix() - language_url = "%s://%s%s" % ( - request.scheme, - request.get_host(), - # insert language after the script prefix and before the - # rest of the URL - request.get_full_path().replace( - script_prefix, - '%s%s/' % (script_prefix, language), - 1 - ) + # Insert language after the script prefix and before the + # rest of the URL + language_url = request.get_full_path().replace( + script_prefix, + '%s%s/' % (script_prefix, language), + 1 ) return self.response_redirect_class(language_url) diff --git a/django/test/testcases.py b/django/test/testcases.py index 663760acd8..461ab79144 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -37,7 +37,9 @@ from django.test.utils import ( override_settings, ) from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.deprecation import ( + RemovedInDjango20Warning, RemovedInDjango21Warning, +) from django.utils.encoding import force_text from django.utils.six.moves.urllib.parse import ( unquote, urlparse, urlsplit, urlunsplit, @@ -249,11 +251,15 @@ class SimpleTestCase(unittest.TestCase): TestClient to do a request (use fetch_redirect_response=False to check such links without fetching them). """ + if host is not None: + warnings.warn( + "The host argument is deprecated and no longer used by assertRedirects", + RemovedInDjango21Warning, stacklevel=2 + ) + if msg_prefix: msg_prefix += ": " - e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) - if hasattr(response, 'redirect_chain'): # The request was a followed redirect self.assertTrue(len(response.redirect_chain) > 0, @@ -295,10 +301,18 @@ class SimpleTestCase(unittest.TestCase): " response code was %d (expected %d)" % (path, redirect_response.status_code, target_status_code)) - e_scheme = e_scheme if e_scheme else scheme or 'http' - e_netloc = e_netloc if e_netloc else host or 'testserver' - expected_url = urlunsplit((e_scheme, e_netloc, e_path, e_query, - e_fragment)) + if url != expected_url: + # For temporary backwards compatibility, try to compare with a relative url + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) + relative_url = urlunsplit(('', '', e_path, e_query, e_fragment)) + if url == relative_url: + warnings.warn( + "assertRedirects had to strip the scheme and domain from the " + "expected URL, as it was always added automatically to URLs " + "before Django 1.9. Please update your expected URLs by " + "removing the scheme and domain.", + RemovedInDjango21Warning, stacklevel=2) + expected_url = relative_url self.assertEqual(url, expected_url, msg_prefix + "Response redirected to '%s', expected '%s'" % diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index cb5d989450..db351ede16 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -22,6 +22,10 @@ details on these changes. * The ``assignment_tag`` helper will be removed. +* The ``host`` argument to ``assertsRedirects`` will be removed. The + compatibility layer which allows absolute URLs to be considered equal to + relative ones when the path is identical will also be removed. + .. _deprecation-removed-in-2.0: 2.0 diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index a555ba76eb..36588cc18a 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -824,8 +824,10 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in The first argument to the constructor is required -- the path to redirect to. This can be a fully qualified URL - (e.g. ``'http://www.yahoo.com/search/'``) or an absolute path with no - domain (e.g. ``'/search/'``). See :class:`HttpResponse` for other optional + (e.g. ``'http://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. .. attribute:: HttpResponseRedirect.url diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 2c787fc40a..c290739c95 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -266,6 +266,21 @@ a directory. Now, Django only silences the exception if the template source does not exist. All other situations result in the original ``IOError`` being raised. +HTTP redirects no longer forced to absolute URIs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Relative redirects are no longer converted to absolute URIs. :rfc:`2616` +required the ``Location`` header in redirect responses to be an absolute URI, +but it has been superseded by :rfc:`7231` which allows relative URIs in +``Location``, recognizing the actual practice of user agents, almost all of +which support them. + +Consequently, the expected URLs passed to ``assertRedirects`` should generally +no longer include the scheme and domain part of the URLs. For example, +``self.assertRedirects(response, 'http://testserver/some-url/')`` should be +replaced by ``self.assertRedirects(response, '/some-url/')`` (unless the +redirection specifically contained an absolute URL, of course). + Miscellaneous ~~~~~~~~~~~~~ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index f3b78d3b31..67f58cccf3 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1398,7 +1398,7 @@ your test suite. You can use this as a context manager in the same way as :meth:`~SimpleTestCase.assertTemplateUsed`. -.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix='', fetch_redirect_response=True) +.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) Asserts that the response returned a ``status_code`` redirect status, redirected to ``expected_url`` (including any ``GET`` data), and that the @@ -1408,14 +1408,6 @@ your test suite. ``target_status_code`` will be the url and status code for the final point of the redirect chain. - The ``host`` argument sets a default host if ``expected_url`` doesn't - include one (e.g. ``"/bar/"``). If ``expected_url`` is an absolute URL that - includes a host (e.g. ``"http://testhost/bar/"``), the ``host`` parameter - will be ignored. Note that the test client doesn't support fetching external - URLs, but the parameter may be useful if you are testing with a custom HTTP - host (for example, initializing the test client with - ``Client(HTTP_HOST="testhost")``. - If ``fetch_redirect_response`` is ``False``, the final page won't be loaded. Since the test client can't fetch externals URLs, this is particularly useful if ``expected_url`` isn't part of your Django app. @@ -1425,6 +1417,11 @@ your test suite. the original request's scheme is used. If present, the scheme in ``expected_url`` is the one used to make the comparisons to. + .. deprecated:: 1.9 + + The ``host`` argument is deprecated, as redirections are no longer + forced to be absolute URLs. + .. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None) Asserts that the strings ``html1`` and ``html2`` are equal. The comparison diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index 91d0d20ff7..a48c84cf27 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -87,7 +87,7 @@ class BasicFormTests(TestCase): def test_post_data(self): res = self.client.post('/contact/', {'name': "Me", 'message': "Hello"}) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') class ModelFormMixinTests(TestCase): @@ -117,7 +117,7 @@ class CreateViewTests(TestCase): res = self.client.post('/edit/authors/create/', {'name': 'Randall Munroe', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), ['']) def test_create_invalid(self): @@ -133,14 +133,14 @@ class CreateViewTests(TestCase): {'name': 'Rene Magritte'}) self.assertEqual(res.status_code, 302) artist = Artist.objects.get(name='Rene Magritte') - self.assertRedirects(res, 'http://testserver/detail/artist/%d/' % artist.pk) + self.assertRedirects(res, '/detail/artist/%d/' % artist.pk) self.assertQuerysetEqual(Artist.objects.all(), ['']) def test_create_with_redirect(self): res = self.client.post('/edit/authors/create/redirect/', {'name': 'Randall Munroe', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/edit/authors/create/') + self.assertRedirects(res, '/edit/authors/create/') self.assertQuerysetEqual(Author.objects.all(), ['']) @ignore_warnings(category=RemovedInDjango20Warning) @@ -152,7 +152,7 @@ class CreateViewTests(TestCase): self.assertQuerysetEqual(Author.objects.all(), ['']) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk) + self.assertRedirects(res, '/edit/author/%d/update/' % pk) # Also test with escaped chars in URL res = self.client.post( '/edit/authors/create/interpolate_redirect_nonascii/', @@ -160,7 +160,7 @@ class CreateViewTests(TestCase): ) self.assertEqual(res.status_code, 302) pk = Author.objects.get(name='John Doe').pk - self.assertRedirects(res, 'http://testserver/%C3%A9dit/author/{}/update/'.format(pk)) + self.assertRedirects(res, '/%C3%A9dit/author/{}/update/'.format(pk)) def test_create_with_special_properties(self): res = self.client.get('/edit/authors/create/special/') @@ -189,7 +189,7 @@ class CreateViewTests(TestCase): res = self.client.post('/edit/authors/create/restricted/', {'name': 'Randall Munroe', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/') + self.assertRedirects(res, '/accounts/login/?next=/edit/authors/create/restricted/') def test_create_view_with_restricted_fields(self): @@ -249,7 +249,7 @@ class UpdateViewTests(TestCase): res = self.client.post('/edit/author/%d/update/' % a.pk, {'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), ['']) def test_update_invalid(self): @@ -269,7 +269,7 @@ class UpdateViewTests(TestCase): res = self.client.post('/edit/artists/%d/update/' % a.pk, {'name': 'Rene Magritte'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/detail/artist/%d/' % a.pk) + self.assertRedirects(res, '/detail/artist/%d/' % a.pk) self.assertQuerysetEqual(Artist.objects.all(), ['']) def test_update_with_redirect(self): @@ -280,7 +280,7 @@ class UpdateViewTests(TestCase): res = self.client.post('/edit/author/%d/update/redirect/' % a.pk, {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/edit/authors/create/') + self.assertRedirects(res, '/edit/authors/create/') self.assertQuerysetEqual(Author.objects.all(), ['']) @ignore_warnings(category=RemovedInDjango20Warning) @@ -296,7 +296,7 @@ class UpdateViewTests(TestCase): self.assertQuerysetEqual(Author.objects.all(), ['']) self.assertEqual(res.status_code, 302) pk = Author.objects.first().pk - self.assertRedirects(res, 'http://testserver/edit/author/%d/update/' % pk) + self.assertRedirects(res, '/edit/author/%d/update/' % pk) # Also test with escaped chars in URL res = self.client.post( '/edit/author/%d/update/interpolate_redirect_nonascii/' % a.pk, @@ -304,7 +304,7 @@ class UpdateViewTests(TestCase): ) self.assertEqual(res.status_code, 302) pk = Author.objects.get(name='John Doe').pk - self.assertRedirects(res, 'http://testserver/%C3%A9dit/author/{}/update/'.format(pk)) + self.assertRedirects(res, '/%C3%A9dit/author/{}/update/'.format(pk)) def test_update_with_special_properties(self): a = Author.objects.create( @@ -322,7 +322,7 @@ class UpdateViewTests(TestCase): res = self.client.post('/edit/author/%d/update/special/' % a.pk, {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/detail/author/%d/' % a.pk) + self.assertRedirects(res, '/detail/author/%d/' % a.pk) self.assertQuerysetEqual(Author.objects.all(), ['']) def test_update_without_redirect(self): @@ -354,7 +354,7 @@ class UpdateViewTests(TestCase): res = self.client.post('/edit/author/update/', {'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'}) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), ['']) @@ -372,7 +372,7 @@ class DeleteViewTests(TestCase): # Deletion with POST res = self.client.post('/edit/author/%d/delete/' % a.pk) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), []) def test_delete_by_delete(self): @@ -380,14 +380,14 @@ class DeleteViewTests(TestCase): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) res = self.client.delete('/edit/author/%d/delete/' % a.pk) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), []) def test_delete_with_redirect(self): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) res = self.client.post('/edit/author/%d/delete/redirect/' % a.pk) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/edit/authors/create/') + self.assertRedirects(res, '/edit/authors/create/') self.assertQuerysetEqual(Author.objects.all(), []) @ignore_warnings(category=RemovedInDjango20Warning) @@ -395,13 +395,13 @@ class DeleteViewTests(TestCase): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) res = self.client.post('/edit/author/%d/delete/interpolate_redirect/' % a.pk) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/edit/authors/create/?deleted=%d' % a.pk) + self.assertRedirects(res, '/edit/authors/create/?deleted=%d' % a.pk) self.assertQuerysetEqual(Author.objects.all(), []) # Also test with escaped chars in URL a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) res = self.client.post('/edit/author/{}/delete/interpolate_redirect_nonascii/'.format(a.pk)) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/%C3%A9dit/authors/create/?deleted={}'.format(a.pk)) + self.assertRedirects(res, '/%C3%A9dit/authors/create/?deleted={}'.format(a.pk)) def test_delete_with_special_properties(self): a = Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'}) @@ -414,7 +414,7 @@ class DeleteViewTests(TestCase): res = self.client.post('/edit/author/%d/delete/special/' % a.pk) self.assertEqual(res.status_code, 302) - self.assertRedirects(res, 'http://testserver/list/authors/') + self.assertRedirects(res, '/list/authors/') self.assertQuerysetEqual(Author.objects.all(), []) def test_delete_without_redirect(self): diff --git a/tests/http_utils/tests.py b/tests/http_utils/tests.py index 2a2b29bbfb..a75cc7ab21 100644 --- a/tests/http_utils/tests.py +++ b/tests/http_utils/tests.py @@ -3,10 +3,8 @@ from __future__ import unicode_literals import gzip import io -from django.http import ( - HttpRequest, HttpResponse, HttpResponseRedirect, StreamingHttpResponse, -) -from django.http.utils import conditional_content_removal, fix_location_header +from django.http import HttpRequest, HttpResponse, StreamingHttpResponse +from django.http.utils import conditional_content_removal from django.test import TestCase @@ -71,15 +69,3 @@ class HttpUtilTests(TestCase): res = StreamingHttpResponse(['abc']) conditional_content_removal(req, res) self.assertEqual(b''.join(res), b'') - - def test_fix_location_without_get_host(self): - """ - Tests that you can return an absolute redirect when the request - host is not in ALLOWED_HOSTS. Issue #20472 - """ - request = HttpRequest() - - def bomb(): - self.assertTrue(False) - request.get_host = bomb - fix_location_header(request, HttpResponseRedirect('http://example.com')) diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py index 07a01f6d9b..f2949ed596 100644 --- a/tests/i18n/patterns/tests.py +++ b/tests/i18n/patterns/tests.py @@ -248,7 +248,7 @@ class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase): def test_en_redirect(self): response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en', follow=True) # target status code of 301 because of CommonMiddleware redirecting - self.assertIn(('http://testserver/en/account/register/', 301), response.redirect_chain) + self.assertIn(('/en/account/register/', 301), response.redirect_chain) self.assertRedirects(response, '/en/account/register/', 302) response = self.client.get('/prefixed.xml', HTTP_ACCEPT_LANGUAGE='en', follow=True) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 1490094ef7..c1e0f77d19 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -64,7 +64,7 @@ class CommonMiddlewareTest(TestCase): request = self.rf.get('/slash') r = CommonMiddleware().process_request(request) self.assertEqual(r.status_code, 301) - self.assertEqual(r.url, 'http://testserver/slash/') + self.assertEqual(r.url, '/slash/') @override_settings(APPEND_SLASH=True, DEBUG=True) def test_append_slash_no_redirect_on_POST_in_DEBUG(self): @@ -106,7 +106,7 @@ class CommonMiddlewareTest(TestCase): self.assertEqual(r.status_code, 301) self.assertEqual( r.url, - 'http://testserver/needsquoting%23/') + '/needsquoting%23/') @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) def test_prepend_www(self): @@ -174,7 +174,7 @@ class CommonMiddlewareTest(TestCase): self.assertIsNotNone(r, "CommonMiddlware failed to return APPEND_SLASH redirect using request.urlconf") self.assertEqual(r.status_code, 301) - self.assertEqual(r.url, 'http://testserver/customurlconf/slash/') + self.assertEqual(r.url, '/customurlconf/slash/') @override_settings(APPEND_SLASH=True, DEBUG=True) def test_append_slash_no_redirect_on_POST_in_DEBUG_custom_urlconf(self): @@ -212,7 +212,7 @@ class CommonMiddlewareTest(TestCase): self.assertEqual(r.status_code, 301) self.assertEqual( r.url, - 'http://testserver/customurlconf/needsquoting%23/') + '/customurlconf/needsquoting%23/') @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) def test_prepend_www_custom_urlconf(self): @@ -264,7 +264,7 @@ class CommonMiddlewareTest(TestCase): request = self.rf.get('/slash') r = CommonMiddleware().process_request(request) self.assertEqual(r.status_code, 301) - self.assertEqual(r.url, 'http://testserver/slash/') + self.assertEqual(r.url, '/slash/') self.assertIsInstance(r, HttpResponsePermanentRedirect) def test_response_redirect_class_subclass(self): @@ -274,7 +274,7 @@ class CommonMiddlewareTest(TestCase): request = self.rf.get('/slash') r = MyCommonMiddleware().process_request(request) self.assertEqual(r.status_code, 302) - self.assertEqual(r.url, 'http://testserver/slash/') + self.assertEqual(r.url, '/slash/') self.assertIsInstance(r, HttpResponseRedirect) diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index 7190ea64b0..39f5e5ee18 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -176,40 +176,27 @@ class ClientTest(TestCase): def test_redirect(self): "GET a URL that redirects elsewhere" response = self.client.get('/redirect_view/') - # Check that the response was a 302 (redirect) and that - # assertRedirect() understands to put an implicit http://testserver/ in - # front of non-absolute URLs. + # Check that the response was a 302 (redirect) self.assertRedirects(response, '/get_view/') - host = 'django.testserver' - client_providing_host = Client(HTTP_HOST=host) - response = client_providing_host.get('/redirect_view/') - # Check that the response was a 302 (redirect) with absolute URI - self.assertRedirects(response, '/get_view/', host=host) - def test_redirect_with_query(self): "GET a URL that redirects with given GET parameters" response = self.client.get('/redirect_view/', {'var': 'value'}) # Check if parameters are intact - self.assertRedirects(response, 'http://testserver/get_view/?var=value') + self.assertRedirects(response, '/get_view/?var=value') def test_permanent_redirect(self): "GET a URL that redirects permanently elsewhere" response = self.client.get('/permanent_redirect_view/') # Check that the response was a 301 (permanent redirect) - self.assertRedirects(response, 'http://testserver/get_view/', status_code=301) - - client_providing_host = Client(HTTP_HOST='django.testserver') - response = client_providing_host.get('/permanent_redirect_view/') - # Check that the response was a 301 (permanent redirect) with absolute URI - self.assertRedirects(response, 'http://django.testserver/get_view/', status_code=301) + self.assertRedirects(response, '/get_view/', status_code=301) def test_temporary_redirect(self): "GET a URL that does a non-permanent redirect" response = self.client.get('/temporary_redirect_view/') # Check that the response was a 302 (non-permanent redirect) - self.assertRedirects(response, 'http://testserver/get_view/', status_code=302) + self.assertRedirects(response, '/get_view/', status_code=302) def test_redirect_to_strange_location(self): "GET a URL that redirects to a non-200 page" @@ -217,12 +204,12 @@ class ClientTest(TestCase): # Check that the response was a 302, and that # the attempt to get the redirection location returned 301 when retrieved - self.assertRedirects(response, 'http://testserver/permanent_redirect_view/', target_status_code=301) + self.assertRedirects(response, '/permanent_redirect_view/', target_status_code=301) def test_follow_redirect(self): "A URL that redirects can be followed to termination." response = self.client.get('/double_redirect_view/', follow=True) - self.assertRedirects(response, 'http://testserver/get_view/', status_code=302, target_status_code=200) + self.assertRedirects(response, '/get_view/', status_code=302, target_status_code=200) self.assertEqual(len(response.redirect_chain), 2) def test_redirect_http(self): @@ -364,7 +351,7 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/login_protected_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/login_protected_view/') + self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/') # Log in login = self.client.login(username='testclient', password='password') @@ -380,7 +367,7 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/login_protected_method_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/login_protected_method_view/') + self.assertRedirects(response, '/accounts/login/?next=/login_protected_method_view/') # Log in login = self.client.login(username='testclient', password='password') @@ -396,7 +383,7 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/login_protected_view_custom_redirect/') - self.assertRedirects(response, 'http://testserver/accounts/login/?redirect_to=/login_protected_view_custom_redirect/') + self.assertRedirects(response, '/accounts/login/?redirect_to=/login_protected_view_custom_redirect/') # Log in login = self.client.login(username='testclient', password='password') @@ -434,7 +421,7 @@ class ClientTest(TestCase): # Request a page that requires a login response = self.client.get('/login_protected_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/login_protected_view/') + self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/') @override_settings(SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies") def test_logout_cookie_sessions(self): @@ -445,7 +432,7 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/permission_protected_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/permission_protected_view/') + self.assertRedirects(response, '/accounts/login/?next=/permission_protected_view/') # Log in login = self.client.login(username='testclient', password='password') @@ -453,7 +440,7 @@ class ClientTest(TestCase): # Log in with wrong permissions. Should result in 302. response = self.client.get('/permission_protected_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/permission_protected_view/') + self.assertRedirects(response, '/accounts/login/?next=/permission_protected_view/') # TODO: Log in with right permissions and request the page again @@ -477,7 +464,7 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/permission_protected_method_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/permission_protected_method_view/') + self.assertRedirects(response, '/accounts/login/?next=/permission_protected_method_view/') # Log in login = self.client.login(username='testclient', password='password') @@ -485,7 +472,7 @@ class ClientTest(TestCase): # Log in with wrong permissions. Should result in 302. response = self.client.get('/permission_protected_method_view/') - self.assertRedirects(response, 'http://testserver/accounts/login/?next=/permission_protected_method_view/') + self.assertRedirects(response, '/accounts/login/?next=/permission_protected_method_view/') # TODO: Log in with right permissions and request the page again diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 663d3cd4da..cddb618792 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -18,7 +18,9 @@ from django.test import Client, TestCase, ignore_warnings, override_settings from django.test.client import RedirectCycleError, RequestFactory, encode_file from django.test.utils import ContextList, str_prefix from django.utils._os import upath -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.deprecation import ( + RemovedInDjango20Warning, RemovedInDjango21Warning, +) from django.utils.translation import ugettext_lazy from .models import CustomUser @@ -342,12 +344,12 @@ class AssertRedirectsTests(TestCase): try: self.assertRedirects(response, '/get_view/') except AssertionError as e: - self.assertIn("Response redirected to 'http://testserver/get_view/?var=value', expected 'http://testserver/get_view/'", str(e)) + self.assertIn("Response redirected to '/get_view/?var=value', expected '/get_view/'", str(e)) try: self.assertRedirects(response, '/get_view/', msg_prefix='abc') except AssertionError as e: - self.assertIn("abc: Response redirected to 'http://testserver/get_view/?var=value', expected 'http://testserver/get_view/'", str(e)) + self.assertIn("abc: Response redirected to '/get_view/?var=value', expected '/get_view/'", str(e)) def test_incorrect_target(self): "An assertion is raised if the response redirects to another target" @@ -380,7 +382,7 @@ class AssertRedirectsTests(TestCase): status_code=302, target_status_code=200) self.assertEqual(len(response.redirect_chain), 1) - self.assertEqual(response.redirect_chain[0], ('http://testserver/no_template_view/', 302)) + self.assertEqual(response.redirect_chain[0], ('/no_template_view/', 302)) def test_multiple_redirect_chain(self): "You can follow a redirect chain of multiple redirects" @@ -389,9 +391,9 @@ class AssertRedirectsTests(TestCase): status_code=302, target_status_code=200) self.assertEqual(len(response.redirect_chain), 3) - self.assertEqual(response.redirect_chain[0], ('http://testserver/redirects/further/', 302)) - self.assertEqual(response.redirect_chain[1], ('http://testserver/redirects/further/more/', 302)) - self.assertEqual(response.redirect_chain[2], ('http://testserver/no_template_view/', 302)) + self.assertEqual(response.redirect_chain[0], ('/redirects/further/', 302)) + self.assertEqual(response.redirect_chain[1], ('/redirects/further/more/', 302)) + self.assertEqual(response.redirect_chain[2], ('/no_template_view/', 302)) def test_redirect_chain_to_non_existent(self): "You can follow a chain to a non-existent view" @@ -507,21 +509,24 @@ class AssertRedirectsTests(TestCase): def test_redirect_scheme(self): "An assertion is raised if the response doesn't have the scheme specified in expected_url" - # Assure that original request scheme is preserved if no scheme specified in the redirect location - response = self.client.get('/redirect_view/', secure=True) - self.assertRedirects(response, 'https://testserver/get_view/') - # For all possible True/False combinations of follow and secure for follow, secure in itertools.product([True, False], repeat=2): # always redirects to https response = self.client.get('/https_redirect_view/', follow=follow, secure=secure) - # no scheme to compare too, always succeeds - self.assertRedirects(response, '/secure_view/', status_code=302) # the goal scheme is https self.assertRedirects(response, 'https://testserver/secure_view/', status_code=302) with self.assertRaises(AssertionError): self.assertRedirects(response, 'http://testserver/secure_view/', status_code=302) + @ignore_warnings(category=RemovedInDjango21Warning) + def test_full_path_in_expected_urls(self): + """ + Test that specifying a full URL as assertRedirects expected_url still + work as backwards compatible behavior until Django 2.1. + """ + response = self.client.get('/redirect_view/') + self.assertRedirects(response, 'http://testserver/get_view/') + @override_settings(ROOT_URLCONF='test_client_regress.urls') class AssertFormErrorTests(TestCase): @@ -852,7 +857,7 @@ class LoginTests(TestDataMixin, TestCase): # At this points, the self.client isn't logged in. # Check that assertRedirects uses the original client, not the # default client. - self.assertRedirects(response, "http://testserver/get_view/") + self.assertRedirects(response, "/get_view/") @override_settings( diff --git a/tests/view_tests/generic_urls.py b/tests/view_tests/generic_urls.py index 9e73a07649..ee1c7d123b 100644 --- a/tests/view_tests/generic_urls.py +++ b/tests/view_tests/generic_urls.py @@ -32,7 +32,6 @@ urlpatterns = [ url(r'^accounts/logout/$', auth_views.logout), # Special URLs for particular regression cases. - url('^中文/$', views.redirect), url('^中文/target/$', views.index_page), ] diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index ef11d7d82a..ae3025b5f1 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -31,7 +31,7 @@ class I18NTests(TestCase): for lang_code, lang_name in settings.LANGUAGES: post_data = dict(language=lang_code, next='/') response = self.client.post('/i18n/setlang/', data=post_data) - self.assertRedirects(response, 'http://testserver/') + self.assertRedirects(response, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) def test_setlang_unsafe_next(self): @@ -42,7 +42,7 @@ class I18NTests(TestCase): lang_code, lang_name = settings.LANGUAGES[0] post_data = dict(language=lang_code, next='//unsafe/redirection/') response = self.client.post('/i18n/setlang/', data=post_data) - self.assertEqual(response.url, 'http://testserver/') + self.assertEqual(response.url, '/') self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code) def test_setlang_reversal(self): @@ -76,13 +76,13 @@ class I18NTests(TestCase): follow=True, HTTP_REFERER='/en/translated/' ) self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl') - self.assertRedirects(response, 'http://testserver/nl/vertaald/') + self.assertRedirects(response, '/nl/vertaald/') # And reverse response = self.client.post( '/i18n/setlang/', data={'language': 'en'}, follow=True, HTTP_REFERER='/nl/vertaald/' ) - self.assertRedirects(response, 'http://testserver/en/translated/') + self.assertRedirects(response, '/en/translated/') def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" diff --git a/tests/view_tests/tests/test_specials.py b/tests/view_tests/tests/test_specials.py index 0af9033ae7..c8997ed57a 100644 --- a/tests/view_tests/tests/test_specials.py +++ b/tests/view_tests/tests/test_specials.py @@ -11,17 +11,6 @@ class URLHandling(TestCase): """ redirect_target = "/%E4%B8%AD%E6%96%87/target/" - def test_combining_redirect(self): - """ - Tests that redirecting to an IRI, requiring encoding before we use it - in an HTTP response, is handled correctly. In this case the arg to - HttpRedirect is ASCII but the current request path contains non-ASCII - characters so this test ensures the creation of the full path with a - base non-ASCII part is handled correctly. - """ - response = self.client.get('/中文/') - self.assertRedirects(response, self.redirect_target) - def test_nonascii_redirect(self): """ Tests that a non-ASCII argument to HttpRedirect is handled properly. diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py index 4be67bfc6f..ad688573df 100644 --- a/tests/view_tests/views.py +++ b/tests/view_tests/views.py @@ -6,9 +6,7 @@ import sys from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.urlresolvers import get_resolver -from django.http import ( - Http404, HttpResponse, HttpResponseRedirect, JsonResponse, -) +from django.http import Http404, HttpResponse, JsonResponse from django.shortcuts import render, render_to_response from django.template import TemplateDoesNotExist from django.utils.log import getLogger @@ -71,13 +69,6 @@ class Http404View(View): raise Http404("Testing class-based technical 404.") -def redirect(request): - """ - Forces an HTTP redirect. - """ - return HttpResponseRedirect("target/") - - def view_exception(request, n): raise BrokenException(except_args[int(n)])