Django

Code

Ticket #4476: 4476_follow_redirects.diff

File 4476_follow_redirects.diff, 14.3 kB (added by keithb, 7 months ago)

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

  • django/test/client.py

    old new  
    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 
  • django/test/testcases.py

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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"