Django

Code

Changeset 5152

Show
Ignore:
Timestamp:
05/05/07 10:16:15 (1 year ago)
Author:
russellm
Message:

Backwards incompatible change: Changed the way test.Client.login operates. Old implemenation was fragile, and tightly bound to forms. New implementation interfaces directly with the login system, is compatible with any authentication backend, and doesn't depend upon specific template inputs being available.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/test/client.py

    r4774 r5152  
     1import datetime 
    12import sys 
    23from cStringIO import StringIO 
    34from urlparse import urlparse 
    45from django.conf import settings 
     6from django.contrib.auth import authenticate, login 
     7from django.contrib.sessions.models import Session 
     8from django.contrib.sessions.middleware import SessionWrapper 
    59from django.core.handlers.base import BaseHandler 
    610from django.core.handlers.wsgi import WSGIRequest 
    711from django.core.signals import got_request_exception 
    812from django.dispatch import dispatcher 
    9 from django.http import urlencode, SimpleCookie 
     13from django.http import urlencode, SimpleCookie, HttpRequest 
    1014from django.test import signals 
    1115from django.utils.functional import curry 
     
    114118        self.defaults = defaults 
    115119        self.cookies = SimpleCookie() 
    116         self.session = {} 
    117120        self.exc_info = None 
    118121         
     
    124127        self.exc_info = sys.exc_info() 
    125128 
     129    def _session(self): 
     130        "Obtain the current session variables" 
     131        if 'django.contrib.sessions' in settings.INSTALLED_APPS: 
     132            cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) 
     133            if cookie: 
     134                return SessionWrapper(cookie.value) 
     135        return {} 
     136    session = property(_session) 
     137     
    126138    def request(self, **request): 
    127139        """ 
     
    172184            raise self.exc_info[1], None, self.exc_info[2] 
    173185         
    174         # Update persistent cookie and session data 
     186        # Update persistent cookie data 
    175187        if response.cookies: 
    176188            self.cookies.update(response.cookies) 
    177189 
    178         if 'django.contrib.sessions' in settings.INSTALLED_APPS: 
    179             from django.contrib.sessions.middleware import SessionWrapper 
    180             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) 
    181             if cookie: 
    182                 self.session = SessionWrapper(cookie.value) 
    183              
    184190        return response 
    185191 
     
    216222        return self.request(**r) 
    217223 
    218     def login(self, path, username, password, **extra): 
    219         """ 
    220         A specialized sequence of GET and POST to log into a view that 
    221         is protected by a @login_required access decorator. 
    222  
    223         path should be the URL of the page that is login protected. 
    224  
    225         Returns the response from GETting the requested URL after 
    226         login is complete. Returns False if login process failed. 
    227         """ 
    228         # First, GET the page that is login protected. 
    229         # This page will redirect to the login page. 
    230         response = self.get(path) 
    231         if response.status_code != 302: 
     224    def login(self, **credentials): 
     225        """Set the Client to appear as if it has sucessfully logged into a site. 
     226 
     227        Returns True if login is possible; False if the provided credentials 
     228        are incorrect, or if the Sessions framework is not available. 
     229        """ 
     230        user = authenticate(**credentials) 
     231        if user and 'django.contrib.sessions' in settings.INSTALLED_APPS: 
     232            obj = Session.objects.get_new_session_object() 
     233 
     234            # Create a fake request to store login details 
     235            request = HttpRequest() 
     236            request.session = SessionWrapper(obj.session_key) 
     237            login(request, user) 
     238 
     239            # Set the cookie to represent the session 
     240            self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key 
     241            self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None 
     242            self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/' 
     243            self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN 
     244            self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None 
     245            self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None 
     246 
     247            # Set the session values 
     248            Session.objects.save(obj.session_key, request.session._session, 
     249                datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))         
     250 
     251            return True 
     252        else: 
    232253            return False 
    233  
    234         _, _, login_path, _, data, _= urlparse(response['Location']) 
    235         next = data.split('=')[1] 
    236  
    237         # Second, GET the login page; required to set up cookies 
    238         response = self.get(login_path, **extra) 
    239         if response.status_code != 200: 
    240             return False 
    241  
    242         # Last, POST the login data. 
    243         form_data = { 
    244             'username': username, 
    245             'password': password, 
    246             'next' : next, 
    247         } 
    248         response = self.post(login_path, data=form_data, **extra) 
    249  
    250         # Login page should 302 redirect to the originally requested page 
    251         if (response.status_code != 302 or  
    252                 urlparse(response['Location'])[2] != path): 
    253             return False 
    254  
    255         # Since we are logged in, request the actual page again 
    256         return self.get(path) 
     254             
  • django/trunk/docs/testing.txt

    r5150 r5152  
    247247    need to manually close the file after it has been provided to the POST. 
    248248 
    249 ``login(path, username, password)`` 
    250     In a production site, it is likely that some views will be protected with 
    251     the @login_required decorator provided by ``django.contrib.auth``. Interacting 
    252     with a URL that has been login protected is a slightly complex operation, 
    253     so the Test Client provides a simple method to automate the login process. A 
    254     call to ``login()`` stimulates the series of GET and POST calls required 
    255     to log a user into a @login_required protected view. 
    256  
    257     If login is possible, the final return value of ``login()`` is the response 
    258     that is generated by issuing a GET request on the protected URL. If login 
    259     is not possible, ``login()`` returns False. 
    260  
     249``login(**credentials)`` 
     250    ** New in Django development version ** 
     251     
     252    On a production site, it is likely that some views will be protected from 
     253    anonymous access through the use of the @login_required decorator, or some 
     254    other login checking mechanism. The ``login()`` method can be used to 
     255    simulate the effect of a user logging into the site. As a result of calling 
     256    this method, the Client will have all the cookies and session data required 
     257    to pass any login-based tests that may form part of a view. 
     258     
     259    In most cases, the ``credentials`` required by this method are the username  
     260    and password of the user that wants to log in, provided as keyword  
     261    arguments:: 
     262     
     263        c = Client() 
     264        c.login(username='fred', password='secret') 
     265        # Now you can access a login protected view 
     266 
     267    If you are using a different authentication backend, this method may 
     268    require different credentials. 
     269     
     270    ``login()`` returns ``True`` if it the credentials were accepted and login  
     271    was successful.  
     272         
    261273    Note that since the test suite will be executed using the test database, 
    262     which contains no users by default. As a result, logins for your production 
    263     site will not work. You will need to create users as part of the test suite 
    264     to be able to test logins to your application. 
     274    which contains no users by default. As a result, logins that are valid 
     275    on your production site will not work under test conditions. You will  
     276    need to create users as part of the test suite (either manually, or 
     277    using a test fixture). 
    265278 
    266279Testing Responses 
  • django/trunk/tests/modeltests/test_client/models.py

    r5150 r5152  
    128128        self.assertRedirects(response, '/accounts/login/') 
    129129         
     130        # Log in 
     131        self.client.login(username='testclient', password='password') 
     132 
    130133        # Request a page that requires a login 
    131         response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') 
    132         self.failUnless(response) 
     134        response = self.client.get('/test_client/login_protected_view/') 
    133135        self.assertEqual(response.status_code, 200) 
    134136        self.assertEqual(response.context['user'].username, 'testclient') 
    135         self.assertEqual(response.template.name, 'Login Template') 
    136137 
    137138    def test_view_with_bad_login(self): 
    138139        "Request a page that is protected with @login, but use bad credentials" 
    139140 
    140         response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword') 
    141         self.failIf(response
     141        login = self.client.login(username='otheruser', password='nopassword') 
     142        self.failIf(login
    142143 
    143144    def test_session_modifying_view(self):