diff options
Diffstat (limited to 'django/test/client.py')
| -rw-r--r-- | django/test/client.py | 127 |
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) + |
