forked from ungleich-public/dynamicweb
203 lines
8 KiB
Python
203 lines
8 KiB
Python
import inspect
|
|
from functools import cache
|
|
from importlib import import_module
|
|
|
|
from django.apps import apps
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.utils.module_loading import module_has_submodule
|
|
|
|
from cms.app_base import CMSAppConfig, CMSAppExtension
|
|
from cms.constants import CMS_CONFIG_NAME
|
|
|
|
|
|
def _find_subclasses(module, klass):
|
|
"""
|
|
Helper function.
|
|
|
|
Returns a list of classes in module which inherit from klass.
|
|
"""
|
|
classes = []
|
|
# Find all classes that inherit from klass
|
|
for name, obj in inspect.getmembers(module):
|
|
is_subclass = (
|
|
# Ignore the import of klass itself
|
|
inspect.isclass(obj) and issubclass(obj, klass) and obj != klass
|
|
)
|
|
if is_subclass:
|
|
classes.append(obj)
|
|
return classes
|
|
|
|
|
|
def _find_config(cms_module):
|
|
"""
|
|
Helper function.
|
|
|
|
Returns the class inheriting from CMSAppConfig in the given module.
|
|
If no such class exists in the module, returns None.
|
|
If multiple classes inherit from CMSAppConfig, raises
|
|
ImproperlyConfigured exception.
|
|
"""
|
|
cms_config_classes = _find_subclasses(cms_module, CMSAppConfig)
|
|
if len(cms_config_classes) == 1:
|
|
return cms_config_classes[0]
|
|
elif len(cms_config_classes) > 1:
|
|
raise ImproperlyConfigured(
|
|
"cms_config.py files can't define more than one "
|
|
"class which inherits from CMSAppConfig")
|
|
|
|
|
|
def _find_extension(cms_module):
|
|
"""
|
|
Helper function.
|
|
|
|
Returns the class inheriting from CMSAppExtension in the given module.
|
|
If no such class exists in the module, returns None.
|
|
If multiple classes inherit from CMSAppExtension, raises
|
|
ImproperlyConfigured exception.
|
|
"""
|
|
cms_extension_classes = _find_subclasses(cms_module, CMSAppExtension)
|
|
if len(cms_extension_classes) == 1:
|
|
return cms_extension_classes[0]
|
|
elif len(cms_extension_classes) > 1:
|
|
raise ImproperlyConfigured(
|
|
"cms_config.py files can't define more than one "
|
|
"class which inherits from CMSAppExtension")
|
|
|
|
|
|
def autodiscover_cms_configs():
|
|
"""
|
|
Find and import all cms_config.py files. Add a cms_app attribute
|
|
to django's app config with an instance of the cms config.
|
|
|
|
This runs in two passes: all CMSAppExtension instances are attached
|
|
first, then the CMSAppConfig objects are instantiated. A config's
|
|
``__init__`` may query registered extensions via
|
|
``CMSAppConfig.get_contract()`` (which caches the result of
|
|
``get_cms_extension_apps()``), so every extension must be registered
|
|
before any config is instantiated. Otherwise the order of apps in
|
|
INSTALLED_APPS could cause a config to cache an incomplete extension
|
|
list (e.g. djangocms_alias not finding djangocms_versioning).
|
|
"""
|
|
discovered = []
|
|
# First pass: import every cms_config module and attach the extensions.
|
|
for app_config in apps.get_app_configs():
|
|
try:
|
|
cms_module = import_module(
|
|
f'{app_config.name}.{CMS_CONFIG_NAME}')
|
|
except: # NOQA
|
|
# If something in cms_config.py raises an exception let that
|
|
# exception bubble up. Only catch the exception if
|
|
# cms_config.py doesn't exist
|
|
if module_has_submodule(app_config.module, CMS_CONFIG_NAME):
|
|
raise
|
|
else:
|
|
config = _find_config(cms_module)
|
|
extension = _find_extension(cms_module)
|
|
if not config and not extension:
|
|
raise ImproperlyConfigured(
|
|
f"The cms_config.py file of the '{app_config.label}' app "
|
|
"must define at least one class which inherits from "
|
|
"CMSAppConfig or CMSAppExtension")
|
|
# We are adding these attributes here rather than in
|
|
# django's app config definition because there are
|
|
# all kinds of limitations as to what can be imported
|
|
# in django's apps.py and leaving it to devs to define this
|
|
# there could cause issues
|
|
if extension:
|
|
app_config.cms_extension = extension()
|
|
discovered.append((app_config, config))
|
|
|
|
# All extensions are now attached; drop any extension lookup that was
|
|
# cached before every extension was registered, so the configs below
|
|
# see the complete list.
|
|
get_cms_extension_apps.cache_clear()
|
|
|
|
# Second pass: instantiate the configs, which may now safely look up
|
|
# any registered extension via get_contract().
|
|
for app_config, config in discovered:
|
|
if config:
|
|
app_config.cms_config = config(app_config)
|
|
|
|
|
|
@cache
|
|
def get_cms_extension_apps():
|
|
"""
|
|
Returns django app configs of apps with a cms extension
|
|
"""
|
|
# NOTE: The cms_extension attr is added by the autodiscover_cms_configs
|
|
# function if a cms_config.py file with a suitable class is found.
|
|
cms_apps = [app_config for app_config in apps.get_app_configs()
|
|
if hasattr(app_config, 'cms_extension')]
|
|
return cms_apps
|
|
|
|
|
|
@cache
|
|
def get_cms_config_apps():
|
|
"""
|
|
Returns django app configs of apps with a cms config
|
|
"""
|
|
# NOTE: The cms_config attr is added by the autodiscover_cms_configs
|
|
# function if a cms_config.py file with a suitable class is found.
|
|
cms_apps = [app_config for app_config in apps.get_app_configs()
|
|
if hasattr(app_config, 'cms_config')]
|
|
return cms_apps
|
|
|
|
|
|
def configure_cms_apps(apps_with_features):
|
|
"""
|
|
Check installed apps for apps that are configured to use cms addons
|
|
and run code to register them with their config
|
|
"""
|
|
for app_with_feature in apps_with_features:
|
|
contract = getattr(app_with_feature.cms_extension, "contract", None)
|
|
if contract is None:
|
|
contract_label = app_with_feature.label # default contract name
|
|
elif isinstance(contract, (tuple, list)) and len(contract) == 2:
|
|
contract_label = contract[0]
|
|
else:
|
|
raise ImproperlyConfigured("CMSExtension contract property needs to be a 2-tuple consisting of the form (contract_name, contract_object)")
|
|
enabled_property = f"{contract_label}_enabled"
|
|
configure_app = app_with_feature.cms_extension.configure_app
|
|
|
|
for app_config in get_cms_config_apps():
|
|
if getattr(app_config.cms_config, enabled_property, False):
|
|
# Feature enabled for this app so configure
|
|
configure_app(app_config.cms_config)
|
|
|
|
|
|
def ready_cms_apps(apps_with_features):
|
|
"""Run ready() methods on every registered cms extension,
|
|
so that final checks can happen after all apps have been configured
|
|
"""
|
|
for app_with_feature in apps_with_features:
|
|
app_with_feature.cms_extension.ready()
|
|
|
|
|
|
# TODO: Remove this function once backwards compatibility is deprecated
|
|
def backwards_compatibility_config():
|
|
"""
|
|
Ensure that old ways of configuring and setting up things (plugin
|
|
pools etc.) still work.
|
|
|
|
NOTE: The autodiscover code has been copied over from
|
|
django.utils.module_loading.autodiscover_modules
|
|
This is because django's autodiscover function imports
|
|
django.apps.apps from within the body of the function, which
|
|
interferes with backwards compatibility testing. The testing is
|
|
non-trivial and requires the patching of django.apps.apps (which
|
|
is impossible when imports are done inside of functions).
|
|
"""
|
|
# The old pools defined various discover methods that looked for
|
|
# specific files and ran the register code in them. Listing the
|
|
# names of the files here.
|
|
modules_to_autodiscover = ['cms_wizards']
|
|
for module in modules_to_autodiscover:
|
|
for app_config in apps.get_app_configs():
|
|
try:
|
|
import_module(f'{app_config.name}.{module}')
|
|
except Exception: # NOQA
|
|
# Decide whether to bubble up this error. If the app just
|
|
# doesn't have the module in question, we can ignore the error
|
|
# attempting to import it, otherwise we want it to bubble up.
|
|
if module_has_submodule(app_config.module, module):
|
|
raise
|