Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#18558 Supply url property to HttpResponseRedirect and HttpResponsePermanentRedirect #657

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -592,6 +592,7 @@ answer newbie questions, and generally made Django that much better:
Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang
Hannes Struß <x@hannesstruss.de>
Hiroki Kiyohara <hirokiky@gmail.com>

A big THANK YOU goes to:

Expand Down
2 changes: 1 addition & 1 deletion django/contrib/auth/tests/decorators.py
Expand Up @@ -34,7 +34,7 @@ def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
"""
response = self.client.get(view_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(login_url in response['Location'])
self.assertTrue(login_url in response.url)
self.login()
response = self.client.get(view_url)
self.assertEqual(response.status_code, 200)
Expand Down
28 changes: 14 additions & 14 deletions django/contrib/auth/tests/views.py
Expand Up @@ -46,7 +46,7 @@ def login(self, password='password'):
'password': password,
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith(settings.LOGIN_REDIRECT_URL))
self.assertTrue(response.url.endswith(settings.LOGIN_REDIRECT_URL))
self.assertTrue(SESSION_KEY in self.client.session)

def assertContainsEscaped(self, response, text, **kwargs):
Expand Down Expand Up @@ -281,7 +281,7 @@ def test_password_change_succeeds(self):
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/password_change/done/'))
self.assertTrue(response.url.endswith('/password_change/done/'))
self.fail_login()
self.login(password='password1')

Expand All @@ -293,13 +293,13 @@ def test_password_change_done_succeeds(self):
'new_password2': 'password1',
})
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/password_change/done/'))
self.assertTrue(response.url.endswith('/password_change/done/'))

def test_password_change_done_fails(self):
with self.settings(LOGIN_URL='/login/'):
response = self.client.get('/password_change/done/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/login/?next=/password_change/done/'))
self.assertTrue(response.url.endswith('/login/?next=/password_change/done/'))


@skipIfCustomUser
Expand Down Expand Up @@ -336,7 +336,7 @@ def test_security_check(self, password='password'):
'password': password,
})
self.assertEqual(response.status_code, 302)
self.assertFalse(bad_url in response['Location'],
self.assertFalse(bad_url in response.url,
"%s should be blocked" % bad_url)

# These URLs *should* still pass the security check
Expand All @@ -357,7 +357,7 @@ def test_security_check(self, password='password'):
'password': password,
})
self.assertEqual(response.status_code, 302)
self.assertTrue(good_url in response['Location'],
self.assertTrue(good_url in response.url,
"%s should be allowed" % good_url)


Expand All @@ -376,7 +376,7 @@ def get_login_required_url(self, login_url):
settings.LOGIN_URL = login_url
response = self.client.get('/login_required/')
self.assertEqual(response.status_code, 302)
return response['Location']
return response.url

def test_standard_login_url(self):
login_url = '/login/'
Expand Down Expand Up @@ -444,11 +444,11 @@ def test_logout_with_overridden_redirect_url(self):
self.login()
response = self.client.get('/logout/next_page/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/somewhere/'))
self.assertTrue(response.url.endswith('/somewhere/'))

response = self.client.get('/logout/next_page/?next=/login/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/login/'))
self.assertTrue(response.url.endswith('/login/'))

self.confirm_logged_out()

Expand All @@ -457,23 +457,23 @@ def test_logout_with_next_page_specified(self):
self.login()
response = self.client.get('/logout/next_page/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/somewhere/'))
self.assertTrue(response.url.endswith('/somewhere/'))
self.confirm_logged_out()

def test_logout_with_redirect_argument(self):
"Logout with query string redirects to specified resource"
self.login()
response = self.client.get('/logout/?next=/login/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/login/'))
self.assertTrue(response.url.endswith('/login/'))
self.confirm_logged_out()

def test_logout_with_custom_redirect_argument(self):
"Logout with custom query string redirects to specified resource"
self.login()
response = self.client.get('/logout/custom_query/?follow=/somewhere/')
self.assertEqual(response.status_code, 302)
self.assertTrue(response['Location'].endswith('/somewhere/'))
self.assertTrue(response.url.endswith('/somewhere/'))
self.confirm_logged_out()

def test_security_check(self, password='password'):
Expand All @@ -492,7 +492,7 @@ def test_security_check(self, password='password'):
self.login()
response = self.client.get(nasty_url)
self.assertEqual(response.status_code, 302)
self.assertFalse(bad_url in response['Location'],
self.assertFalse(bad_url in response.url,
"%s should be blocked" % bad_url)
self.confirm_logged_out()

Expand All @@ -512,6 +512,6 @@ def test_security_check(self, password='password'):
self.login()
response = self.client.get(safe_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(good_url in response['Location'],
self.assertTrue(good_url in response.url,
"%s should be allowed" % good_url)
self.confirm_logged_out()
36 changes: 18 additions & 18 deletions django/contrib/formtools/tests/wizard/namedwizardtests/tests.py
Expand Up @@ -21,7 +21,7 @@ def setUp(self):
def test_initial_call(self):
response = self.client.get(reverse('%s_start' % self.wizard_urlname))
self.assertEqual(response.status_code, 302)
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)
wizard = response.context['wizard']
self.assertEqual(wizard['steps'].current, 'form1')
Expand All @@ -40,7 +40,7 @@ def test_initial_call_with_params(self):
self.assertEqual(response.status_code, 302)

# Test for proper redirect GET parameters
location = response['Location']
location = response.url
self.assertNotEqual(location.find('?'), -1)
querydict = QueryDict(location[location.find('?') + 1:])
self.assertEqual(dict(querydict.items()), get_params)
Expand All @@ -60,7 +60,7 @@ def test_form_post_success(self):
response = self.client.post(
reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
wizard = response.context['wizard']
Expand All @@ -79,7 +79,7 @@ def test_form_stepback(self):
response = self.client.post(
reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
Expand All @@ -88,7 +88,7 @@ def test_form_stepback(self):
reverse(self.wizard_urlname, kwargs={
'step': response.context['wizard']['steps'].current
}), {'wizard_goto_step': response.context['wizard']['steps'].prev})
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form1')
Expand Down Expand Up @@ -116,7 +116,7 @@ def test_form_finish(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
Expand All @@ -128,7 +128,7 @@ def test_form_finish(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
post_data)
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3')
Expand All @@ -137,7 +137,7 @@ def test_form_finish(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[2])
response = self.client.get(response['Location'])
response = self.client.get(response.url)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form4')
Expand All @@ -146,7 +146,7 @@ def test_form_finish(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[3])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

all_data = response.context['form_list']
Expand All @@ -169,7 +169,7 @@ def test_cleaned_data(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

post_data = self.wizard_step_data[1]
Expand All @@ -178,7 +178,7 @@ def test_cleaned_data(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
post_data)
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

step2_url = reverse(self.wizard_urlname, kwargs={'step': 'form2'})
Expand All @@ -194,14 +194,14 @@ def test_cleaned_data(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[2])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

response = self.client.post(
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[3])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

all_data = response.context['all_cleaned_data']
Expand All @@ -227,7 +227,7 @@ def test_manipulated_data(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

post_data = self.wizard_step_data[1]
Expand All @@ -237,14 +237,14 @@ def test_manipulated_data(self):
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
post_data)
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)

response = self.client.post(
reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}),
self.wizard_step_data[2])
loc = response['Location']
loc = response.url
response = self.client.get(loc)
self.assertEqual(response.status_code, 200, loc)

Expand All @@ -263,15 +263,15 @@ def test_form_reset(self):
response = self.client.post(
reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
self.wizard_step_data[0])
response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2')

response = self.client.get(
'%s?reset=1' % reverse('%s_start' % self.wizard_urlname))
self.assertEqual(response.status_code, 302)

response = self.client.get(response['Location'])
response = self.client.get(response.url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form1')

Expand Down
2 changes: 2 additions & 0 deletions django/http/response.py
Expand Up @@ -392,6 +392,8 @@ def __init__(self, redirect_to, *args, **kwargs):
super(HttpResponseRedirectBase, self).__init__(*args, **kwargs)
self['Location'] = iri_to_uri(redirect_to)

url = property(lambda self: self['Location'])


class HttpResponseRedirect(HttpResponseRedirectBase):
status_code = 302
Expand Down
2 changes: 1 addition & 1 deletion django/test/client.py
Expand Up @@ -564,7 +564,7 @@ def _handle_redirects(self, response, **extra):

response.redirect_chain = []
while response.status_code in (301, 302, 303, 307):
url = response['Location']
url = response.url
redirect_chain = response.redirect_chain
redirect_chain.append((url, response.status_code))

Expand Down
2 changes: 1 addition & 1 deletion django/test/testcases.py
Expand Up @@ -591,7 +591,7 @@ def assertRedirects(self, response, expected_url, status_code=302,
" code was %d (expected %d)" %
(response.status_code, status_code))

url = response['Location']
url = response.url
scheme, netloc, path, query, fragment = urlsplit(url)

redirect_response = response.client.get(path, QueryDict(query))
Expand Down
12 changes: 12 additions & 0 deletions docs/ref/request-response.txt
Expand Up @@ -746,11 +746,23 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
domain (e.g. ``'/search/'``). See :class:`HttpResponse` for other optional
constructor arguments. Note that this returns an HTTP status code 302.

.. attribute:: HttpResponseRedirect.url

.. versionadded:: 1.6

This attribute represents a path to redirect.

.. class:: HttpResponsePermanentRedirect

Like :class:`HttpResponseRedirect`, but it returns a permanent redirect
(HTTP status code 301) instead of a "found" redirect (status code 302).

.. attribute:: HttpResponsePermanentRedirect.url

.. versionadded:: 1.6

This attribute represents a path to redirect.

.. class:: HttpResponseNotModified

The constructor doesn't take any arguments and no content should be added
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A versionadded directive is missing in both snippets.

Expand Down
3 changes: 3 additions & 0 deletions docs/releases/1.6.txt
Expand Up @@ -28,6 +28,9 @@ Minor features
undefined if the given ``QuerySet`` isn't ordered and there are more than
one ordered values to compare against.

* :class:`HttpResponseRedirect` and :class:`HttpResponsePermanentRedirect` now
provides ``url`` attribute. This attribute represents a path to redirect.

Backwards incompatible changes in 1.6
=====================================

Expand Down
2 changes: 1 addition & 1 deletion tests/regressiontests/admin_views/tests.py
Expand Up @@ -1641,7 +1641,7 @@ def test_shortcut_view_only_available_to_staff(self):
response = self.client.get(shortcut_url, follow=False)
# Can't use self.assertRedirects() because User.get_absolute_url() is silly.
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'http://example.com/users/super/')
self.assertEqual(response.url, 'http://example.com/users/super/')


@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
Expand Down