123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- """
- Settings and configuration for Django.
- Read values from the module specified by the DJANGO_SETTINGS_MODULE environment
- variable, and then from django.conf.global_settings; see the global_settings.py
- for a list of all possible variables.
- """
- import importlib
- import os
- import time
- import traceback
- import warnings
- from pathlib import Path
- import django
- from django.conf import global_settings
- from django.core.exceptions import ImproperlyConfigured
- from django.utils.deprecation import RemovedInDjango51Warning, RemovedInDjango60Warning
- from django.utils.functional import LazyObject, empty
- ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
- DEFAULT_STORAGE_ALIAS = "default"
- STATICFILES_STORAGE_ALIAS = "staticfiles"
- DEFAULT_FILE_STORAGE_DEPRECATED_MSG = (
- "The DEFAULT_FILE_STORAGE setting is deprecated. Use STORAGES instead."
- )
- STATICFILES_STORAGE_DEPRECATED_MSG = (
- "The STATICFILES_STORAGE setting is deprecated. Use STORAGES instead."
- )
- # RemovedInDjango60Warning.
- FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG = (
- "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated."
- )
- class SettingsReference(str):
- """
- String subclass which references a current settings value. It's treated as
- the value in memory but serializes to a settings.NAME attribute reference.
- """
- def __new__(self, value, setting_name):
- return str.__new__(self, value)
- def __init__(self, value, setting_name):
- self.setting_name = setting_name
- class LazySettings(LazyObject):
- """
- A lazy proxy for either global Django settings or a custom settings object.
- The user can manually configure settings prior to using them. Otherwise,
- Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
- """
- def _setup(self, name=None):
- """
- Load the settings module pointed to by the environment variable. This
- is used the first time settings are needed, if the user hasn't
- configured settings manually.
- """
- settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
- if not settings_module:
- desc = ("setting %s" % name) if name else "settings"
- raise ImproperlyConfigured(
- "Requested %s, but settings are not configured. "
- "You must either define the environment variable %s "
- "or call settings.configure() before accessing settings."
- % (desc, ENVIRONMENT_VARIABLE)
- )
- self._wrapped = Settings(settings_module)
- def __repr__(self):
- # Hardcode the class name as otherwise it yields 'Settings'.
- if self._wrapped is empty:
- return "<LazySettings [Unevaluated]>"
- return '<LazySettings "%(settings_module)s">' % {
- "settings_module": self._wrapped.SETTINGS_MODULE,
- }
- def __getattr__(self, name):
- """Return the value of a setting and cache it in self.__dict__."""
- if (_wrapped := self._wrapped) is empty:
- self._setup(name)
- _wrapped = self._wrapped
- val = getattr(_wrapped, name)
- # Special case some settings which require further modification.
- # This is done here for performance reasons so the modified value is cached.
- if name in {"MEDIA_URL", "STATIC_URL"} and val is not None:
- val = self._add_script_prefix(val)
- elif name == "SECRET_KEY" and not val:
- raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
- self.__dict__[name] = val
- return val
- def __setattr__(self, name, value):
- """
- Set the value of setting. Clear all cached values if _wrapped changes
- (@override_settings does this) or clear single values when set.
- """
- if name == "_wrapped":
- self.__dict__.clear()
- else:
- self.__dict__.pop(name, None)
- super().__setattr__(name, value)
- def __delattr__(self, name):
- """Delete a setting and clear it from cache if needed."""
- super().__delattr__(name)
- self.__dict__.pop(name, None)
- def configure(self, default_settings=global_settings, **options):
- """
- Called to manually configure the settings. The 'default_settings'
- parameter sets where to retrieve any unspecified values from (its
- argument must support attribute access (__getattr__)).
- """
- if self._wrapped is not empty:
- raise RuntimeError("Settings already configured.")
- holder = UserSettingsHolder(default_settings)
- for name, value in options.items():
- if not name.isupper():
- raise TypeError("Setting %r must be uppercase." % name)
- setattr(holder, name, value)
- self._wrapped = holder
- @staticmethod
- def _add_script_prefix(value):
- """
- Add SCRIPT_NAME prefix to relative paths.
- Useful when the app is being served at a subpath and manually prefixing
- subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
- """
- # Don't apply prefix to absolute paths and URLs.
- if value.startswith(("http://", "https://", "/")):
- return value
- from django.urls import get_script_prefix
- return "%s%s" % (get_script_prefix(), value)
- @property
- def configured(self):
- """Return True if the settings have already been configured."""
- return self._wrapped is not empty
- def _show_deprecation_warning(self, message, category):
- stack = traceback.extract_stack()
- # Show a warning if the setting is used outside of Django.
- # Stack index: -1 this line, -2 the property, -3 the
- # LazyObject __getattribute__(), -4 the caller.
- filename, _, _, _ = stack[-4]
- if not filename.startswith(os.path.dirname(django.__file__)):
- warnings.warn(message, category, stacklevel=2)
- # RemovedInDjango51Warning.
- @property
- def DEFAULT_FILE_STORAGE(self):
- self._show_deprecation_warning(
- DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
- )
- return self.__getattr__("DEFAULT_FILE_STORAGE")
- # RemovedInDjango51Warning.
- @property
- def STATICFILES_STORAGE(self):
- self._show_deprecation_warning(
- STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning
- )
- return self.__getattr__("STATICFILES_STORAGE")
- class Settings:
- def __init__(self, settings_module):
- # update this dict from global settings (but only for ALL_CAPS settings)
- for setting in dir(global_settings):
- if setting.isupper():
- setattr(self, setting, getattr(global_settings, setting))
- # store the settings module in case someone later cares
- self.SETTINGS_MODULE = settings_module
- mod = importlib.import_module(self.SETTINGS_MODULE)
- tuple_settings = (
- "ALLOWED_HOSTS",
- "INSTALLED_APPS",
- "TEMPLATE_DIRS",
- "LOCALE_PATHS",
- "SECRET_KEY_FALLBACKS",
- )
- self._explicit_settings = set()
- for setting in dir(mod):
- if setting.isupper():
- setting_value = getattr(mod, setting)
- 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 self.is_overridden("FORMS_URLFIELD_ASSUME_HTTPS"):
- warnings.warn(
- FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG,
- RemovedInDjango60Warning,
- )
- if hasattr(time, "tzset") and self.TIME_ZONE:
- # When we can, attempt to validate the timezone. If we can't find
- # this file, no check happens and it's harmless.
- zoneinfo_root = Path("/usr/share/zoneinfo")
- zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split("/"))
- if zoneinfo_root.exists() and not zone_info_file.exists():
- raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
- # Move the time zone info into os.environ. See ticket #2315 for why
- # we don't do this unconditionally (breaks Windows).
- os.environ["TZ"] = self.TIME_ZONE
- time.tzset()
- if self.is_overridden("DEFAULT_FILE_STORAGE"):
- if self.is_overridden("STORAGES"):
- raise ImproperlyConfigured(
- "DEFAULT_FILE_STORAGE/STORAGES are mutually exclusive."
- )
- self.STORAGES = {
- **self.STORAGES,
- DEFAULT_STORAGE_ALIAS: {"BACKEND": self.DEFAULT_FILE_STORAGE},
- }
- warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
- if self.is_overridden("STATICFILES_STORAGE"):
- if self.is_overridden("STORAGES"):
- raise ImproperlyConfigured(
- "STATICFILES_STORAGE/STORAGES are mutually exclusive."
- )
- self.STORAGES = {
- **self.STORAGES,
- STATICFILES_STORAGE_ALIAS: {"BACKEND": self.STATICFILES_STORAGE},
- }
- warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
- # RemovedInDjango51Warning.
- if self.is_overridden("STORAGES"):
- setattr(
- self,
- "DEFAULT_FILE_STORAGE",
- self.STORAGES.get(DEFAULT_STORAGE_ALIAS, {}).get("BACKEND"),
- )
- setattr(
- self,
- "STATICFILES_STORAGE",
- self.STORAGES.get(STATICFILES_STORAGE_ALIAS, {}).get("BACKEND"),
- )
- def is_overridden(self, setting):
- return setting in self._explicit_settings
- def __repr__(self):
- return '<%(cls)s "%(settings_module)s">' % {
- "cls": self.__class__.__name__,
- "settings_module": self.SETTINGS_MODULE,
- }
- class UserSettingsHolder:
- """Holder for user configured settings."""
- # SETTINGS_MODULE doesn't make much sense in the manually configured
- # (standalone) case.
- SETTINGS_MODULE = None
- def __init__(self, default_settings):
- """
- Requests for configuration variables not in this class are satisfied
- from the module specified in default_settings (if possible).
- """
- self.__dict__["_deleted"] = set()
- self.default_settings = default_settings
- def __getattr__(self, name):
- if not name.isupper() or name in self._deleted:
- raise AttributeError
- return getattr(self.default_settings, name)
- def __setattr__(self, name, value):
- self._deleted.discard(name)
- if name == "DEFAULT_FILE_STORAGE":
- self.STORAGES[DEFAULT_STORAGE_ALIAS] = {
- "BACKEND": self.DEFAULT_FILE_STORAGE
- }
- warnings.warn(DEFAULT_FILE_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
- if name == "STATICFILES_STORAGE":
- self.STORAGES[STATICFILES_STORAGE_ALIAS] = {
- "BACKEND": self.STATICFILES_STORAGE
- }
- warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
- if name == "FORMS_URLFIELD_ASSUME_HTTPS":
- warnings.warn(
- FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG,
- RemovedInDjango60Warning,
- )
- super().__setattr__(name, value)
- # RemovedInDjango51Warning.
- if name == "STORAGES":
- if default_file_storage := self.STORAGES.get(DEFAULT_STORAGE_ALIAS):
- super().__setattr__(
- "DEFAULT_FILE_STORAGE", default_file_storage.get("BACKEND")
- )
- else:
- self.STORAGES.setdefault(
- DEFAULT_STORAGE_ALIAS,
- {"BACKEND": "django.core.files.storage.FileSystemStorage"},
- )
- if staticfiles_storage := self.STORAGES.get(STATICFILES_STORAGE_ALIAS):
- super().__setattr__(
- "STATICFILES_STORAGE", staticfiles_storage.get("BACKEND")
- )
- else:
- self.STORAGES.setdefault(
- STATICFILES_STORAGE_ALIAS,
- {
- "BACKEND": (
- "django.contrib.staticfiles.storage.StaticFilesStorage"
- ),
- },
- )
- def __delattr__(self, name):
- self._deleted.add(name)
- if hasattr(self, name):
- super().__delattr__(name)
- def __dir__(self):
- return sorted(
- s
- for s in [*self.__dict__, *dir(self.default_settings)]
- if s not in self._deleted
- )
- def is_overridden(self, setting):
- deleted = setting in self._deleted
- set_locally = setting in self.__dict__
- set_on_default = getattr(
- self.default_settings, "is_overridden", lambda s: False
- )(setting)
- return deleted or set_locally or set_on_default
- def __repr__(self):
- return "<%(cls)s>" % {
- "cls": self.__class__.__name__,
- }
- settings = LazySettings()
|