Ticket #4476: 4476_follow_redirects.diff

File 4476_follow_redirects.diff, 14.3 KB (added by Keith Bussell, 12 years ago)

Another version of the fix, since I already did the work

  • django/test/client.py

     
    11import datetime
    22import sys
    33from cStringIO import StringIO
    4 from urlparse import urlparse
     4from urlparse import urlparse, urlsplit
    55from django.conf import settings
    66from django.contrib.auth import authenticate, login
    77from django.core.handlers.base import BaseHandler
    88from django.core.handlers.wsgi import WSGIRequest
    99from django.core.signals import got_request_exception
    1010from django.dispatch import dispatcher
    11 from django.http import SimpleCookie, HttpRequest
     11from django.http import SimpleCookie, HttpRequest, QueryDict
    1212from django.template import TemplateDoesNotExist
    1313from django.test import signals
    1414from django.utils.functional import curry
     
    205205
    206206        return response
    207207
    208     def get(self, path, data={}, **extra):
     208    def get(self, path, data={}, follow=True, **extra):
    209209        "Request a response from the server using GET."
    210210        r = {
    211211            'CONTENT_LENGTH':  None,
     
    216216        }
    217217        r.update(extra)
    218218
    219         return self.request(**r)
     219        response = self.request(**r)
     220        if follow:
     221            response = self._handle_redirects(response)
     222        return response
    220223
    221     def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
     224    def post(self, path, data={}, content_type=MULTIPART_CONTENT, follow=True, **extra):
    222225        "Request a response from the server using POST."
    223226
    224227        if content_type is MULTIPART_CONTENT:
     
    235238        }
    236239        r.update(extra)
    237240
    238         return self.request(**r)
     241        response = self.request(**r)
     242        if follow:
     243            response = self._handle_redirects(response)
     244        return response
    239245
    240246    def login(self, **credentials):
    241247        """Set the Client to appear as if it has sucessfully logged into a site.
     
    276282        session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
    277283        session.delete(session_key=self.cookies['sessionid'].value)
    278284        self.cookies = SimpleCookie()
     285
     286    def _handle_redirects(self, response):
     287        "Follows any redirects by requesting responses from the server using GET."
     288
     289        response.redirect_chain = []
     290        while response.status_code in (301, 302, 303, 307):
     291            url = response['Location']
     292            scheme, netloc, path, query, fragment = urlsplit(url)
     293
     294            local_redirect_chain = response.redirect_chain
     295            local_redirect_chain.append((url, response.status_code))
     296
     297            # The test client doesn't handle external links,
     298            # but since the situation is simulated in test_client,
     299            # we fake things here by ignoring the netloc portion of the
     300            # redirected URL.
     301            response = self.get(path, QueryDict(query), follow=False)
     302            response.redirect_chain = local_redirect_chain
     303
     304            # prevent any loops
     305            if response.redirect_chain[-1] in response.redirect_chain[0:-1]:
     306                break
     307
     308        return response
     309 No newline at end of file
  • django/test/testcases.py

     
    22import unittest
    33from urlparse import urlsplit, urlunsplit
    44
    5 from django.http import QueryDict
    65from django.db import transaction
    76from django.core import mail
    87from django.core.management import call_command
     
    7473        super(TestCase, self).__call__(result)
    7574
    7675    def assertRedirects(self, response, expected_url, status_code=302,
    77                         target_status_code=200, host=None):
     76                        target_status_code=200, host=None, redirect_level=-1):
    7877        """Asserts that a response redirected to a specific URL, and that the
    7978        redirect URL can be loaded.
    8079
    8180        Note that assertRedirects won't work for external links since it uses
    8281        TestClient to do a request.
     82       
     83        Note that assertRedirects only works properly if follow was set to true
     84        in the request.
    8385        """
    84         self.assertEqual(response.status_code, status_code,
     86
     87        redirect_status_code = response.status_code
     88        redirect_url = response.get('Location', '')
     89        chain = response.__dict__.get('redirect_chain')
     90        if chain:
     91            redirect_url = chain[redirect_level][0]
     92            redirect_status_code = chain[redirect_level][1]
     93
     94        self.assertEqual(redirect_status_code, status_code,
    8595            ("Response didn't redirect as expected: Response code was %d"
    86              " (expected %d)" % (response.status_code, status_code)))
    87         url = response['Location']
    88         scheme, netloc, path, query, fragment = urlsplit(url)
     96             " (expected %d)" % (redirect_status_code, status_code)))
     97
    8998        e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url)
    9099        if not (e_scheme or e_netloc):
    91100            expected_url = urlunsplit(('http', host or 'testserver', e_path,
    92101                    e_query, e_fragment))
    93         self.assertEqual(url, expected_url,
    94             "Response redirected to '%s', expected '%s'" % (url, expected_url))
     102        self.assertEqual(redirect_url, expected_url,
     103            "Response redirected to '%s', expected '%s'" % (redirect_url, expected_url))
    95104
    96         # Get the redirection page, using the same client that was used
    97         # to obtain the original response.
    98         redirect_response = response.client.get(path, QueryDict(query))
    99         self.assertEqual(redirect_response.status_code, target_status_code,
     105        self.assertEqual(response.status_code, target_status_code,
    100106            ("Couldn't retrieve redirection page '%s': response code was %d"
    101107             " (expected %d)") %
    102                  (path, redirect_response.status_code, target_status_code))
     108                 (redirect_url, response.status_code, target_status_code))
    103109
    104110    def assertContains(self, response, text, count=None, status_code=200):
    105111        """
  • docs/testing.txt

     
    453453
    454454Once you have a ``Client`` instance, you can call any of the following methods:
    455455
    456 ``get(path, data={})``
     456``get(path, data={}, follow=True)``
    457457    Makes a GET request on the provided ``path`` and returns a ``Response``
    458     object, which is documented below.
     458    object, which is documented below, and will optionally ``follow`` any
     459    server redirects.
    459460
    460461    The key-value pairs in the ``data`` dictionary are used to create a GET
    461462    data payload. For example::
     
    467468
    468469        /customers/details/?name=fred&age=7
    469470
    470 ``post(path, data={}, content_type=MULTIPART_CONTENT)``
     471    If any redirects occurred, the response will have a ``redirect_chain``
     472    attribute containing tuples of the intermediate urls and status codes.
     473    If you had an url ``/redirect_me/`` that redirected to ``/final/``, this is
     474    what you'd see:
     475   
     476        >>> response = c.get('/redirect_me/')
     477        >>> response.redirect_chain
     478        [(u'/final/', 301)]
     479
     480``post(path, data={}, content_type=MULTIPART_CONTENT, follow=True)``
    471481    Makes a POST request on the provided ``path`` and returns a ``Response``
    472     object, which is documented below.
     482    object, which is documented below, and will optionally ``follow`` any
     483    server redirects.
    473484
    474485    The key-value pairs in the ``data`` dictionary are used to submit POST
    475486    data. For example::
     
    501512
    502513        {'choices': ('a', 'b', 'd')}
    503514
     515    If any redirects occurred, the response will have a ``redirect_chain``
     516    attribute containing tuples of the intermediate urls and status codes.
     517    If you had an url ``/redirect_me/`` that redirected to ``/final/``, this is
     518    what you'd see:
     519   
     520        >>> response = c.post('/redirect_me/', {'data': 'test'})
     521        >>> response.redirect_chain
     522        [(u'/final/', 302)]
     523
    504524    Submitting files is a special case. To POST a file, you need only provide
    505525    the file field name as a key, and a file handle to the file you wish to
    506526    upload as a value. For example::
     
    834854    Asserts that the template with the given name was *not* used in rendering
    835855    the response.
    836856
    837 ``assertRedirects(response, expected_url, status_code=302, target_status_code=200)``
     857``assertRedirects(response, expected_url, status_code=302, target_status_code=200, redirect_level=-1)``
    838858    Asserts that the response return a ``status_code`` redirect status,
    839     it redirected to ``expected_url`` (including any GET data), and the subsequent
     859    it redirected to ``expected_url`` (including any GET data), and the final
    840860    page was received with ``target_status_code``.
     861   
     862    If you're expecting multiple levels of redirects, you can specify to which
     863    ``redirect_level`` the ``expected_url`` and ``status_code`` are matched against.
     864    A value of -1 checks against the last redirect in the chain.
    841865
    842866``assertTemplateUsed(response, template_name)``
    843867    Asserts that the template with the given name was used in rendering the
  • tests/modeltests/test_client/models.py

     
    114114
    115115    def test_redirect_to_strange_location(self):
    116116        "GET a URL that redirects to a non-200 page"
     117        response = self.client.get('/test_client/redirect_to_bad_view/')
     118
     119        # Check that the response was a 404
     120        self.assertRedirects(response, 'http://testserver/test_client/bad_view/', target_status_code=404)
     121
     122    def test_chained_redirects(self):
     123        "GET a URL that redirects more than once"
    117124        response = self.client.get('/test_client/double_redirect_view/')
    118125
    119         # Check that the response was a 302, and that
    120         # the attempt to get the redirection location returned 301 when retrieved
    121         self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', target_status_code=301)
     126        # Check that there were two redirects, the first being a 302 and the second 301
     127        self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', status_code=302, redirect_level=0)
     128        self.assertRedirects(response, 'http://testserver/test_client/get_view/', status_code=301, redirect_level=1)
    122129
    123130    def test_notfound_response(self):
    124131        "GET a URL that responds as '404:Not Found'"
  • tests/modeltests/test_client/urls.py

     
    99    (r'^redirect_view/$', views.redirect_view),
    1010    (r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_view/' }),
    1111    (r'^double_redirect_view/$', views.double_redirect_view),
     12    (r'^redirect_to_bad_view/$', views.redirect_to_bad_view),
     13    (r'^infinite_redirect/$', views.redirect_to_self),
    1214    (r'^bad_view/$', views.bad_view),
    1315    (r'^form_view/$', views.form_view),
    1416    (r'^form_view_with_template/$', views.form_view_with_template),
  • tests/modeltests/test_client/views.py

     
    6060    "A view that redirects all requests to a redirection view"
    6161    return HttpResponseRedirect('/test_client/permanent_redirect_view/')
    6262
     63def redirect_to_bad_view(request):
     64    "A view that redirects all requests to 404 page"
     65    return HttpResponseRedirect('/test_client/bad_view/')
     66
     67def redirect_to_self(request):
     68    "A view that redirects to itself (bad thing!)"
     69    return HttpResponseRedirect('/test_client/infinite_redirect/')
     70
    6371def bad_view(request):
    6472    "A view that returns a 404 with some error content"
    6573    return HttpResponseNotFound('Not found!. This page contains some MAGIC content')
  • tests/regressiontests/test_client_regress/models.py

     
    132132
    133133    def test_target_page(self):
    134134        "An assertion is raised if the response redirect target cannot be retrieved as expected"
    135         response = self.client.get('/test_client/double_redirect_view/')
     135        response = self.client.get('/test_client/redirect_to_bad_view/')
    136136        try:
    137             # The redirect target responds with a 301 code, not 200
    138             self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/')
     137            # The redirect target responds with a 404 code, not 200 (simulated case.)
     138            self.assertRedirects(response, 'http://testserver/test_client/bad_view/')
    139139        except AssertionError, e:
    140             self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
     140            self.assertEquals(str(e), "Couldn't retrieve redirection page 'http://testserver/test_client/bad_view/': response code was 404 (expected 200)")
    141141
     142    def test_infinite_loop_redirect(self):
     143        "An exception is raised if an URL contains a loop in the redirect chain"
     144
     145        # Check that the test client breaks out of infinite redirect loops
     146        response = self.client.get('/test_client/infinite_redirect/')
     147        try:
     148            self.assertRedirects(response, 'http://testserver/test_client/infinite_redirect/')
     149        except AssertionError, e:
     150            self.assertEquals(str(e), "Couldn't retrieve redirection page 'http://testserver/test_client/infinite_redirect/': response code was 302 (expected 200)")
     151
     152        # For educational purposes: how to tell that you encountered a loop
     153        self.assertTrue(response.redirect_chain[-1] in response.redirect_chain[0:-1])
     154
    142155class AssertFormErrorTests(TestCase):
    143156    def test_unknown_form(self):
    144157        "An assertion is raised if the form name is unknown"
Back to Top