diff --git a/django/test/client.py b/django/test/client.py
index 96d0c89..d7230cd 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,7 +301,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 head(self, path, data={}, **extra):
         """
@@ -352,7 +359,7 @@ class Client(object):
 
         return self.request(**r)
 
-    def delete(self, path, data={}, **extra):
+    def delete(self, path, data={}, follow=True, **extra):
         """
         Send a DELETE request to the server.
         """
@@ -365,7 +372,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 +426,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 eed252b..6c4f5bb 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -269,32 +269,50 @@ class TransactionTestCase(unittest.TestCase):
             clear_url_caches()
 
     def assertRedirects(self, response, expected_url, status_code=302,
-                        target_status_code=200, host=None):
+                        target_status_code=200, host=None, redirect_level=-1):
         """Asserts that a response redirected to a specific URL, and that the
-        redirect URL can be loaded.
+        redirect URL could be loaded.
 
         Note that assertRedirects won't work for external links since it uses
         TestClient to do a request.
+
+        Note that assertRedirects only works properly if follow was set to True
+        in the request.
         """
-        self.assertEqual(response.status_code, status_code,
+
+        redirect_url = response.get('Location', '')
+        redirect_status_code = response.status_code
+        if not hasattr(response, 'redirect_chain'):
+            # follow wasn't set to True. Be backwards compatible...
+            import warnings
+            warnings.warn(
+                "For using TestCase.assertRedirects() you have to call get() and/or post() with follow=True",
+                PendingDeprecationWarning,
+            )
+            scheme, netloc, path, query, fragment = urlsplit(redirect_url)
+            final_response = response.client.get(path, QueryDict(query))
+            response = final_response
+        else:
+            chain = response.redirect_chain
+            redirect_url = chain[redirect_level][0]
+            redirect_status_code = chain[redirect_level][1]
+        scheme, netloc, path, query, fragment = urlsplit(redirect_url)
+
+        self.assertEqual(redirect_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)
+             " (expected %d)" % (redirect_status_code, 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))
-        self.assertEqual(url, expected_url,
-            "Response redirected to '%s', expected '%s'" % (url, expected_url))
+        self.assertEqual(redirect_url, expected_url,
+            "Response redirected to '%s', expected '%s'" % (redirect_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,
+        self.assertEqual(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))
+                 (path, response.status_code, target_status_code))
 
     def assertContains(self, response, text, count=None, status_code=200):
         """
@@ -430,4 +448,4 @@ class TestCase(TransactionTestCase):
         restore_transaction_methods()
         transaction.rollback()
         transaction.leave_transaction_management()
-        connection.close()
\ No newline at end of file
+        connection.close()
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
index bd68f6b..e0b7878 100644
--- a/docs/topics/testing.txt
+++ b/docs/topics/testing.txt
@@ -478,7 +478,10 @@ 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)
+
+       .. versionchanged:: 1.1
+          The ``follow`` argument is new in this version.
 
         Makes a GET request on the provided ``path`` and returns a ``Response``
         object, which is documented below.
@@ -505,7 +508,22 @@ 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 ``/final/``, this is
+        what you'd see::
+
+            >>> response = c.get('/redirect_me/')
+            >>> response.redirect_chain
+            [(u'/final/', 301)]
+
+
+    .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False)
+
+       .. versionchanged:: 1.1
+          The ``follow`` argument is new in this version.
 
         Makes a POST request on the provided ``path`` and returns a
         ``Response`` object, which is documented below.
@@ -568,6 +586,10 @@ arguments at time of construction:
         to retrieve the username and password, and could interrogate request.GET
         to determine if the user was a visitor.
 
+        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={})
 
         .. versionadded:: development
@@ -1025,12 +1047,17 @@ applications:
     Asserts that the template with the given name was *not* used in rendering
     the response.
 
-.. method:: assertRedirects(response, expected_url, status_code=302, target_status_code=200)
+.. method:: assertRedirects(response, expected_url, status_code=302, target_status_code=200, redirect_level=-1)
 
     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``.
 
+    If you are expecting multiple levels of redirects, you can specify to
+    which ``redirect_level`` the ``expected_url`` and ``status_code`` are
+    matched against. A value of -2 checks against the latest redirect in
+    the chain.
+
 E-mail services
 ---------------
 
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 7015f71..af50b43 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -148,6 +148,22 @@ 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):
+        # First with GET
+        response = self.client.get('/test_client_regress/redirects/', {}, True)
+        self.assertRedirects(response,
+            '/test_client_regress/redirects/further/', 301, 404, redirect_level=0)
+        self.assertRedirects(response,
+            '/test_client_regress/redirects/further/more/', 301, 404, redirect_level=1)
+        self.assertRedirects(response,
+            '/test_client_regress/redirects/further/more/end/', 301, 404, redirect_level=2)
+
+        # Now with POST
+        response = self.client.post('/test_client_regress/redirects/',
+            {'nothing': 'to_send'}, follow=True)
+        self.assertRedirects(response,
+            '/test_client_regress/redirects/further/more/end/', 301, 404)
+
 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 e7db15a..4b38699 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,9 @@ urlpatterns = patterns('',
     (r'^request_data/$', views.request_data),
     url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
     (r'^login_protected_redirect_view/$', views.login_protected_redirect_view),
+    (r'^redirects/$', redirect_to, {'url': 'further/'}),
+    (r'^redirects/further/$', redirect_to, {'url': 'more/'}),
+    (r'^redirects/further/more/$', redirect_to, {'url': 'end/'}),
     (r'^set_session/$', views.set_session_view),
     (r'^check_session/$', views.check_session_view),
     (r'^request_methods/$', views.request_methods_view),
