Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Backwards incompatible change: Changed the way test.Client.login oper…

…ates. 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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5152 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 36b164d838c3de168defe9f1ebc02ea1abc790be 1 parent a0ef3ba
@freakboy3742 freakboy3742 authored
View
88 django/test/client.py
@@ -1,12 +1,16 @@
+import datetime
import sys
from cStringIO import StringIO
from urlparse import urlparse
from django.conf import settings
+from django.contrib.auth import authenticate, login
+from django.contrib.sessions.models import Session
+from django.contrib.sessions.middleware import SessionWrapper
from django.core.handlers.base import BaseHandler
from django.core.handlers.wsgi import WSGIRequest
from django.core.signals import got_request_exception
from django.dispatch import dispatcher
-from django.http import urlencode, SimpleCookie
+from django.http import urlencode, SimpleCookie, HttpRequest
from django.test import signals
from django.utils.functional import curry
@@ -113,7 +117,6 @@ def __init__(self, **defaults):
self.handler = ClientHandler()
self.defaults = defaults
self.cookies = SimpleCookie()
- self.session = {}
self.exc_info = None
def store_exc_info(self, *args, **kwargs):
@@ -123,6 +126,15 @@ def store_exc_info(self, *args, **kwargs):
"""
self.exc_info = sys.exc_info()
+ def _session(self):
+ "Obtain the current session variables"
+ if 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
+ if cookie:
+ return SessionWrapper(cookie.value)
+ return {}
+ session = property(_session)
+
def request(self, **request):
"""
The master request method. Composes the environment dictionary
@@ -171,16 +183,10 @@ def request(self, **request):
if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2]
- # Update persistent cookie and session data
+ # Update persistent cookie data
if response.cookies:
self.cookies.update(response.cookies)
- if 'django.contrib.sessions' in settings.INSTALLED_APPS:
- from django.contrib.sessions.middleware import SessionWrapper
- cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
- if cookie:
- self.session = SessionWrapper(cookie.value)
-
return response
def get(self, path, data={}, **extra):
@@ -215,42 +221,34 @@ def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
return self.request(**r)
- def login(self, path, username, password, **extra):
- """
- A specialized sequence of GET and POST to log into a view that
- is protected by a @login_required access decorator.
-
- path should be the URL of the page that is login protected.
+ def login(self, **credentials):
+ """Set the Client to appear as if it has sucessfully logged into a site.
- Returns the response from GETting the requested URL after
- login is complete. Returns False if login process failed.
+ Returns True if login is possible; False if the provided credentials
+ are incorrect, or if the Sessions framework is not available.
"""
- # First, GET the page that is login protected.
- # This page will redirect to the login page.
- response = self.get(path)
- if response.status_code != 302:
- return False
-
- _, _, login_path, _, data, _= urlparse(response['Location'])
- next = data.split('=')[1]
-
- # Second, GET the login page; required to set up cookies
- response = self.get(login_path, **extra)
- if response.status_code != 200:
- return False
-
- # Last, POST the login data.
- form_data = {
- 'username': username,
- 'password': password,
- 'next' : next,
- }
- response = self.post(login_path, data=form_data, **extra)
-
- # Login page should 302 redirect to the originally requested page
- if (response.status_code != 302 or
- urlparse(response['Location'])[2] != path):
+ user = authenticate(**credentials)
+ if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ obj = Session.objects.get_new_session_object()
+
+ # Create a fake request to store login details
+ request = HttpRequest()
+ request.session = SessionWrapper(obj.session_key)
+ login(request, user)
+
+ # Set the cookie to represent the session
+ self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
+ self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
+ self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
+ self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
+ self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
+ self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
+
+ # Set the session values
+ Session.objects.save(obj.session_key, request.session._session,
+ datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+
+ return True
+ else:
return False
-
- # Since we are logged in, request the actual page again
- return self.get(path)
+
View
41 docs/testing.txt
@@ -246,22 +246,35 @@ can be invoked on the ``Client`` instance.
file name), and `attachment_file` (containing the file data). Note that you
need to manually close the file after it has been provided to the POST.
-``login(path, username, password)``
- In a production site, it is likely that some views will be protected with
- the @login_required decorator provided by ``django.contrib.auth``. Interacting
- with a URL that has been login protected is a slightly complex operation,
- so the Test Client provides a simple method to automate the login process. A
- call to ``login()`` stimulates the series of GET and POST calls required
- to log a user into a @login_required protected view.
-
- If login is possible, the final return value of ``login()`` is the response
- that is generated by issuing a GET request on the protected URL. If login
- is not possible, ``login()`` returns False.
+``login(**credentials)``
+ ** New in Django development version **
+
+ On a production site, it is likely that some views will be protected from
+ anonymous access through the use of the @login_required decorator, or some
+ other login checking mechanism. The ``login()`` method can be used to
+ simulate the effect of a user logging into the site. As a result of calling
+ this method, the Client will have all the cookies and session data required
+ to pass any login-based tests that may form part of a view.
+
+ In most cases, the ``credentials`` required by this method are the username
+ and password of the user that wants to log in, provided as keyword
+ arguments::
+
+ c = Client()
+ c.login(username='fred', password='secret')
+ # Now you can access a login protected view
+ If you are using a different authentication backend, this method may
+ require different credentials.
+
+ ``login()`` returns ``True`` if it the credentials were accepted and login
+ was successful.
+
Note that since the test suite will be executed using the test database,
- which contains no users by default. As a result, logins for your production
- site will not work. You will need to create users as part of the test suite
- to be able to test logins to your application.
+ which contains no users by default. As a result, logins that are valid
+ on your production site will not work under test conditions. You will
+ need to create users as part of the test suite (either manually, or
+ using a test fixture).
Testing Responses
~~~~~~~~~~~~~~~~~
View
11 tests/modeltests/test_client/models.py
@@ -127,18 +127,19 @@ def test_view_with_login(self):
response = self.client.get('/test_client/login_protected_view/')
self.assertRedirects(response, '/accounts/login/')
+ # Log in
+ self.client.login(username='testclient', password='password')
+
# Request a page that requires a login
- response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password')
- self.failUnless(response)
+ response = self.client.get('/test_client/login_protected_view/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
- self.assertEqual(response.template.name, 'Login Template')
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"
- response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword')
- self.failIf(response)
+ login = self.client.login(username='otheruser', password='nopassword')
+ self.failIf(login)
def test_session_modifying_view(self):
"Request a page that modifies the session"
Please sign in to comment.
Something went wrong with that request. Please try again.