From e735fe7160d786efeb2e8bea595174c1a68409a2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Feb 2009 13:14:59 +0000 Subject: [PATCH] Fixed #4476 -- Added a ``follow`` option to the test client request methods. This implements browser-like behavior for the test client, following redirect chains when a 30X response is received. Thanks to Marc Fargas and Keith Bussell for their work on this. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9911 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 72 ++++++++--- django/test/testcases.py | 65 ++++++---- docs/topics/testing.txt | 112 ++++++++++++------ tests/modeltests/test_client/models.py | 12 +- .../test_client_regress/models.py | 101 ++++++++++++++++ .../test_client_regress/urls.py | 10 ++ .../test_client_regress/views.py | 2 +- 7 files changed, 298 insertions(+), 76 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index 96d0c89602..20283b7b3b 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -1,5 +1,5 @@ import urllib -from urlparse import urlparse, urlunparse +from urlparse import urlparse, urlunparse, urlsplit import sys import os try: @@ -12,7 +12,7 @@ from django.contrib.auth import authenticate, login from django.core.handlers.base import BaseHandler from django.core.handlers.wsgi import WSGIRequest from django.core.signals import got_request_exception -from django.http import SimpleCookie, HttpRequest +from django.http import SimpleCookie, HttpRequest, QueryDict from django.template import TemplateDoesNotExist from django.test import signals from django.utils.functional import curry @@ -261,7 +261,7 @@ class Client(object): return response - def get(self, path, data={}, **extra): + def get(self, path, data={}, follow=False, **extra): """ Requests a response from the server using GET. """ @@ -275,9 +275,13 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response - def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): + def post(self, path, data={}, content_type=MULTIPART_CONTENT, + follow=False, **extra): """ Requests a response from the server using POST. """ @@ -297,9 +301,12 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response - def head(self, path, data={}, **extra): + def head(self, path, data={}, follow=False, **extra): """ Request a response from the server using HEAD. """ @@ -313,9 +320,12 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response - def options(self, path, data={}, **extra): + def options(self, path, data={}, follow=False, **extra): """ Request a response from the server using OPTIONS. """ @@ -328,9 +338,13 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response - def put(self, path, data={}, content_type=MULTIPART_CONTENT, **extra): + def put(self, path, data={}, content_type=MULTIPART_CONTENT, + follow=False, **extra): """ Send a resource to the server using PUT. """ @@ -350,9 +364,12 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response - def delete(self, path, data={}, **extra): + def delete(self, path, data={}, follow=False, **extra): """ Send a DELETE request to the server. """ @@ -365,7 +382,10 @@ class Client(object): } r.update(extra) - return self.request(**r) + response = self.request(**r) + if follow: + response = self._handle_redirects(response) + return response def login(self, **credentials): """ @@ -416,3 +436,27 @@ class Client(object): session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore() session.delete(session_key=self.cookies[settings.SESSION_COOKIE_NAME].value) self.cookies = SimpleCookie() + + def _handle_redirects(self, response): + "Follows any redirects by requesting responses from the server using GET." + + response.redirect_chain = [] + while response.status_code in (301, 302, 303, 307): + url = response['Location'] + scheme, netloc, path, query, fragment = urlsplit(url) + + redirect_chain = response.redirect_chain + redirect_chain.append((url, response.status_code)) + + # The test client doesn't handle external links, + # but since the situation is simulated in test_client, + # we fake things here by ignoring the netloc portion of the + # redirected URL. + response = self.get(path, QueryDict(query), follow=False) + response.redirect_chain = redirect_chain + + # Prevent loops + if response.redirect_chain[-1] in response.redirect_chain[0:-1]: + break + return response + diff --git a/django/test/testcases.py b/django/test/testcases.py index eed252b8cf..cf1bf6ea63 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -43,7 +43,7 @@ def disable_transaction_methods(): transaction.savepoint_commit = nop transaction.savepoint_rollback = nop transaction.enter_transaction_management = nop - transaction.leave_transaction_management = nop + transaction.leave_transaction_management = nop def restore_transaction_methods(): transaction.commit = real_commit @@ -198,7 +198,7 @@ class DocTestRunner(doctest.DocTestRunner): # Rollback, in case of database errors. Otherwise they'd have # side effects on other tests. transaction.rollback_unless_managed() - + class TransactionTestCase(unittest.TestCase): def _pre_setup(self): """Performs any pre-test setup. This includes: @@ -242,7 +242,7 @@ class TransactionTestCase(unittest.TestCase): import sys result.addError(self, sys.exc_info()) return - super(TransactionTestCase, self).__call__(result) + super(TransactionTestCase, self).__call__(result) try: self._post_teardown() except (KeyboardInterrupt, SystemExit): @@ -263,7 +263,7 @@ class TransactionTestCase(unittest.TestCase): def _fixture_teardown(self): pass - def _urlconf_teardown(self): + def _urlconf_teardown(self): if hasattr(self, '_old_root_urlconf'): settings.ROOT_URLCONF = self._old_root_urlconf clear_url_caches() @@ -276,25 +276,48 @@ class TransactionTestCase(unittest.TestCase): Note that assertRedirects won't work for external links since it uses TestClient to do a request. """ - self.assertEqual(response.status_code, status_code, - ("Response didn't redirect as expected: Response code was %d" - " (expected %d)" % (response.status_code, status_code))) - url = response['Location'] - scheme, netloc, path, query, fragment = urlsplit(url) + if hasattr(response, 'redirect_chain'): + # The request was a followed redirect + self.assertTrue(len(response.redirect_chain) > 0, + ("Response didn't redirect as expected: Response code was %d" + " (expected %d)" % (response.status_code, status_code))) + + self.assertEqual(response.redirect_chain[0][1], status_code, + ("Initial response didn't redirect as expected: Response code was %d" + " (expected %d)" % (response.redirect_chain[0][1], status_code))) + + url, status_code = response.redirect_chain[-1] + + self.assertEqual(response.status_code, target_status_code, + ("Response didn't redirect as expected: Final Response code was %d" + " (expected %d)" % (response.status_code, target_status_code))) + + else: + # Not a followed redirect + self.assertEqual(response.status_code, status_code, + ("Response didn't redirect as expected: Response code was %d" + " (expected %d)" % (response.status_code, status_code))) + + url = response['Location'] + scheme, netloc, path, query, fragment = urlsplit(url) + + redirect_response = response.client.get(path, QueryDict(query)) + + # Get the redirection page, using the same client that was used + # to obtain the original response. + self.assertEqual(redirect_response.status_code, target_status_code, + ("Couldn't retrieve redirection page '%s': response code was %d" + " (expected %d)") % + (path, redirect_response.status_code, target_status_code)) + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) if not (e_scheme or e_netloc): expected_url = urlunsplit(('http', host or 'testserver', e_path, - e_query, e_fragment)) + e_query, e_fragment)) + self.assertEqual(url, expected_url, "Response redirected to '%s', expected '%s'" % (url, expected_url)) - # Get the redirection page, using the same client that was used - # to obtain the original response. - redirect_response = response.client.get(path, QueryDict(query)) - self.assertEqual(redirect_response.status_code, target_status_code, - ("Couldn't retrieve redirection page '%s': response code was %d" - " (expected %d)") % - (path, redirect_response.status_code, target_status_code)) def assertContains(self, response, text, count=None, status_code=200): """ @@ -401,15 +424,15 @@ class TransactionTestCase(unittest.TestCase): class TestCase(TransactionTestCase): """ Does basically the same as TransactionTestCase, but surrounds every test - with a transaction, monkey-patches the real transaction management routines to - do nothing, and rollsback the test transaction at the end of the test. You have + with a transaction, monkey-patches the real transaction management routines to + do nothing, and rollsback the test transaction at the end of the test. You have to use TransactionTestCase, if you need transaction management inside a test. """ def _fixture_setup(self): if not settings.DATABASE_SUPPORTS_TRANSACTIONS: return super(TestCase, self)._fixture_setup() - + transaction.enter_transaction_management() transaction.managed(True) disable_transaction_methods() @@ -426,7 +449,7 @@ class TestCase(TransactionTestCase): def _fixture_teardown(self): if not settings.DATABASE_SUPPORTS_TRANSACTIONS: return super(TestCase, self)._fixture_teardown() - + restore_transaction_methods() transaction.rollback() transaction.leave_transaction_management() diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index bd68f6ba7a..b7d04b4771 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -478,7 +478,8 @@ arguments at time of construction: Once you have a ``Client`` instance, you can call any of the following methods: - .. method:: Client.get(path, data={}) + .. method:: Client.get(path, data={}, follow=False) + Makes a GET request on the provided ``path`` and returns a ``Response`` object, which is documented below. @@ -505,7 +506,18 @@ arguments at time of construction: If you provide URL both an encoded GET data and a data argument, the data argument will take precedence. - .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT) + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + If you had an url ``/redirect_me/`` that redirected to ``/next/``, that + redirected to ``/final/``, this is what you'd see:: + + >>> response = c.get('/redirect_me/') + >>> response.redirect_chain + [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] + + .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False) Makes a POST request on the provided ``path`` and returns a ``Response`` object, which is documented below. @@ -556,7 +568,7 @@ arguments at time of construction: Note that you should manually close the file after it has been provided to ``post()``. - .. versionadded:: development + .. versionchanged:: 1.1 If the URL you request with a POST contains encoded parameters, these parameters will be made available in the request.GET data. For example, @@ -568,7 +580,11 @@ arguments at time of construction: to retrieve the username and password, and could interrogate request.GET to determine if the user was a visitor. - .. method:: Client.head(path, data={}) + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + .. method:: Client.head(path, data={}, follow=False) .. versionadded:: development @@ -576,14 +592,22 @@ arguments at time of construction: object. Useful for testing RESTful interfaces. Acts just like :meth:`Client.get` except it does not return a message body. - .. method:: Client.options(path, data={}) + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + .. method:: Client.options(path, data={}, follow=False) .. versionadded:: development Makes an OPTIONS request on the provided ``path`` and returns a ``Response`` object. Useful for testing RESTful interfaces. - .. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT) + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + .. method:: Client.put(path, data={}, content_type=MULTIPART_CONTENT, follow=False) .. versionadded:: development @@ -591,13 +615,21 @@ arguments at time of construction: ``Response`` object. Useful for testing RESTful interfaces. Acts just like :meth:`Client.post` except with the PUT request method. - .. method:: Client.delete(path) + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + .. method:: Client.delete(path, follow=False) .. versionadded:: development Makes an DELETE request on the provided ``path`` and returns a ``Response`` object. Useful for testing RESTful interfaces. + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + .. method:: Client.login(**credentials) .. versionadded:: 1.0 @@ -789,47 +821,47 @@ additions. .. class:: TransactionTestCase() -Django ``TestCase`` classes make use of database transaction facilities, if -available, to speed up the process of resetting the database to a known state -at the beginning of each test. A consequence of this, however, is that the -effects of transaction commit and rollback cannot be tested by a Django -``TestCase`` class. If your test requires testing of such transactional +Django ``TestCase`` classes make use of database transaction facilities, if +available, to speed up the process of resetting the database to a known state +at the beginning of each test. A consequence of this, however, is that the +effects of transaction commit and rollback cannot be tested by a Django +``TestCase`` class. If your test requires testing of such transactional behavior, you should use a Django ``TransactionTestCase``. -``TransactionTestCase`` and ``TestCase`` are identical except for the manner -in which the database is reset to a known state and the ability for test code -to test the effects of commit and rollback. A ``TranscationTestCase`` resets -the database before the test runs by truncating all tables and reloading -initial data. A ``TransactionTestCase`` may call commit and rollback and -observe the effects of these calls on the database. +``TransactionTestCase`` and ``TestCase`` are identical except for the manner +in which the database is reset to a known state and the ability for test code +to test the effects of commit and rollback. A ``TranscationTestCase`` resets +the database before the test runs by truncating all tables and reloading +initial data. A ``TransactionTestCase`` may call commit and rollback and +observe the effects of these calls on the database. -A ``TestCase``, on the other hand, does not truncate tables and reload initial -data at the beginning of a test. Instead, it encloses the test code in a -database transaction that is rolled back at the end of the test. It also -prevents the code under test from issuing any commit or rollback operations -on the database, to ensure that the rollback at the end of the test restores -the database to its initial state. In order to guarantee that all ``TestCase`` -code starts with a clean database, the Django test runner runs all ``TestCase`` -tests first, before any other tests (e.g. doctests) that may alter the +A ``TestCase``, on the other hand, does not truncate tables and reload initial +data at the beginning of a test. Instead, it encloses the test code in a +database transaction that is rolled back at the end of the test. It also +prevents the code under test from issuing any commit or rollback operations +on the database, to ensure that the rollback at the end of the test restores +the database to its initial state. In order to guarantee that all ``TestCase`` +code starts with a clean database, the Django test runner runs all ``TestCase`` +tests first, before any other tests (e.g. doctests) that may alter the database without restoring it to its original state. -When running on a database that does not support rollback (e.g. MySQL with the -MyISAM storage engine), ``TestCase`` falls back to initializing the database +When running on a database that does not support rollback (e.g. MySQL with the +MyISAM storage engine), ``TestCase`` falls back to initializing the database by truncating tables and reloading initial data. .. note:: - The ``TestCase`` use of rollback to un-do the effects of the test code - may reveal previously-undetected errors in test code. For example, - test code that assumes primary keys values will be assigned starting at - one may find that assumption no longer holds true when rollbacks instead - of table truncation are being used to reset the database. Similarly, - the reordering of tests so that all ``TestCase`` classes run first may - reveal unexpected dependencies on test case ordering. In such cases a + The ``TestCase`` use of rollback to un-do the effects of the test code + may reveal previously-undetected errors in test code. For example, + test code that assumes primary keys values will be assigned starting at + one may find that assumption no longer holds true when rollbacks instead + of table truncation are being used to reset the database. Similarly, + the reordering of tests so that all ``TestCase`` classes run first may + reveal unexpected dependencies on test case ordering. In such cases a quick fix is to switch the ``TestCase`` to a ``TransactionTestCase``. A better long-term fix, that allows the test to take advantage of the speed benefit of ``TestCase``, is to fix the underlying test problem. - + Default test client ~~~~~~~~~~~~~~~~~~~ @@ -1028,9 +1060,15 @@ applications: .. method:: assertRedirects(response, expected_url, status_code=302, target_status_code=200) Asserts that the response return a ``status_code`` redirect status, it - redirected to ``expected_url`` (including any GET data), and the subsequent + redirected to ``expected_url`` (including any GET data), and the final page was received with ``target_status_code``. + .. versionadded:: 1.1 + + If your request used the ``follow`` argument, the ``expected_url`` and + ``target_status_code`` will be the url and status code for the final + point of the redirect chain. + E-mail services --------------- diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index b975d139c6..dfc84169f3 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -70,13 +70,13 @@ class ClientTest(TestCase): self.assertEqual(response.context['data'], '37') self.assertEqual(response.template.name, 'POST Template') self.failUnless('Data received' in response.content) - + def test_response_headers(self): "Check the value of HTTP headers returned in a response" response = self.client.get("/test_client/header_view/") - + self.assertEquals(response['X-DJANGO-TEST'], 'Slartibartfast') - + def test_raw_post(self): "POST raw data (with a content type) to a view" test_doc = """BlinkMalcolm Gladwell""" @@ -132,6 +132,12 @@ class ClientTest(TestCase): # the attempt to get the redirection location returned 301 when retrieved self.assertRedirects(response, 'http://testserver/test_client/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('/test_client/double_redirect_view/', follow=True) + self.assertRedirects(response, 'http://testserver/test_client/get_view/', status_code=302, target_status_code=200) + self.assertEquals(len(response.redirect_chain), 2) + def test_notfound_response(self): "GET a URL that responds as '404:Not Found'" response = self.client.get('/test_client/bad_view/') diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 7015f71e00..16c26365dd 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -148,6 +148,107 @@ class AssertRedirectsTests(TestCase): except AssertionError, e: self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)") + def test_redirect_chain(self): + "You can follow a redirect chain of multiple redirects" + response = self.client.get('/test_client_regress/redirects/further/more/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/no_template_view/', + status_code=301, target_status_code=200) + + self.assertEquals(len(response.redirect_chain), 1) + self.assertEquals(response.redirect_chain[0], ('http://testserver/test_client_regress/no_template_view/', 301)) + + def test_multiple_redirect_chain(self): + "You can follow a redirect chain of multiple redirects" + response = self.client.get('/test_client_regress/redirects/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/no_template_view/', + status_code=301, target_status_code=200) + + self.assertEquals(len(response.redirect_chain), 3) + self.assertEquals(response.redirect_chain[0], ('http://testserver/test_client_regress/redirects/further/', 301)) + self.assertEquals(response.redirect_chain[1], ('http://testserver/test_client_regress/redirects/further/more/', 301)) + self.assertEquals(response.redirect_chain[2], ('http://testserver/test_client_regress/no_template_view/', 301)) + + def test_redirect_chain_to_non_existent(self): + "You can follow a chain to a non-existent view" + response = self.client.get('/test_client_regress/redirect_to_non_existent_view2/', {}, follow=True) + self.assertRedirects(response, '/test_client_regress/non_existent_view/', + status_code=301, target_status_code=404) + + def test_redirect_chain_to_self(self): + "Redirections to self are caught and escaped" + response = self.client.get('/test_client_regress/redirect_to_self/', {}, follow=True) + # The chain of redirects stops once the cycle is detected. + self.assertRedirects(response, '/test_client_regress/redirect_to_self/', + status_code=301, target_status_code=301) + self.assertEquals(len(response.redirect_chain), 2) + + def test_circular_redirect(self): + "Circular redirect chains are caught and escaped" + response = self.client.get('/test_client_regress/circular_redirect_1/', {}, follow=True) + # The chain of redirects will get back to the starting point, but stop there. + self.assertRedirects(response, '/test_client_regress/circular_redirect_2/', + status_code=301, target_status_code=301) + self.assertEquals(len(response.redirect_chain), 4) + + def test_redirect_chain_post(self): + "A redirect chain will be followed from an initial POST post" + response = self.client.post('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEquals(len(response.redirect_chain), 3) + + def test_redirect_chain_head(self): + "A redirect chain will be followed from an initial HEAD request" + response = self.client.head('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEquals(len(response.redirect_chain), 3) + + def test_redirect_chain_options(self): + "A redirect chain will be followed from an initial OPTIONS request" + response = self.client.options('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEquals(len(response.redirect_chain), 3) + + def test_redirect_chain_put(self): + "A redirect chain will be followed from an initial PUT request" + response = self.client.put('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEquals(len(response.redirect_chain), 3) + + def test_redirect_chain_delete(self): + "A redirect chain will be followed from an initial DELETE request" + response = self.client.delete('/test_client_regress/redirects/', + {'nothing': 'to_send'}, follow=True) + self.assertRedirects(response, + '/test_client_regress/no_template_view/', 301, 200) + self.assertEquals(len(response.redirect_chain), 3) + + def test_redirect_chain_on_non_redirect_page(self): + "An assertion is raised if the original page couldn't be retrieved as expected" + # This page will redirect with code 301, not 302 + response = self.client.get('/test_client/get_view/', follow=True) + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError, e: + self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)") + + def test_redirect_on_non_redirect_page(self): + "An assertion is raised if the original page couldn't be retrieved as expected" + # This page will redirect with code 301, not 302 + response = self.client.get('/test_client/get_view/') + try: + self.assertRedirects(response, '/test_client/get_view/') + except AssertionError, e: + self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 200 (expected 302)") + + class AssertFormErrorTests(TestCase): def test_unknown_form(self): "An assertion is raised if the form name is unknown" diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index e7db15a46b..9e573f6384 100644 --- a/tests/regressiontests/test_client_regress/urls.py +++ b/tests/regressiontests/test_client_regress/urls.py @@ -1,4 +1,5 @@ from django.conf.urls.defaults import * +from django.views.generic.simple import redirect_to import views urlpatterns = patterns('', @@ -8,6 +9,15 @@ urlpatterns = patterns('', (r'^request_data/$', views.request_data), url(r'^arg_view/(?P.+)/$', views.view_with_argument, name='arg_view'), (r'^login_protected_redirect_view/$', views.login_protected_redirect_view), + (r'^redirects/$', redirect_to, {'url': '/test_client_regress/redirects/further/'}), + (r'^redirects/further/$', redirect_to, {'url': '/test_client_regress/redirects/further/more/'}), + (r'^redirects/further/more/$', redirect_to, {'url': '/test_client_regress/no_template_view/'}), + (r'^redirect_to_non_existent_view/$', redirect_to, {'url': '/test_client_regress/non_existent_view/'}), + (r'^redirect_to_non_existent_view2/$', redirect_to, {'url': '/test_client_regress/redirect_to_non_existent_view/'}), + (r'^redirect_to_self/$', redirect_to, {'url': '/test_client_regress/redirect_to_self/'}), + (r'^circular_redirect_1/$', redirect_to, {'url': '/test_client_regress/circular_redirect_2/'}), + (r'^circular_redirect_2/$', redirect_to, {'url': '/test_client_regress/circular_redirect_3/'}), + (r'^circular_redirect_3/$', redirect_to, {'url': '/test_client_regress/circular_redirect_1/'}), (r'^set_session/$', views.set_session_view), (r'^check_session/$', views.check_session_view), (r'^request_methods/$', views.request_methods_view), diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index e62f6a17c5..453c37a32e 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -58,4 +58,4 @@ def check_session_view(request): def request_methods_view(request): "A view that responds with the request method" - return HttpResponse('request method: %s' % request.method) \ No newline at end of file + return HttpResponse('request method: %s' % request.method)