diff --git a/django/test/client.py b/django/test/client.py index dd735dd8331..c65e07ce7e5 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -835,8 +835,11 @@ class Client(ClientMixin, RequestFactory): if url.port: extra['SERVER_PORT'] = str(url.port) + path = url.path + # RFC 2616: bare domains without path are treated as the root. + if not path and url.netloc: + path = '/' # Prepend the request path to handle relative path redirects - path = url.path or '/' if not path.startswith('/'): path = urljoin(response.request['PATH_INFO'], path) diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index 3799dada912..e8ac4bfc00e 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -291,6 +291,13 @@ class ClientTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.request['PATH_INFO'], '/accounts/login/') + def test_redirect_to_querystring_only(self): + """A URL that consists of a querystring only can be followed""" + response = self.client.post('/post_then_get_view/', follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.request['PATH_INFO'], '/post_then_get_view/') + self.assertEqual(response.content, b'The value of success is true.') + def test_follow_307_and_308_redirect(self): """ A 307 or 308 redirect preserves the request method after the redirect. diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py index e2275797af5..51f1fdbb725 100644 --- a/tests/test_client/urls.py +++ b/tests/test_client/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path('upload_view/', views.upload_view, name='upload_view'), path('get_view/', views.get_view, name='get_view'), path('post_view/', views.post_view), + path('post_then_get_view/', views.post_then_get_view), path('put_view/', views.put_view), path('trace_view/', views.trace_view), path('header_view/', views.view_with_header), diff --git a/tests/test_client/views.py b/tests/test_client/views.py index 692581e892a..fa2bac2943a 100644 --- a/tests/test_client/views.py +++ b/tests/test_client/views.py @@ -85,6 +85,21 @@ def post_view(request): return HttpResponse(t.render(c)) +def post_then_get_view(request): + """ + A view that expects a POST request, returns a redirect response + to itself providing only a ?success=true querystring, + the value of this querystring is then rendered upon GET. + """ + if request.method == 'POST': + return HttpResponseRedirect('?success=true') + + t = Template('The value of success is {{ value }}.', name='GET Template') + c = Context({'value': request.GET.get('success', 'false')}) + + return HttpResponse(t.render(c)) + + def json_view(request): """ A view that expects a request with the header 'application/json' and JSON