summaryrefslogtreecommitdiff
path: root/django/test/client.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/test/client.py')
-rw-r--r--django/test/client.py127
1 files changed, 81 insertions, 46 deletions
diff --git a/django/test/client.py b/django/test/client.py
index 6e0b443f83..e4fd54c23b 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -1,11 +1,22 @@
+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
+BOUNDARY = 'BoUnDaRyStRiNg'
+MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
+
class ClientHandler(BaseHandler):
"""
A HTTP Handler that can be used for testing purposes.
@@ -54,14 +65,19 @@ def encode_multipart(boundary, data):
if isinstance(value, file):
lines.extend([
'--' + boundary,
- 'Content-Disposition: form-data; name="%s"' % key,
- '',
- '--' + boundary,
- 'Content-Disposition: form-data; name="%s_file"; filename="%s"' % (key, value.name),
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' % (key, value.name),
'Content-Type: application/octet-stream',
'',
value.read()
])
+ elif hasattr(value, '__iter__'):
+ for item in value:
+ lines.extend([
+ '--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % key,
+ '',
+ str(item)
+ ])
else:
lines.extend([
'--' + boundary,
@@ -97,8 +113,25 @@ class Client:
def __init__(self, **defaults):
self.handler = ClientHandler()
self.defaults = defaults
- self.cookie = SimpleCookie()
+ self.cookies = SimpleCookie()
+ self.exc_info = None
+
+ def store_exc_info(self, *args, **kwargs):
+ """
+ Utility method that can be used to store exceptions when they are
+ generated by a view.
+ """
+ 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
@@ -108,7 +141,7 @@ class Client:
"""
environ = {
- 'HTTP_COOKIE': self.cookie,
+ 'HTTP_COOKIE': self.cookies,
'PATH_INFO': '/',
'QUERY_STRING': '',
'REQUEST_METHOD': 'GET',
@@ -126,6 +159,9 @@ class Client:
on_template_render = curry(store_rendered_templates, data)
dispatcher.connect(on_template_render, signal=signals.template_rendered)
+ # Capture exceptions created by the handler
+ dispatcher.connect(self.store_exc_info, signal=got_request_exception)
+
response = self.handler(environ)
# Add any rendered template detail to the response
@@ -140,8 +176,13 @@ class Client:
else:
setattr(response, detail, None)
+ # Look for a signalled exception and reraise it
+ if self.exc_info:
+ raise self.exc_info[1], None, self.exc_info[2]
+
+ # Update persistent cookie data
if response.cookies:
- self.cookie.update(response.cookies)
+ self.cookies.update(response.cookies)
return response
@@ -158,59 +199,53 @@ class Client:
return self.request(**r)
- def post(self, path, data={}, **extra):
+ def post(self, path, data={}, content_type=MULTIPART_CONTENT, **extra):
"Request a response from the server using POST."
- BOUNDARY = 'BoUnDaRyStRiNg'
+ if content_type is MULTIPART_CONTENT:
+ post_data = encode_multipart(BOUNDARY, data)
+ else:
+ post_data = data
- encoded = encode_multipart(BOUNDARY, data)
- stream = StringIO(encoded)
r = {
- 'CONTENT_LENGTH': len(encoded),
- 'CONTENT_TYPE': 'multipart/form-data; boundary=%s' % BOUNDARY,
+ 'CONTENT_LENGTH': len(post_data),
+ 'CONTENT_TYPE': content_type,
'PATH_INFO': path,
'REQUEST_METHOD': 'POST',
- 'wsgi.input': stream,
+ 'wsgi.input': StringIO(post_data),
}
r.update(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
+ user = authenticate(**credentials)
+ if user and 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ obj = Session.objects.get_new_session_object()
- login_path, data = response['Location'].split('?')
- next = data.split('=')[1]
+ # Create a fake request to store login details
+ request = HttpRequest()
+ request.session = SessionWrapper(obj.session_key)
+ login(request, user)
- # Second, GET the login page; required to set up cookies
- response = self.get(login_path, **extra)
- if response.status_code != 200:
- return False
+ # 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
- # Last, POST the login data.
- form_data = {
- 'username': username,
- 'password': password,
- 'next' : next,
- }
- response = self.post(login_path, data=form_data, **extra)
+ # Set the session values
+ Session.objects.save(obj.session_key, request.session._session,
+ datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
- # Login page should 302 redirect to the originally requested page
- if response.status_code != 302 or response['Location'] != path:
+ return True
+ else:
return False
-
- # Since we are logged in, request the actual page again
- return self.get(path)
+