diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 9e4b0a4b1e..6813afb3b8 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -115,15 +115,15 @@ class Settings: if setting.isupper(): setting_value = getattr(mod, setting) + if setting == 'SECRET_KEY' and not setting_value: + raise ImproperlyConfigured('The SECRET_KEY setting must not be empty.') + if (setting in tuple_settings and not isinstance(setting_value, (list, tuple))): raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting) setattr(self, setting, setting_value) self._explicit_settings.add(setting) - if not self.SECRET_KEY: - raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.") - if self.is_overridden('DEFAULT_CONTENT_TYPE'): warnings.warn('The DEFAULT_CONTENT_TYPE setting is deprecated.', RemovedInDjango30Warning) @@ -139,6 +139,11 @@ class Settings: os.environ['TZ'] = self.TIME_ZONE time.tzset() + def __getattr__(self, name): + if name == 'SECRET_KEY': + raise ImproperlyConfigured('The SECRET_KEY setting must be set.') + raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) + def is_overridden(self, setting): return setting in self._explicit_settings diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index befade160f..26fa492892 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -256,11 +256,6 @@ ABSOLUTE_URL_OVERRIDES = {} # ] IGNORABLE_404_URLS = [] -# A secret key for this particular Django installation. Used in secret-key -# hashing algorithms. Set this in your settings, or Django will complain -# loudly. -SECRET_KEY = '' - # Default file storage mechanism that holds media. DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 3647e60663..635cadc848 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2074,7 +2074,7 @@ object. See :ref:`how-django-processes-a-request` for details. ``SECRET_KEY`` -------------- -Default: ``''`` (Empty string) +Default: Not defined A secret key for a particular Django installation. This is used to provide :doc:`cryptographic signing `, and should be set to a unique, @@ -2087,7 +2087,9 @@ Uses of the key shouldn't assume that it's text or bytes. Every use should go through :func:`~django.utils.encoding.force_text` or :func:`~django.utils.encoding.force_bytes` to convert it to the desired type. -Django will refuse to start if :setting:`SECRET_KEY` is not set. +Django will refuse to start if :setting:`SECRET_KEY` is set to an empty value. +:class:`~django.core.exceptions.ImproperlyConfigured` is raised if +``SECRET_KEY`` is accessed but not set. .. warning:: @@ -2120,6 +2122,10 @@ affect them. startproject ` creates a unique ``SECRET_KEY`` for convenience. +.. versionchanged:: 2.1 + + In older versions, ``SECRET_KEY`` defaults to an empty string. + .. setting:: SECURE_BROWSER_XSS_FILTER ``SECURE_BROWSER_XSS_FILTER`` diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 3c4e01dfac..17347a476b 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -2213,7 +2213,11 @@ class DiffSettings(AdminScriptTestCase): out, err = self.run_manage(args) self.assertNoOutput(err) self.assertOutput(out, "+ FOO = 'bar'") - self.assertOutput(out, "- SECRET_KEY = ''") + self.assertOutput(out, "- INSTALLED_APPS = []") + self.assertOutput( + out, + "+ INSTALLED_APPS = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']" + ) self.assertOutput(out, "+ SECRET_KEY = 'django_tests_secret_key'") self.assertNotInOutput(out, " APPEND_SLASH = True") @@ -2229,7 +2233,12 @@ class DiffSettings(AdminScriptTestCase): self.assertNoOutput(err) self.assertOutput(out, " APPEND_SLASH = True") self.assertOutput(out, "+ FOO = 'bar'") - self.assertOutput(out, "- SECRET_KEY = ''") + self.assertOutput(out, "- INSTALLED_APPS = []") + self.assertOutput( + out, + "+ INSTALLED_APPS = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts']" + ) + self.assertOutput(out, "+ SECRET_KEY = 'django_tests_secret_key'") class Dumpdata(AdminScriptTestCase): diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index 09c062c897..9187f87388 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -292,8 +292,20 @@ class SettingsTests(SimpleTestCase): def test_no_secret_key(self): settings_module = ModuleType('fake_settings_module') sys.modules['fake_settings_module'] = settings_module - msg = 'The SECRET_KEY setting must not be empty.' + msg = 'The SECRET_KEY setting must be set.' try: + settings = Settings('fake_settings_module') + with self.assertRaisesMessage(ImproperlyConfigured, msg): + settings.SECRET_KEY + finally: + del sys.modules['fake_settings_module'] + + def test_secret_key_empty_string(self): + settings_module = ModuleType('fake_settings_module') + settings_module.SECRET_KEY = '' + sys.modules['fake_settings_module'] = settings_module + try: + msg = 'The SECRET_KEY setting must not be empty.' with self.assertRaisesMessage(ImproperlyConfigured, msg): Settings('fake_settings_module') finally: