8000 Introduced app registration system (#6421) · django-cms/django-cms@97515c8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 97515c8

Browse files
monikasulikczpython
authored andcommitted
Introduced app registration system (#6421)
1 parent e0c940c commit 97515c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+676
-1
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ Contributors (based on gitlog, 497 unique authors):
342342
* Milo Price
343343
* Mitar
344344
* Mokys
345+
* Monika Sulik
345346
* Morgan Wahl
346347
* Motiejus Jakštys
347348
* motleytech

cms/app_base.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# -*- coding: utf-8 -*-
2+
from abc import ABCMeta, abstractmethod
3+
4+
from django.utils import six
25

36

47
class CMSApp(object):
@@ -93,3 +96,32 @@ def get_urls(self, page=None, language=None, **kwargs):
9396
:return: list of urlconfs strings
9497
"""
9598
return self._urls
99+
100+
101+
class CMSAppConfig(object):
102+
"""Base class that all cms app configurations should inherit from"""
103+
104+
def __init__(self, django_app_config):
105+
self.app_config = django_app_config
106+
107+
108+
class CMSAppExtension(six.with_metaclass(ABCMeta)):
109+
"""Base class that all cms app extensions should inherit from"""
110+
111+
@abstractmethod
112+
def configure_app(self, cms_config):
113+
"""
114+
Implement this method if the app provides functionality that
115+
other apps can use and configure.
116+
117+
This method will be run once for every app that defines an
118+
attribute like "<app_label>_enabled" as True on its cms app
119+
config class.
120+
So for example if app A with label "app_a" implements this
121+
method and app B and app C define app_a_enabled = True on their
122+
cms config classes, the method app A has defined will run twice,
123+
once for app B and once for app C.
124+
125+
:param cms_config: the cms config class of the app
126+
"""
127+
pass

cms/app_registration.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# -*- coding: utf-8 -*-
2+
import inspect
3+
from importlib import import_module
4+
5+
from django.apps import apps
6+
from django.utils.lru_cache import lru_cache
7+
from django.utils.module_loading import module_has_submodule
8+
from django.core.exceptions import ImproperlyConfigured
9+
10+
from cms.app_base import CMSAppConfig, CMSAppExtension
11+
from cms.constants import CMS_CONFIG_NAME
12+
13+
14+
def _find_subclasses(module, klass):
15+
"""
16+
Helper function.
17+
18+
Returns a list of classes in module which inherit from klass.
19+
"""
20+
classes = []
21+
# Find all classes that inherit from klass
22+
for name, obj in inspect.getmembers(module):
23+
is_subclass = (
24+
inspect.isclass(obj) and
25+
issubclass(obj, klass) and
26+
# Ignore the import of klass itself
27+
obj != klass
28+
)
29+
if is_subclass:
30+
classes.append(obj)
31+
return classes
32+
33+
34+
def _find_config(cms_module):
35+
&quo F438 t;""
36+
Helper function.
37+
38+
Returns the class inheriting from CMSAppConfig in the given module.
39+
If no such class exists in the module, returns None.
40+
If multiple classes inherit from CMSAppConfig, raises
41+
ImproperlyConfigured exception.
42+
"""
43+
cms_config_classes = _find_subclasses(cms_module, CMSAppConfig)
44+
if len(cms_config_classes) == 1:
45+
return cms_config_classes[0]
46+
elif len(cms_config_classes) > 1:
47+
raise ImproperlyConfigured(
48+
"cms_config.py files can't define more than one "
49+
"class which inherits from CMSAppConfig")
50+
51+
52+
def _find_extension(cms_module):
53+
"""
54+
Helper function.
55+
56+
Returns the class inheriting from CMSAppExtension in the given module.
57+
If no such class exists in the module, returns None.
58+
If multiple classes inherit from CMSAppExtension, raises
59+
ImproperlyConfigured exception.
60+
"""
61+
cms_extension_classes = _find_subclasses(cms_module, CMSAppExtension)
62+
if len(cms_extension_classes) == 1:
63+
return cms_extension_classes[0]
64+
elif len(cms_extension_classes) > 1:
65+
raise ImproperlyConfigured(
66+
"cms_config.py files can't define more than one "
67+
"class which inherits from CMSAppExtension")
68+
69+
70+
def autodiscover_cms_configs():
71+
"""
72+
Find and import all cms_config.py files. Add a cms_app attribute
73+
to django's app config with an instance of the cms config.
74+
"""
75+
for app_config in apps.get_app_configs():
76+
try:
77+
cms_module = import_module(
78+
'%s.%s' % (app_config.name, CMS_CONFIG_NAME))
79+
except:
80+
# If something in cms_config.py raises an exception let that
81+
# exception bubble up. Only catch the exception if
82+
# cms_config.py doesn't exist
83+
if module_has_submodule(app_config.module, CMS_CONFIG_NAME):
84+
raise
85+
else:
86+
config = _find_config(cms_module)
87+
extension = _find_extension(cms_module)
88+
# We are adding these attributes here rather than in
89+
# django's app config definition because there are
90+
# all kinds of limitations as to what can be imported
91+
# in django's apps.py and leaving it to devs to define this
92+
# there could cause issues
93+
if config:
94+
app_config.cms_config = config(app_config)
95+
if extension:
96+
app_config.cms_extension = extension()
97+
if not config and not extension:
98+
raise ImproperlyConfigured(
99+
"cms_config.py files must define at least one "
100+
"class which inherits from CMSAppConfig or "
101+
"CMSAppExtension")
102+
103+
104+
@lru_cache(maxsize=None)
105+
def get_cms_extension_apps():
106+
"""
107+
Returns django app configs of apps with a cms extension
108+
"""
109+
# NOTE: The cms_extension attr is added by the autodiscover_cms_configs
110+
# function if a cms_config.py file with a suitable class is found.
111+
cms_apps = [app_config for app_config in apps.get_app_configs()
112+
if hasattr(app_config, 'cms_extension')]
113+
return cms_apps
114+
115+
116+
@lru_cache(maxsize=None)
117+
def get_cms_config_apps():
118+
"""
119+
Returns django app configs of apps with a cms config
120+
"""
121+
# NOTE: The cms_config attr is added by the autodiscover_cms_configs
122+
# function if a cms_config.py file with a suitable class is found.
123+
cms_apps = [app_config for app_config in apps.get_app_configs()
124+
if hasattr(app_config, 'cms_config')]
125+
return cms_apps
126+
127+
128+
def configure_cms_apps(apps_with_features):
129+
"""
130+
Check installed apps for apps that are configured to use cms addons
131+
and run code to register them with their config
132+
"""
133+
for app_with_feature in apps_with_features:
134+
enabled_property = "{}_enabled".format(app_with_feature.label)
135+
configure_app = app_with_feature.cms_extension.configure_app
136+
137+
for app_config in get_cms_config_apps():
138+
if getattr(app_config.cms_config, enabled_property, False):
139+
# Feature enabled for this app so configure
140+
configure_app(app_config.cms_config)

cms/apps.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class CMSConfig(AppConfig):
77
verbose_name = _("django CMS")
88

99
def ready(self):
10-
from cms.utils.setup import setup
10+
from cms.utils.setup import setup, setup_cms_apps
1111

1212
setup()
13+
setup_cms_apps()

cms/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@
4646
PUBLISH_COMMENT = "Publish"
4747

4848
SCRIPT_USERNAME = 'script'
49+
50+
CMS_CONFIG_NAME = 'cms_config'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
default_app_config = 'cms.test_utils.project.app_using_non_feature.apps.NonFeatureCMSConfig'

cms/test_utils/project/app_using_non_feature/admin.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class NonFeatureCMSConfig(AppConfig):
5+
name = 'cms.test_utils.project.app_using_non_feature'
6+
label = 'app_using_non_feature'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from cms.app_base import CMSAppConfig
2+
3+
4+
class NonFeatureCMSConfig(CMSAppConfig):
5+
# Attempting to use features from a cms app that doesnt define a
6+
# configure_app method
7+
app_with_feature_not_implemented_enabled = True

cms/test_utils/project/app_using_non_feature/models.py

Whitespace-only changes.

0 commit comments

Comments
 (0)
0