from __future__ import unicode_literals, absolute_import import collections from urlparse import urlsplit from django.core.urlresolvers import resolve, reverse from django.contrib.messages.storage.fallback import FallbackStorage from django.contrib.auth.models import User from django.test.client import RequestFactory from django.core.files import File class FakeSession(collections.MutableMapping): """ http://stackoverflow.com/questions/3387691/python-how-to-perfectly-override-a-dict """ def __init__(self, *args, **kwargs): self.store = dict() self.update(dict(*args, **kwargs)) # use the free update to set keys def __getitem__(self, key): return self.store[self.__keytransform__(key)] def __setitem__(self, key, value): self.store[self.__keytransform__(key)] = value def __delitem__(self, key): del self.store[self.__keytransform__(key)] def __iter__(self): return iter(self.store) def __len__(self): return len(self.store) def __keytransform__(self, key): return key def set_test_cookie(self): pass def flush(self): pass class FastDispatchMixin(object): default_cms_page = None def get_fake_request(self, path, method='get', get_params=None, post_params=None, request_extras=None, file_params=None): get_params = get_params if get_params else {} post_params = post_params if post_params else {} file_params = file_params if file_params else {} factory = RequestFactory() handler = getattr(factory, method) request = handler(path, post_params) request.GET = request.GET.copy() for key, value in get_params.iteritems(): if hasattr(value, '__iter__'): request.GET.setlist(key, value) else: if not isinstance(value, basestring): raise Exception("GET and POST can only contain strings, " "but %s = %s (%s)" % (key, value, value.__class__)) request.GET.setlist(key, [value]) request.POST = request.POST.copy() for key, value in post_params.iteritems(): if isinstance(value, File): request.FILES.setlist(key, [value]) elif hasattr(value, '__iter__'): request.POST.setlist(key, value) else: if not isinstance(value, basestring): raise Exception("GET and POST can only contain strings, " "but %s = %s (%s)" % (key, value, value.__class__)) request.POST.setlist(key, [value]) for key, value in file_params.iteritems(): request.FILES.setlist(key, [value]) # Make them immutable to catch abuses that otherwise would only # appear in real life, not in the tests. request.GET._mutable = False request.POST._mutable = False request.session = FakeSession() request._messages = FallbackStorage(request) from django.conf import settings if 'django.contrib.auth.middleware.AuthenticationMiddleware' in settings.MIDDLEWARE_CLASSES: from django.contrib.auth.models import AnonymousUser request.user = getattr(self, 'user', AnonymousUser()) if 'django.middleware.locale.LocaleMiddleware' in settings.MIDDLEWARE_CLASSES: request.LANGUAGE_CODE = settings.LANGUAGE_CODE # Resources filter plugin tests use this a lot. request.current_page = self.default_cms_page self.request_hook(request) if request_extras is not None: for key, value in request_extras.iteritems(): setattr(request, key, value) return request def fast_dispatch(self, view_name, method='get', url_args=None, url_kwargs=None, post_params=None, get_params=None, language=None, request_extras=None, file_params=None): url_args = url_args if url_args else [] url_kwargs = url_kwargs if url_kwargs else {} from django.utils.translation import override with override(language): path = reverse(view_name, args=url_args, kwargs=url_kwargs) resolved = resolve(path) view = resolved.func view.request = self.get_fake_request(path, method, get_params, post_params, request_extras, file_params) self.last_request = view.request response = view(view.request, *resolved.args, **resolved.kwargs) response.view = view # make sure that we render while language override is in effect! if language is not None and hasattr(response, 'render'): response.render() return response def request_hook(self, request): pass def assertRedirectsNoFollow(self, response, expected_url): self.assertTrue(response._headers['location'][1].endswith(expected_url)) self.assertEqual(response.status_code, 302) def assert_not_redirected(self, response, expected_status_code=200, msg_prefix=''): if msg_prefix: msg_prefix += ": " try: location = response._headers['location'][1] msg_prefix += ("unexpectedly redirected to %s" % location) except KeyError: # no location header pass self.assertEqual(response.status_code, expected_status_code, msg_prefix) def assert_no_adminform_with_errors(self, response): if not hasattr(response, 'context'): return adminform = self.assertInDict('adminform', response.context) if not adminform: return # if there are global errors, this will fail, and show us all # the errors when it does. self.assertDictEqual({}, adminform.form.errors) # if there are field errors, this will fail, and show us the # the field name and the errors for fieldset in adminform: for line in fieldset: # should this be line.errors()? # as FieldlineWithCustomReadOnlyField.errors # is a method, not a property: self.assertIsNone(line.errors, "should not be any errors on %s" % line) for field in line: # similarly django.contrib.admin.helpers.AdminField.errors # is a method: self.assertIsNone(field.errors, "should not be any errors on %s" % field) self.assertIsNone(adminform.form.non_field_errors) def assert_redirected_mini(self, response, expected_url, status_code=302, host=None, msg_prefix=''): """ Without trying to retrieve the redirect target URL, so it works with fast_dispatch. """ if msg_prefix: msg_prefix += ": " if 'content' in dir(response): if hasattr(response, 'render'): response.render() if isinstance(response.content, str): content = unicode(response.content, 'utf-8') else: content = response.content msg_prefix = content + u"\n\n" + unicode(msg_prefix) self.assertEqual(response.status_code, status_code, msg_prefix + "Response didn't redirect as expected: Response" " code was %d (expected %d)" % (response.status_code, status_code)) actual_url = response['Location'] scheme, netloc, path, query, fragment = urlsplit(actual_url) # Redirect URLs are canonicalised by middleware that we've bypassed, # so we're expecting a path, not a URL, for local redirects. """ e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) if not (e_scheme or e_netloc): expected_url = urlunsplit(('http', host or 'testserver', e_path, e_query, e_fragment)) """ self.assertEqual(actual_url, expected_url, msg_prefix + "Response redirected to '%s', expected '%s'" % (actual_url, expected_url)) def assert_login_required(self, view_name, message='', *args, **kwargs): response = self.fast_dispatch(view_name, *args, **kwargs) from django.core.urlresolvers import reverse uri = reverse(view_name, args=kwargs.get('url_args', []), kwargs=kwargs.get('url_kwargs', {})) from django.conf import settings login_url = settings.LOGIN_URL + "?next=" + uri self.assert_redirected_mini(response, login_url, msg_prefix=message) return response def stuff_session(self, dictionary): from django.conf import settings if settings.SESSION_ENGINE != 'django.contrib.sessions.backends.db': print "Unknown session engine: %s Sessions won't work" % settings.SESSION_ENGINE return self.client.logout() from django.contrib.sessions.backends.db import SessionStore store = SessionStore() store.save() # we need to make load() work, or the cookie is worthless self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key self.client.session.update(dictionary) self.client.session.save() self.client.login(username=self.user.username, password=self.password) def assertInDict(self, member, container, msg=None): """ Returns the member if the assertion passes. Makes sense that if you're asserting that a dictionary has a member, you might want to use that member! Just saying. If not, you can always throw it away. """ self.assertIn('__getitem__', dir(container), "Only use this assertion " "with a dict as the container") self.assertIn(member, container, msg=msg) try: return container[member] except TypeError as e: raise TypeError(("%s (is the second argument really a " + "dictionary? %s)") % (e, container)) def assertContains(self, response, text, count=None, status_code=200, msg_prefix='', html=False): if msg_prefix: msg_prefix = msg_prefix + ': ' if hasattr(response, 'render'): response.render() from django.utils.encoding import force_text content = force_text(response.content) msg_prefix = content + "\n\n" + msg_prefix try: super(FastDispatchMixin, self).assertContains(response, text, count, status_code, msg_prefix, html) except AssertionError as e: import sys raise sys.exc_info()[0], "%s\n\nThe complete response was:\n%s" % \ (e, content), sys.exc_info()[2] def absolute_url_for_site(self, relative_url): """ Convert a relative URL to an absolute URL, using the name of the current site, which is hackish but doesn't require a request object (so it can be generated in an email, for example), makes the canonical name configurable, and matches what the absurl templatetag does. """ from django.contrib.sites.models import Site return "http://%s%s" % (Site.objects.get_current().domain, relative_url) def absolute_url_for_request(self, relative_url): """ Convert a relative URL to an absolute URL, using the server name hard-coded in django.test.client.RequestFactory, which matches the value used by HttpRequest.build_absolute_uri when called by the test client. """ return "http://%s%s" % ('testserver', relative_url)