import os from functools import update_wrapper from typing import Any from urllib.parse import urljoin from django.conf import settings from django.core.cache import caches from django.core.exceptions import ImproperlyConfigured from django.utils.connection import ConnectionProxy from django.utils.translation import gettext_lazy as _ from cms import __version__, constants __all__ = [ 'get_cms_setting', 'get_site_id' ] class VERIFIED: pass # need a unique identifier for CMS_LANGUAGES def _load_from_file(module_path): """ Load a python module from its absolute filesystem path """ from importlib.machinery import SourceFileLoader imported = None if module_path: imported = SourceFileLoader("mod", module_path).load_module() return imported def default(name): def decorator(wrapped): def wrapper(): if hasattr(settings, name): return getattr(settings, name) return wrapped() update_wrapper(wrapper, wrapped) return wrapper return decorator DEFAULTS = { 'TEMPLATE_INHERITANCE': True, 'DEFAULT_X_FRAME_OPTIONS': constants.X_FRAME_OPTIONS_INHERIT, 'TOOLBAR_SIMPLE_STRUCTURE_MODE': True, 'PLACEHOLDER_CONF': {}, 'PLACEHOLDERS': (('', ('content',), _("Single placeholder")),), 'PERMISSION': False, # Whether to use raw ID lookups for users when PERMISSION is True 'RAW_ID_USERS': False, 'PUBLIC_FOR': 'all', 'APPHOOKS': [], 'TOOLBARS': [], 'SITE_CHOICES_CACHE_KEY': 'CMS:site_choices', 'PAGE_CHOICES_CACHE_KEY': 'CMS:page_choices', 'MEDIA_PATH': 'cms/', 'PAGE_MEDIA_PATH': 'cms_page_media/', 'TITLE_CHARACTER': '+', 'PAGE_CACHE': True, 'PLACEHOLDER_CACHE': True, 'PLUGIN_CACHE': True, 'MENU_CACHE_BACKEND': 'default', 'CACHE_PREFIX': f'cms_{__version__}_', 'PLUGIN_PROCESSORS': [], 'PLUGIN_CONTEXT_PROCESSORS': [], 'UNIHANDECODE_VERSION': None, 'UNIHANDECODE_DECODERS': ['ja', 'zh', 'kr', 'vn', 'diacritic'], 'UNIHANDECODE_DEFAULT_DECODER': 'diacritic', 'TOOLBAR_ANONYMOUS_ON': True, 'TOOLBAR_URL__PERSIST': 'persist', 'TOOLBAR_URL__DISABLE': 'toolbar_off', 'TOOLBAR_URL__ENABLE': 'toolbar_on', 'ADMIN_NAMESPACE': 'admin', 'APP_NAME': None, 'TOOLBAR_HIDE': False, 'INTERNAL_IPS': [], 'REQUEST_IP_RESOLVER': 'cms.utils.request_ip_resolvers.default_request_ip_resolver', 'PAGE_WIZARD_DEFAULT_TEMPLATE': constants.TEMPLATE_INHERITANCE_MAGIC, 'PAGE_WIZARD_CONTENT_PLUGIN': 'TextPlugin', 'PAGE_WIZARD_CONTENT_PLUGIN_BODY': 'body', 'PAGE_WIZARD_CONTENT_PLACEHOLDER': None, # Use first placeholder it finds. 'SIDEFRAME_ENABLED': True, 'ENDPOINT_LIVE_URL_QUERYSTRING_PARAM_ENABLED': False, 'ENDPOINT_LIVE_URL_QUERYSTRING_PARAM': "live-url", 'REDIRECT_PRESERVE_QUERY_PARAMS': False, 'REDIRECT_TO_LOWERCASE_SLUG': False, 'HIDE_LEGACY_FEATURES': True, 'COLOR_SCHEME': 'auto', 'COLOR_SCHEME_TOGGLE': True, 'CATCH_PLUGIN_500_EXCEPTION': True, 'DEFAULT_IN_NAVIGATION': True, 'ALWAYS_REFRESH_CONTENT': False, 'ENABLE_HELP': True, # Adds help menu toolbar 'EXTRA_HELP_MENU_ITEMS': (), 'HELP_MENU_ITEMS': ( (_('Community forum'), 'https://discourse.django-cms.org/'), (_('Documentation'), 'https://docs.django-cms.org/en/latest/'), (_('Getting started'), 'https://www.django-cms.org/en/get-started-django-cms/'), (_('Talk to us'), 'https://www.django-cms.org/en/support/'), ) } def get_cache_durations(): """ Returns the setting: CMS_CACHE_DURATIONS or the defaults. """ return getattr(settings, 'CMS_CACHE_DURATIONS', { 'menus': 60 * 60, 'content': 60, 'permissions': 60 * 60, }) def get_menu_cache(): return ConnectionProxy(caches, get_cms_setting('MENU_CACHE_BACKEND')) @default('CMS_MEDIA_ROOT') def get_media_root(): return os.path.join(settings.MEDIA_ROOT, get_cms_setting('MEDIA_PATH')) @default('CMS_MEDIA_URL') def get_media_url(): return urljoin(settings.MEDIA_URL, get_cms_setting('MEDIA_PATH')) @default('CMS_TOOLBAR_URL__PERSIST') def get_toolbar_url__persist(): return get_cms_setting('TOOLBAR_URL__PERSIST') @default('CMS_TOOLBAR_URL__DISABLE') def get_toolbar_url__disable(): return get_cms_setting('TOOLBAR_URL__DISABLE') @default('CMS_TOOLBAR_URL__ENABLE') def get_toolbar_url__enable(): return get_cms_setting('TOOLBAR_URL__ENABLE') def get_templates() -> list[tuple[str, Any]]: if getattr(settings, 'CMS_TEMPLATES_DIR', False): tpldir = getattr(settings, 'CMS_TEMPLATES_DIR', False) # CMS_TEMPLATES_DIR can either be a string pointing to the templates directory # or a dictionary holding 'site: template dir' entries if isinstance(tpldir, dict): tpldir = tpldir[settings.SITE_ID] # We must extract the relative path of CMS_TEMPLATES_DIR to the nearest # valid templates' directory. Here we mimic what the filesystem and # app_directories template loaders do prefix = '' # Relative to TEMPLATE['DIRS'] for filesystem loader path = [template['DIRS'][0] for template in settings.TEMPLATES] for basedir in path: if tpldir.find(basedir) == 0: prefix = tpldir.replace(basedir + os.sep, '') break # Relative to 'templates' directory that app_directory scans if not prefix: components = tpldir.split(os.sep) try: prefix = os.path.join(*components[components.index('templates') + 1:]) except ValueError: # If templates is not found we use the directory name as prefix # and hope for the best prefix = os.path.basename(tpldir) config_path = os.path.join(tpldir, '__init__.py') # Try to load templates list and names from the template module # If module file is not present skip configuration and just dump the filenames as templates if os.path.isfile(config_path): template_module = _load_from_file(config_path) templates = [ (os.path.join(prefix, data[0].strip()), data[1]) for data in template_module.TEMPLATES.items() ] else: templates = list((os.path.join(prefix, tpl), tpl) for tpl in os.listdir(tpldir)) else: templates = list(getattr(settings, 'CMS_TEMPLATES', [])) if get_cms_setting('TEMPLATE_INHERITANCE') and templates: templates.append((constants.TEMPLATE_INHERITANCE_MAGIC, _('Inherit the template of the nearest ancestor'))) return templates def get_placeholders(): if getattr(settings, 'CMS_PLACEHOLDERS', False): return settings.CMS_PLACEHOLDERS if getattr(settings, 'CMS_TEMPLATES', False) or getattr(settings, 'CMS_TEMPLATES_DIR', False): return () return DEFAULTS['PLACEHOLDERS'] def _ensure_languages_settings(languages): valid_language_keys = ['code', 'name', 'fallbacks', 'hide_untranslated', 'redirect_on_fallback', 'public'] required_language_keys = ['code', 'name'] simple_defaults = ['public', 'redirect_on_fallback', 'hide_untranslated'] if not isinstance(languages, dict): raise ImproperlyConfigured( "CMS_LANGUAGES must be a dictionary with site IDs and 'default'" " as keys. Please check the format.") defaults = languages.pop('default', {}) default_fallbacks = defaults.get('fallbacks') needs_fallbacks = [] for key in defaults: if key not in valid_language_keys: raise ImproperlyConfigured("CMS_LANGUAGES has an invalid property in the default properties: %s" % key) for key in simple_defaults: if key not in defaults: defaults[key] = True for site, language_list in languages.items(): if site != hash(site): raise ImproperlyConfigured( "CMS_LANGUAGES can only be filled with integers (site IDs) and 'default'" " for default values. %s is not a valid key." % site) for language_object in language_list: for required_key in required_language_keys: if required_key not in language_object: raise ImproperlyConfigured("CMS_LANGUAGES has a language which is missing the required key %r " "in site %r" % (key, site)) language_code = language_object['code'] for key in language_object: if key not in valid_language_keys: raise ImproperlyConfigured( f"CMS_LANGUAGES has invalid key {key!r} in language {language_code!r} in site {site!r}" ) if 'fallbacks' not in language_object: if default_fallbacks: language_object['fallbacks'] = default_fallbacks else: needs_fallbacks.append((site, language_object)) for key in simple_defaults: if key not in language_object: language_object[key] = defaults[key] site_fallbacks = {} for site, language_object in needs_fallbacks: if site not in site_fallbacks: site_fallbacks[site] = [lang['code'] for lang in languages[site] if lang['public']] language_object['fallbacks'] = [ lang_code for lang_code in site_fallbacks[site] if lang_code != language_object['code'] ] languages['default'] = defaults languages[VERIFIED] = True # this will be busted by @override_settings and cause a re-check return languages def get_languages(): site_settings = getattr(settings, 'SITE_ID', None) if site_settings and site_settings != hash(site_settings): raise ImproperlyConfigured( "SITE_ID must be an integer" ) if not settings.USE_I18N: if site_settings: return _ensure_languages_settings( {site_settings: [{'code': settings.LANGUAGE_CODE, 'name': settings.LANGUAGE_CODE}]} ) settings.LANGUAGES = ((settings.LANGUAGE_CODE, settings.LANGUAGE_CODE),) return {'default': {}, VERIFIED: True} if settings.LANGUAGE_CODE not in dict(settings.LANGUAGES): raise ImproperlyConfigured( 'LANGUAGE_CODE "%s" must have a matching entry in LANGUAGES' % settings.LANGUAGE_CODE ) if site_settings: languages = getattr(settings, 'CMS_LANGUAGES', { site_settings: [{'code': code, 'name': _(name)} for code, name in settings.LANGUAGES] }) else: languages = {'default': {}, VERIFIED: True} if VERIFIED in languages: return languages return _ensure_languages_settings(languages) def get_unihandecode_host(): host = getattr(settings, 'CMS_UNIHANDECODE_HOST', None) if not host: return host if host.endswith('/'): return host else: return host + '/' COMPLEX = { 'CACHE_DURATIONS': get_cache_durations, 'MEDIA_ROOT': get_media_root, 'MEDIA_URL': get_media_url, # complex because not prefixed by CMS_ 'TEMPLATES': get_templates, 'PLACEHOLDERS': get_placeholders, 'LANGUAGES': get_languages, 'UNIHANDECODE_HOST': get_unihandecode_host, 'CMS_TOOLBAR_URL__PERSIST': get_toolbar_url__persist, 'CMS_TOOLBAR_URL__DISABLE': get_toolbar_url__disable, 'CMS_TOOLBAR_URL__ENABLE': get_toolbar_url__enable, } def get_cms_setting(name) -> Any: if name in COMPLEX: return COMPLEX[name]() return getattr(settings, 'CMS_%s' % name, DEFAULTS[name]) def get_site_id(site): from django.contrib.sites.models import Site if isinstance(site, Site): return site.id try: return int(site) except (TypeError, ValueError): pass return getattr(settings, 'SITE_ID', None)