Ticket #4476: 4476_follow_redirects.diff

File 4476_follow_redirects.diff, 14.3 KB (added by Keith Bussell, 19 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