mirror of https://github.com/django/django.git
Fixed #15561 -- Extended test setting override code added in r16165 with a decorator and a signal for setting changes.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16237 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
091c9b530e
commit
a3a53e0b73
|
@ -1,3 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
template_rendered = Signal(providing_args=["template", "context"])
|
||||
|
||||
setting_changed = Signal(providing_args=["setting", "value"])
|
||||
|
|
|
@ -2,7 +2,6 @@ from __future__ import with_statement
|
|||
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
from xml.dom.minidom import parseString, Node
|
||||
|
@ -17,7 +16,7 @@ from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS,
|
|||
from django.http import QueryDict
|
||||
from django.test import _doctest as doctest
|
||||
from django.test.client import Client
|
||||
from django.test.utils import get_warnings_state, restore_warnings_state
|
||||
from django.test.utils import get_warnings_state, restore_warnings_state, override_settings
|
||||
from django.utils import simplejson, unittest as ut2
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
|
@ -342,21 +341,12 @@ class TransactionTestCase(ut2.TestCase):
|
|||
"""
|
||||
restore_warnings_state(self._warnings_state)
|
||||
|
||||
@contextmanager
|
||||
def settings(self, **options):
|
||||
def settings(self, **kwargs):
|
||||
"""
|
||||
A context manager that temporarily sets a setting and reverts
|
||||
back to the original value when exiting the context.
|
||||
"""
|
||||
old_wrapped = settings._wrapped
|
||||
override = UserSettingsHolder(settings._wrapped)
|
||||
try:
|
||||
for key, new_value in options.items():
|
||||
setattr(override, key, new_value)
|
||||
settings._wrapped = override
|
||||
yield
|
||||
finally:
|
||||
settings._wrapped = old_wrapped
|
||||
return override_settings(**kwargs)
|
||||
|
||||
def assertRedirects(self, response, expected_url, status_code=302,
|
||||
target_status_code=200, host=None, msg_prefix=''):
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
from __future__ import with_statement
|
||||
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import warnings
|
||||
from django.conf import settings
|
||||
from django.conf import settings, UserSettingsHolder
|
||||
from django.core import mail
|
||||
from django.core.mail.backends import locmem
|
||||
from django.test import signals
|
||||
from django.test.signals import template_rendered, setting_changed
|
||||
from django.template import Template, loader, TemplateDoesNotExist
|
||||
from django.template.loaders import cached
|
||||
from django.utils.translation import deactivate
|
||||
from django.utils.functional import wraps
|
||||
|
||||
__all__ = ('Approximate', 'ContextList', 'setup_test_environment',
|
||||
'teardown_test_environment', 'get_runner')
|
||||
|
||||
__all__ = (
|
||||
'Approximate', 'ContextList', 'get_runner', 'override_settings',
|
||||
'setup_test_environment', 'teardown_test_environment',
|
||||
)
|
||||
|
||||
RESTORE_LOADERS_ATTR = '_original_template_source_loaders'
|
||||
|
||||
|
@ -56,7 +62,7 @@ def instrumented_test_render(self, context):
|
|||
An instrumented Template render method, providing a signal
|
||||
that can be intercepted by the test system Client
|
||||
"""
|
||||
signals.template_rendered.send(sender=self, template=self, context=context)
|
||||
template_rendered.send(sender=self, template=self, context=context)
|
||||
return self.nodelist.render(context)
|
||||
|
||||
|
||||
|
@ -160,3 +166,46 @@ def restore_template_loaders():
|
|||
"""
|
||||
loader.template_source_loaders = getattr(loader, RESTORE_LOADERS_ATTR)
|
||||
delattr(loader, RESTORE_LOADERS_ATTR)
|
||||
|
||||
|
||||
class OverrideSettingsHolder(UserSettingsHolder):
|
||||
"""
|
||||
A custom setting holder that sends a signal upon change.
|
||||
"""
|
||||
def __setattr__(self, name, value):
|
||||
UserSettingsHolder.__setattr__(self, name, value)
|
||||
setting_changed.send(sender=name, setting=name, value=value)
|
||||
|
||||
|
||||
class override_settings(object):
|
||||
"""
|
||||
Acts as either a decorator, or a context manager. If it's a decorator it
|
||||
takes a function and returns a wrapped function. If it's a contextmanager
|
||||
it's used with the ``with`` statement. In either event entering/exiting
|
||||
are called before and after, respectively, the function/block is executed.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.options = kwargs
|
||||
self.wrapped = settings._wrapped
|
||||
|
||||
def __enter__(self):
|
||||
self.enable()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.disable()
|
||||
|
||||
def __call__(self, func):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
with self:
|
||||
return func(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
def enable(self):
|
||||
override = OverrideSettingsHolder(settings._wrapped)
|
||||
for key, new_value in self.options.items():
|
||||
setattr(override, key, new_value)
|
||||
settings._wrapped = override
|
||||
|
||||
def disable(self):
|
||||
settings._wrapped = self.wrapped
|
||||
|
|
|
@ -460,6 +460,29 @@ Test signals
|
|||
|
||||
Signals only sent when :doc:`running tests </topics/testing>`.
|
||||
|
||||
setting_changed
|
||||
---------------
|
||||
|
||||
.. versionadded:: 1.4
|
||||
|
||||
.. data:: django.test.signals.setting_changed
|
||||
:module:
|
||||
|
||||
Sent when some :ref:`settings are overridden <overriding-setting>` with the
|
||||
:meth:`django.test.TestCase.setting` context manager or the
|
||||
:func:`django.test.utils.override_settings` decorator/context manager.
|
||||
|
||||
Arguments sent with this signal:
|
||||
|
||||
``sender``
|
||||
The setting name (string).
|
||||
|
||||
``setting``
|
||||
Same as sender
|
||||
|
||||
``value``
|
||||
The new setting value.
|
||||
|
||||
template_rendered
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -1361,6 +1361,8 @@ For example::
|
|||
This test case will flush *all* the test databases before running
|
||||
``testIndexPageView``.
|
||||
|
||||
.. _overriding-setting:
|
||||
|
||||
Overriding settings
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -1376,7 +1378,14 @@ this use case Django provides a standard `Python context manager`_
|
|||
from django.test import TestCase
|
||||
|
||||
class LoginTestCase(TestCase):
|
||||
def test_overriding_settings(self):
|
||||
|
||||
def test_login(self):
|
||||
|
||||
# First check for the default behavior
|
||||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
|
||||
|
||||
# Then override the LOGING_URL setting
|
||||
with self.settings(LOGIN_URL='/other/login/'):
|
||||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||
|
@ -1384,7 +1393,58 @@ this use case Django provides a standard `Python context manager`_
|
|||
This example will override the :setting:`LOGIN_URL` setting for the code
|
||||
in the ``with`` block and reset its value to the previous state afterwards.
|
||||
|
||||
.. function:: utils.override_settings
|
||||
|
||||
In case you want to override a setting for just one test method or even the
|
||||
whole TestCase class, Django provides the
|
||||
:func:`django.test.utils.override_settings` decorator_. It's used like this::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
class LoginTestCase(TestCase):
|
||||
|
||||
@override_settings(LOGIN_URL='/other/login/')
|
||||
def test_login(self):
|
||||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||
|
||||
The decorator can also be applied to test case classes::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
class LoginTestCase(TestCase):
|
||||
|
||||
def test_login(self):
|
||||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||
|
||||
LoginTestCase = override_settings(LOGIN_URL='/other/login/')(LoginTestCase)
|
||||
|
||||
On Python 2.6 and higher you can also use the well known decorator syntax to
|
||||
decorate the class::
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
@override_settings(LOGIN_URL='/other/login/')
|
||||
class LoginTestCase(TestCase):
|
||||
|
||||
def test_login(self):
|
||||
response = self.client.get('/sekrit/')
|
||||
self.assertRedirects(response, '/other/login/?next=/sekrit/')
|
||||
|
||||
.. note::
|
||||
|
||||
When overriding settings make sure to also handle the cases in which
|
||||
Django or your app's code use a cache or another feature that retain
|
||||
state even if the setting is changed. Django provides the
|
||||
:data:`django.test.signals.setting_changed` signal to connect cleanup
|
||||
and other state resetting callbacks to.
|
||||
|
||||
.. _`Python context manager`: http://www.python.org/dev/peps/pep-0343/
|
||||
.. _`decorator`: http://www.python.org/dev/peps/pep-0318/
|
||||
|
||||
Emptying the test outbox
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -9,48 +9,14 @@ from StringIO import StringIO
|
|||
import tempfile
|
||||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
|
||||
EmailMultiAlternatives, send_mail, send_mass_mail)
|
||||
from django.core.mail.backends import console, dummy, locmem, filebased, smtp
|
||||
from django.core.mail.message import BadHeaderError
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.utils.functional import wraps
|
||||
|
||||
|
||||
def alter_django_settings(**kwargs):
|
||||
oldvalues = {}
|
||||
nonexistant = []
|
||||
for setting, newvalue in kwargs.iteritems():
|
||||
try:
|
||||
oldvalues[setting] = getattr(settings, setting)
|
||||
except AttributeError:
|
||||
nonexistant.append(setting)
|
||||
setattr(settings, setting, newvalue)
|
||||
return oldvalues, nonexistant
|
||||
|
||||
|
||||
def restore_django_settings(state):
|
||||
oldvalues, nonexistant = state
|
||||
for setting, oldvalue in oldvalues.iteritems():
|
||||
setattr(settings, setting, oldvalue)
|
||||
for setting in nonexistant:
|
||||
delattr(settings, setting)
|
||||
|
||||
|
||||
def with_django_settings(**kwargs):
|
||||
def decorator(test):
|
||||
@wraps(test)
|
||||
def decorated_test(self):
|
||||
state = alter_django_settings(**kwargs)
|
||||
try:
|
||||
return test(self)
|
||||
finally:
|
||||
restore_django_settings(state)
|
||||
return decorated_test
|
||||
return decorator
|
||||
|
||||
|
||||
class MailTests(TestCase):
|
||||
|
@ -251,7 +217,7 @@ class MailTests(TestCase):
|
|||
shutil.rmtree(tmp_dir)
|
||||
self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend))
|
||||
|
||||
@with_django_settings(
|
||||
@override_settings(
|
||||
EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend',
|
||||
ADMINS=[('nobody', 'nobody@example.com')],
|
||||
MANAGERS=[('nobody', 'nobody@example.com')])
|
||||
|
@ -323,10 +289,11 @@ class BaseEmailBackendTests(object):
|
|||
email_backend = None
|
||||
|
||||
def setUp(self):
|
||||
self.__settings_state = alter_django_settings(EMAIL_BACKEND=self.email_backend)
|
||||
self.settings_override = override_settings(EMAIL_BACKEND=self.email_backend)
|
||||
self.settings_override.enable()
|
||||
|
||||
def tearDown(self):
|
||||
restore_django_settings(self.__settings_state)
|
||||
self.settings_override.disable()
|
||||
|
||||
def assertStartsWith(self, first, second):
|
||||
if not first.startswith(second):
|
||||
|
@ -375,7 +342,7 @@ class BaseEmailBackendTests(object):
|
|||
self.assertEqual(message.get_payload(), "Content")
|
||||
self.assertEqual(message["from"], "=?utf-8?q?Firstname_S=C3=BCrname?= <from@example.com>")
|
||||
|
||||
@with_django_settings(MANAGERS=[('nobody', 'nobody@example.com')])
|
||||
@override_settings(MANAGERS=[('nobody', 'nobody@example.com')])
|
||||
def test_html_mail_managers(self):
|
||||
"""Test html_message argument to mail_managers"""
|
||||
mail_managers('Subject', 'Content', html_message='HTML Content')
|
||||
|
@ -390,7 +357,7 @@ class BaseEmailBackendTests(object):
|
|||
self.assertEqual(message.get_payload(1).get_payload(), 'HTML Content')
|
||||
self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
|
||||
|
||||
@with_django_settings(ADMINS=[('nobody', 'nobody@example.com')])
|
||||
@override_settings(ADMINS=[('nobody', 'nobody@example.com')])
|
||||
def test_html_mail_admins(self):
|
||||
"""Test html_message argument to mail_admins """
|
||||
mail_admins('Subject', 'Content', html_message='HTML Content')
|
||||
|
@ -405,8 +372,9 @@ class BaseEmailBackendTests(object):
|
|||
self.assertEqual(message.get_payload(1).get_payload(), 'HTML Content')
|
||||
self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
|
||||
|
||||
@with_django_settings(ADMINS=[('nobody', 'nobody+admin@example.com')],
|
||||
MANAGERS=[('nobody', 'nobody+manager@example.com')])
|
||||
@override_settings(
|
||||
ADMINS=[('nobody', 'nobody+admin@example.com')],
|
||||
MANAGERS=[('nobody', 'nobody+manager@example.com')])
|
||||
def test_manager_and_admin_mail_prefix(self):
|
||||
"""
|
||||
String prefix + lazy translated subject = bad output
|
||||
|
@ -421,7 +389,7 @@ class BaseEmailBackendTests(object):
|
|||
message = self.get_the_message()
|
||||
self.assertEqual(message.get('subject'), '[Django] Subject')
|
||||
|
||||
@with_django_settings(ADMINS=(), MANAGERS=())
|
||||
@override_settings(ADMINS=(), MANAGERS=())
|
||||
def test_empty_admins(self):
|
||||
"""
|
||||
Test that mail_admins/mail_managers doesn't connect to the mail server
|
||||
|
@ -501,13 +469,14 @@ class FileBackendTests(BaseEmailBackendTests, TestCase):
|
|||
email_backend = 'django.core.mail.backends.filebased.EmailBackend'
|
||||
|
||||
def setUp(self):
|
||||
super(FileBackendTests, self).setUp()
|
||||
self.tmp_dir = tempfile.mkdtemp()
|
||||
self.__settings_state = alter_django_settings(EMAIL_FILE_PATH=self.tmp_dir)
|
||||
self.addCleanup(shutil.rmtree, self.tmp_dir)
|
||||
self.settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir)
|
||||
self.settings_override.enable()
|
||||
super(FileBackendTests, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
restore_django_settings(self.__settings_state)
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
self.settings_override.disable()
|
||||
super(FileBackendTests, self).tearDown()
|
||||
|
||||
def flush_mailbox(self):
|
||||
|
@ -642,13 +611,15 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.server = FakeSMTPServer(('127.0.0.1', 0), None)
|
||||
cls.settings = alter_django_settings(
|
||||
cls.settings_override = override_settings(
|
||||
EMAIL_HOST="127.0.0.1",
|
||||
EMAIL_PORT=cls.server.socket.getsockname()[1])
|
||||
cls.settings_override.enable()
|
||||
cls.server.start()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.settings_override.disable()
|
||||
cls.server.stop()
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,7 +1,22 @@
|
|||
from __future__ import with_statement
|
||||
import os
|
||||
import os, sys
|
||||
from django.conf import settings, global_settings
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, signals
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.unittest import skipIf
|
||||
|
||||
|
||||
class SettingGetter(object):
|
||||
def __init__(self):
|
||||
self.test = getattr(settings, 'TEST', 'undefined')
|
||||
|
||||
testvalue = None
|
||||
|
||||
def signal_callback(sender, setting, value, **kwargs):
|
||||
global testvalue
|
||||
testvalue = value
|
||||
|
||||
signals.setting_changed.connect(signal_callback, sender='TEST')
|
||||
|
||||
class SettingsTests(TestCase):
|
||||
|
||||
|
@ -29,6 +44,43 @@ class SettingsTests(TestCase):
|
|||
settings.TEST = 'test'
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
|
||||
@override_settings(TEST='override')
|
||||
def test_decorator(self):
|
||||
self.assertEqual('override', settings.TEST)
|
||||
|
||||
def test_context_manager(self):
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
override = override_settings(TEST='override')
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
override.enable()
|
||||
self.assertEqual('override', settings.TEST)
|
||||
override.disable()
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
|
||||
def test_class_decorator(self):
|
||||
self.assertEqual(SettingGetter().test, 'undefined')
|
||||
DecoratedSettingGetter = override_settings(TEST='override')(SettingGetter)
|
||||
self.assertEqual(DecoratedSettingGetter().test, 'override')
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
|
||||
@skipIf(sys.version_info[:2] < (2, 6), "Python version is lower than 2.6")
|
||||
def test_new_class_decorator(self):
|
||||
self.assertEqual(SettingGetter().test, 'undefined')
|
||||
@override_settings(TEST='override')
|
||||
class DecoratedSettingGetter(SettingGetter):
|
||||
pass
|
||||
self.assertEqual(DecoratedSettingGetter().test, 'override')
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
|
||||
def test_signal_callback_context_manager(self):
|
||||
self.assertRaises(AttributeError, getattr, settings, 'TEST')
|
||||
with self.settings(TEST='override'):
|
||||
self.assertEqual(testvalue, 'override')
|
||||
|
||||
@override_settings(TEST='override')
|
||||
def test_signal_callback_decorator(self):
|
||||
self.assertEqual(testvalue, 'override')
|
||||
|
||||
#
|
||||
# Regression tests for #10130: deleting settings.
|
||||
#
|
||||
|
|
Loading…
Reference in New Issue