import os import sys import unittest from types import ModuleType, SimpleNamespace from unittest import mock from django.conf import ( ENVIRONMENT_VARIABLE, USE_DEPRECATED_PYTZ_DEPRECATED_MSG, LazySettings, Settings, settings, ) from django.core.exceptions import ImproperlyConfigured from django.http import HttpRequest from django.test import ( SimpleTestCase, TestCase, TransactionTestCase, modify_settings, override_settings, signals, ) from django.test.utils import requires_tz_support from django.urls import clear_script_prefix, set_script_prefix from django.utils.deprecation import RemovedInDjango50Warning @modify_settings(ITEMS={"prepend": ["b"], "append": ["d"], "remove": ["a", "e"]}) @override_settings( ITEMS=["a", "c", "e"], ITEMS_OUTER=[1, 2, 3], TEST="override", TEST_OUTER="outer" ) class FullyDecoratedTranTestCase(TransactionTestCase): available_apps = [] def test_override(self): self.assertEqual(settings.ITEMS, ["b", "c", "d"]) self.assertEqual(settings.ITEMS_OUTER, [1, 2, 3]) self.assertEqual(settings.TEST, "override") self.assertEqual(settings.TEST_OUTER, "outer") @modify_settings( ITEMS={ "append": ["e", "f"], "prepend": ["a"], "remove": ["d", "c"], } ) def test_method_list_override(self): self.assertEqual(settings.ITEMS, ["a", "b", "e", "f"]) self.assertEqual(settings.ITEMS_OUTER, [1, 2, 3]) @modify_settings( ITEMS={ "append": ["b"], "prepend": ["d"], "remove": ["a", "c", "e"], } ) def test_method_list_override_no_ops(self): self.assertEqual(settings.ITEMS, ["b", "d"]) @modify_settings( ITEMS={ "append": "e", "prepend": "a", "remove": "c", } ) def test_method_list_override_strings(self): self.assertEqual(settings.ITEMS, ["a", "b", "d", "e"]) @modify_settings(ITEMS={"remove": ["b", "d"]}) @modify_settings(ITEMS={"append": ["b"], "prepend": ["d"]}) def test_method_list_override_nested_order(self): self.assertEqual(settings.ITEMS, ["d", "c", "b"]) @override_settings(TEST="override2") def test_method_override(self): self.assertEqual(settings.TEST, "override2") self.assertEqual(settings.TEST_OUTER, "outer") def test_decorated_testcase_name(self): self.assertEqual( FullyDecoratedTranTestCase.__name__, "FullyDecoratedTranTestCase" ) def test_decorated_testcase_module(self): self.assertEqual(FullyDecoratedTranTestCase.__module__, __name__) @modify_settings(ITEMS={"prepend": ["b"], "append": ["d"], "remove": ["a", "e"]}) @override_settings(ITEMS=["a", "c", "e"], TEST="override") class FullyDecoratedTestCase(TestCase): def test_override(self): self.assertEqual(settings.ITEMS, ["b", "c", "d"]) self.assertEqual(settings.TEST, "override") @modify_settings( ITEMS={ "append": "e", "prepend": "a", "remove": "c", } ) @override_settings(TEST="override2") def test_method_override(self): self.assertEqual(settings.ITEMS, ["a", "b", "d", "e"]) self.assertEqual(settings.TEST, "override2") class ClassDecoratedTestCaseSuper(TestCase): """ Dummy class for testing max recursion error in child class call to super(). Refs #17011. """ def test_max_recursion_error(self): pass @override_settings(TEST="override") class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper): @classmethod def setUpClass(cls): super().setUpClass() cls.foo = getattr(settings, "TEST", "BUG") def test_override(self): self.assertEqual(settings.TEST, "override") def test_setupclass_override(self): """Settings are overridden within setUpClass (#21281).""" self.assertEqual(self.foo, "override") @override_settings(TEST="override2") def test_method_override(self): self.assertEqual(settings.TEST, "override2") def test_max_recursion_error(self): """ Overriding a method on a super class and then calling that method on the super class should not trigger infinite recursion. See #17011. """ super().test_max_recursion_error() @modify_settings(ITEMS={"append": "mother"}) @override_settings(ITEMS=["father"], TEST="override-parent") class ParentDecoratedTestCase(TestCase): pass @modify_settings(ITEMS={"append": ["child"]}) @override_settings(TEST="override-child") class ChildDecoratedTestCase(ParentDecoratedTestCase): def test_override_settings_inheritance(self): self.assertEqual(settings.ITEMS, ["father", "mother", "child"]) self.assertEqual(settings.TEST, "override-child") class SettingsTests(SimpleTestCase): def setUp(self): self.testvalue = None signals.setting_changed.connect(self.signal_callback) def tearDown(self): signals.setting_changed.disconnect(self.signal_callback) def signal_callback(self, sender, setting, value, **kwargs): if setting == "TEST": self.testvalue = value def test_override(self): settings.TEST = "test" self.assertEqual("test", settings.TEST) with self.settings(TEST="override"): self.assertEqual("override", settings.TEST) self.assertEqual("test", settings.TEST) del settings.TEST def test_override_change(self): settings.TEST = "test" self.assertEqual("test", settings.TEST) with self.settings(TEST="override"): self.assertEqual("override", settings.TEST) settings.TEST = "test2" self.assertEqual("test", settings.TEST) del settings.TEST def test_override_doesnt_leak(self): with self.assertRaises(AttributeError): getattr(settings, "TEST") with self.settings(TEST="override"): self.assertEqual("override", settings.TEST) settings.TEST = "test" with self.assertRaises(AttributeError): getattr(settings, "TEST") @override_settings(TEST="override") def test_decorator(self): self.assertEqual("override", settings.TEST) def test_context_manager(self): with self.assertRaises(AttributeError): getattr(settings, "TEST") override = override_settings(TEST="override") with self.assertRaises(AttributeError): getattr(settings, "TEST") override.enable() self.assertEqual("override", settings.TEST) override.disable() with self.assertRaises(AttributeError): getattr(settings, "TEST") def test_class_decorator(self): # SimpleTestCase can be decorated by override_settings, but not ut.TestCase class SimpleTestCaseSubclass(SimpleTestCase): pass class UnittestTestCaseSubclass(unittest.TestCase): pass decorated = override_settings(TEST="override")(SimpleTestCaseSubclass) self.assertIsInstance(decorated, type) self.assertTrue(issubclass(decorated, SimpleTestCase)) with self.assertRaisesMessage( Exception, "Only subclasses of Django SimpleTestCase" ): decorated = override_settings(TEST="override")(UnittestTestCaseSubclass) def test_signal_callback_context_manager(self): with self.assertRaises(AttributeError): getattr(settings, "TEST") with self.settings(TEST="override"): self.assertEqual(self.testvalue, "override") self.assertIsNone(self.testvalue) @override_settings(TEST="override") def test_signal_callback_decorator(self): self.assertEqual(self.testvalue, "override") # # Regression tests for #10130: deleting settings. # def test_settings_delete(self): settings.TEST = "test" self.assertEqual("test", settings.TEST) del settings.TEST msg = "'Settings' object has no attribute 'TEST'" with self.assertRaisesMessage(AttributeError, msg): getattr(settings, "TEST") def test_settings_delete_wrapped(self): with self.assertRaisesMessage(TypeError, "can't delete _wrapped."): delattr(settings, "_wrapped") def test_override_settings_delete(self): """ Allow deletion of a setting in an overridden settings set (#18824) """ previous_i18n = settings.USE_I18N previous_tz = settings.USE_TZ with self.settings(USE_I18N=False): del settings.USE_I18N with self.assertRaises(AttributeError): getattr(settings, "USE_I18N") # Should also work for a non-overridden setting del settings.USE_TZ with self.assertRaises(AttributeError): getattr(settings, "USE_TZ") self.assertNotIn("USE_I18N", dir(settings)) self.assertNotIn("USE_TZ", dir(settings)) self.assertEqual(settings.USE_I18N, previous_i18n) self.assertEqual(settings.USE_TZ, previous_tz) def test_override_settings_nested(self): """ override_settings uses the actual _wrapped attribute at runtime, not when it was instantiated. """ with self.assertRaises(AttributeError): getattr(settings, "TEST") with self.assertRaises(AttributeError): getattr(settings, "TEST2") inner = override_settings(TEST2="override") with override_settings(TEST="override"): self.assertEqual("override", settings.TEST) with inner: self.assertEqual("override", settings.TEST) self.assertEqual("override", settings.TEST2) # inner's __exit__ should have restored the settings of the outer # context manager, not those when the class was instantiated self.assertEqual("override", settings.TEST) with self.assertRaises(AttributeError): getattr(settings, "TEST2") with self.assertRaises(AttributeError): getattr(settings, "TEST") with self.assertRaises(AttributeError): getattr(settings, "TEST2") @override_settings(SECRET_KEY="") def test_no_secret_key(self): msg = "The SECRET_KEY setting must not be empty." with self.assertRaisesMessage(ImproperlyConfigured, msg): settings.SECRET_KEY def test_no_settings_module(self): msg = ( "Requested setting%s, but settings are not configured. You " "must either define the environment variable DJANGO_SETTINGS_MODULE " "or call settings.configure() before accessing settings." ) orig_settings = os.environ[ENVIRONMENT_VARIABLE] os.environ[ENVIRONMENT_VARIABLE] = "" try: with self.assertRaisesMessage(ImproperlyConfigured, msg % "s"): settings._setup() with self.assertRaisesMessage(ImproperlyConfigured, msg % " TEST"): settings._setup("TEST") finally: os.environ[ENVIRONMENT_VARIABLE] = orig_settings def test_already_configured(self): with self.assertRaisesMessage(RuntimeError, "Settings already configured."): settings.configure() def test_nonupper_settings_prohibited_in_configure(self): s = LazySettings() with self.assertRaisesMessage(TypeError, "Setting 'foo' must be uppercase."): s.configure(foo="bar") def test_nonupper_settings_ignored_in_default_settings(self): s = LazySettings() s.configure(SimpleNamespace(foo="bar")) with self.assertRaises(AttributeError): getattr(s, "foo") @requires_tz_support @mock.patch("django.conf.global_settings.TIME_ZONE", "test") def test_incorrect_timezone(self): with self.assertRaisesMessage(ValueError, "Incorrect timezone setting: test"): settings._setup() def test_use_tz_false_deprecation(self): settings_module = ModuleType("fake_settings_module") settings_module.SECRET_KEY = "foo" sys.modules["fake_settings_module"] = settings_module msg = ( "The default value of USE_TZ will change from False to True in " "Django 5.0. Set USE_TZ to False in your project settings if you " "want to keep the current default behavior." ) try: with self.assertRaisesMessage(RemovedInDjango50Warning, msg): Settings("fake_settings_module") finally: del sys.modules["fake_settings_module"] def test_use_deprecated_pytz_deprecation(self): settings_module = ModuleType("fake_settings_module") settings_module.USE_DEPRECATED_PYTZ = True settings_module.USE_TZ = True sys.modules["fake_settings_module"] = settings_module try: with self.assertRaisesMessage( RemovedInDjango50Warning, USE_DEPRECATED_PYTZ_DEPRECATED_MSG ): Settings("fake_settings_module") finally: del sys.modules["fake_settings_module"] holder = LazySettings() with self.assertRaisesMessage( RemovedInDjango50Warning, USE_DEPRECATED_PYTZ_DEPRECATED_MSG ): holder.configure(USE_DEPRECATED_PYTZ=True) class TestComplexSettingOverride(SimpleTestCase): def setUp(self): self.old_warn_override_settings = signals.COMPLEX_OVERRIDE_SETTINGS.copy() signals.COMPLEX_OVERRIDE_SETTINGS.add("TEST_WARN") def tearDown(self): signals.COMPLEX_OVERRIDE_SETTINGS = self.old_warn_override_settings self.assertNotIn("TEST_WARN", signals.COMPLEX_OVERRIDE_SETTINGS) def test_complex_override_warning(self): """Regression test for #19031""" msg = "Overriding setting TEST_WARN can lead to unexpected behavior." with self.assertWarnsMessage(UserWarning, msg) as cm: with override_settings(TEST_WARN="override"): self.assertEqual(settings.TEST_WARN, "override") self.assertEqual(cm.filename, __file__) class SecureProxySslHeaderTest(SimpleTestCase): @override_settings(SECURE_PROXY_SSL_HEADER=None) def test_none(self): req = HttpRequest() self.assertIs(req.is_secure(), False) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_without_xheader(self): req = HttpRequest() self.assertIs(req.is_secure(), False) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_with_xheader_wrong(self): req = HttpRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "wrongvalue" self.assertIs(req.is_secure(), False) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_with_xheader_right(self): req = HttpRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "https" self.assertIs(req.is_secure(), True) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_with_xheader_leftmost_right(self): req = HttpRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "https, http" self.assertIs(req.is_secure(), True) req.META["HTTP_X_FORWARDED_PROTO"] = "https , http" self.assertIs(req.is_secure(), True) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_with_xheader_leftmost_not_secure(self): req = HttpRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "http, https" self.assertIs(req.is_secure(), False) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_set_with_xheader_multiple_not_secure(self): req = HttpRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "http ,wrongvalue,http,http" self.assertIs(req.is_secure(), False) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) def test_xheader_preferred_to_underlying_request(self): class ProxyRequest(HttpRequest): def _get_scheme(self): """Proxy always connecting via HTTPS""" return "https" # Client connects via HTTP. req = ProxyRequest() req.META["HTTP_X_FORWARDED_PROTO"] = "http" self.assertIs(req.is_secure(), False) class IsOverriddenTest(SimpleTestCase): def test_configure(self): s = LazySettings() s.configure(SECRET_KEY="foo") self.assertTrue(s.is_overridden("SECRET_KEY")) def test_module(self): settings_module = ModuleType("fake_settings_module") settings_module.SECRET_KEY = "foo" settings_module.USE_TZ = False sys.modules["fake_settings_module"] = settings_module try: s = Settings("fake_settings_module") self.assertTrue(s.is_overridden("SECRET_KEY")) self.assertFalse(s.is_overridden("ALLOWED_HOSTS")) finally: del sys.modules["fake_settings_module"] def test_override(self): self.assertFalse(settings.is_overridden("ALLOWED_HOSTS")) with override_settings(ALLOWED_HOSTS=[]): self.assertTrue(settings.is_overridden("ALLOWED_HOSTS")) def test_unevaluated_lazysettings_repr(self): lazy_settings = LazySettings() expected = "" self.assertEqual(repr(lazy_settings), expected) def test_evaluated_lazysettings_repr(self): lazy_settings = LazySettings() module = os.environ.get(ENVIRONMENT_VARIABLE) expected = '' % module # Force evaluation of the lazy object. lazy_settings.APPEND_SLASH self.assertEqual(repr(lazy_settings), expected) def test_usersettingsholder_repr(self): lazy_settings = LazySettings() lazy_settings.configure(APPEND_SLASH=False) expected = "" self.assertEqual(repr(lazy_settings._wrapped), expected) def test_settings_repr(self): module = os.environ.get(ENVIRONMENT_VARIABLE) lazy_settings = Settings(module) expected = '' % module self.assertEqual(repr(lazy_settings), expected) class TestListSettings(SimpleTestCase): """ Make sure settings that should be lists or tuples throw ImproperlyConfigured if they are set to a string instead of a list or tuple. """ list_or_tuple_settings = ( "ALLOWED_HOSTS", "INSTALLED_APPS", "TEMPLATE_DIRS", "LOCALE_PATHS", "SECRET_KEY_FALLBACKS", ) def test_tuple_settings(self): settings_module = ModuleType("fake_settings_module") settings_module.SECRET_KEY = "foo" msg = "The %s setting must be a list or a tuple." for setting in self.list_or_tuple_settings: setattr(settings_module, setting, ("non_list_or_tuple_value")) sys.modules["fake_settings_module"] = settings_module try: with self.assertRaisesMessage(ImproperlyConfigured, msg % setting): Settings("fake_settings_module") finally: del sys.modules["fake_settings_module"] delattr(settings_module, setting) class SettingChangeEnterException(Exception): pass class SettingChangeExitException(Exception): pass class OverrideSettingsIsolationOnExceptionTests(SimpleTestCase): """ The override_settings context manager restore settings if one of the receivers of "setting_changed" signal fails. Check the three cases of receiver failure detailed in receiver(). In each case, ALL receivers are called when exiting the context manager. """ def setUp(self): signals.setting_changed.connect(self.receiver) self.addCleanup(signals.setting_changed.disconnect, self.receiver) # Create a spy that's connected to the `setting_changed` signal and # executed AFTER `self.receiver`. self.spy_receiver = mock.Mock() signals.setting_changed.connect(self.spy_receiver) self.addCleanup(signals.setting_changed.disconnect, self.spy_receiver) def receiver(self, **kwargs): """ A receiver that fails while certain settings are being changed. - SETTING_BOTH raises an error while receiving the signal on both entering and exiting the context manager. - SETTING_ENTER raises an error only on enter. - SETTING_EXIT raises an error only on exit. """ setting = kwargs["setting"] enter = kwargs["enter"] if setting in ("SETTING_BOTH", "SETTING_ENTER") and enter: raise SettingChangeEnterException if setting in ("SETTING_BOTH", "SETTING_EXIT") and not enter: raise SettingChangeExitException def check_settings(self): """Assert that settings for these tests aren't present.""" self.assertFalse(hasattr(settings, "SETTING_BOTH")) self.assertFalse(hasattr(settings, "SETTING_ENTER")) self.assertFalse(hasattr(settings, "SETTING_EXIT")) self.assertFalse(hasattr(settings, "SETTING_PASS")) def check_spy_receiver_exit_calls(self, call_count): """ Assert that `self.spy_receiver` was called exactly `call_count` times with the ``enter=False`` keyword argument. """ kwargs_with_exit = [ kwargs for args, kwargs in self.spy_receiver.call_args_list if ("enter", False) in kwargs.items() ] self.assertEqual(len(kwargs_with_exit), call_count) def test_override_settings_both(self): """Receiver fails on both enter and exit.""" with self.assertRaises(SettingChangeEnterException): with override_settings(SETTING_PASS="BOTH", SETTING_BOTH="BOTH"): pass self.check_settings() # Two settings were touched, so expect two calls of `spy_receiver`. self.check_spy_receiver_exit_calls(call_count=2) def test_override_settings_enter(self): """Receiver fails on enter only.""" with self.assertRaises(SettingChangeEnterException): with override_settings(SETTING_PASS="ENTER", SETTING_ENTER="ENTER"): pass self.check_settings() # Two settings were touched, so expect two calls of `spy_receiver`. self.check_spy_receiver_exit_calls(call_count=2) def test_override_settings_exit(self): """Receiver fails on exit only.""" with self.assertRaises(SettingChangeExitException): with override_settings(SETTING_PASS="EXIT", SETTING_EXIT="EXIT"): pass self.check_settings() # Two settings were touched, so expect two calls of `spy_receiver`. self.check_spy_receiver_exit_calls(call_count=2) def test_override_settings_reusable_on_enter(self): """ Error is raised correctly when reusing the same override_settings instance. """ @override_settings(SETTING_ENTER="ENTER") def decorated_function(): pass with self.assertRaises(SettingChangeEnterException): decorated_function() signals.setting_changed.disconnect(self.receiver) # This call shouldn't raise any errors. decorated_function() class MediaURLStaticURLPrefixTest(SimpleTestCase): def set_script_name(self, val): clear_script_prefix() if val is not None: set_script_prefix(val) def test_not_prefixed(self): # Don't add SCRIPT_NAME prefix to absolute paths, URLs, or None. tests = ( "/path/", "http://myhost.com/path/", "http://myhost/path/", "https://myhost/path/", None, ) for setting in ("MEDIA_URL", "STATIC_URL"): for path in tests: new_settings = {setting: path} with self.settings(**new_settings): for script_name in ["/somesubpath", "/somesubpath/", "/", "", None]: with self.subTest(script_name=script_name, **new_settings): try: self.set_script_name(script_name) self.assertEqual(getattr(settings, setting), path) finally: clear_script_prefix() def test_add_script_name_prefix(self): tests = ( # Relative paths. ("/somesubpath", "path/", "/somesubpath/path/"), ("/somesubpath/", "path/", "/somesubpath/path/"), ("/", "path/", "/path/"), # Invalid URLs. ( "/somesubpath/", "htp://myhost.com/path/", "/somesubpath/htp://myhost.com/path/", ), # Blank settings. ("/somesubpath/", "", "/somesubpath/"), ) for setting in ("MEDIA_URL", "STATIC_URL"): for script_name, path, expected_path in tests: new_settings = {setting: path} with self.settings(**new_settings): with self.subTest(script_name=script_name, **new_settings): try: self.set_script_name(script_name) self.assertEqual(getattr(settings, setting), expected_path) finally: clear_script_prefix()