From 0160a9dc0feb0a20233313652d5612cea5d9e570 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 16 May 2025 19:20:29 +0200 Subject: [PATCH 01/12] fix: Allow adding plugins to `CMS_COMPONENT_PLUGINS` by just their name --- djangocms_frontend/plugin_tag.py | 18 +++++++++++++----- .../how-to/use-frontend-as-component.rst | 5 +++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index 37fa511d..f8746f40 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -25,11 +25,6 @@ "ui_item", ) -allowed_plugin_types = tuple( - getattr(importlib.import_module(cls.rsplit(".", 1)[0]), cls.rsplit(".", 1)[-1]) if isinstance(cls, str) else cls - for cls in getattr(settings, "CMS_COMPONENT_PLUGINS", []) -) - def _get_plugindefaults(instance): defaults = { @@ -65,7 +60,20 @@ def patch_template(template): return copied_template if patch else template +def get_plugin_class(settings_string: str | type) -> type: + """Get the plugin class from the settings string or import it if it's a dotted path.""" + if isinstance(settings_string, str): + if "." in settings_string: + # import the class if a dotted oath is given + return importlib.import_module(*settings_string.rsplit(".", 1)) + # Get the plugin class from the plugin pool by its name + return plugin_pool.get_plugin(settings_string) + return settings_string + + def setup(): + allowed_plugin_types = tuple(get_plugin_class(cls) for cls in getattr(settings, "CMS_COMPONENT_PLUGINS", [])) + for plugin in plugin_pool.get_all_plugins(): if not issubclass(plugin, allowed_plugin_types): continue diff --git a/docs/source/how-to/use-frontend-as-component.rst b/docs/source/how-to/use-frontend-as-component.rst index f6bec27d..b92e3a48 100644 --- a/docs/source/how-to/use-frontend-as-component.rst +++ b/docs/source/how-to/use-frontend-as-component.rst @@ -22,8 +22,9 @@ repetition. To make plugins available as components, ensure that the ``CMS_COMPONENT_PLUGINS`` setting in your project's ``settings.py`` - includes the necessary plugin classes and their subclasses. This setting - allows you to specify which plugins can be used directly in templates + is a list that includes the necessary plugin names or dotted path to + a plugin parent class . Only plugins named in the listing or their + child classes can be used directly in templates without creating database entries. * To include all ``djangocms-frontend`` plugins, use From d69b761d63e836d0a0bd2bfbabca5a3f515ae3eb Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 16 May 2025 19:48:38 +0200 Subject: [PATCH 02/12] Fix `__module__` for component plugins --- djangocms_frontend/cms_plugins.py | 8 +++++++- djangocms_frontend/component_base.py | 2 +- djangocms_frontend/plugin_tag.py | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/djangocms_frontend/cms_plugins.py b/djangocms_frontend/cms_plugins.py index 8533a400..04684a52 100644 --- a/djangocms_frontend/cms_plugins.py +++ b/djangocms_frontend/cms_plugins.py @@ -1,4 +1,5 @@ from cms.plugin_pool import plugin_pool +from django.core.exceptions import ImproperlyConfigured from .ui_plugin_base import CMSUIPluginBase @@ -11,7 +12,7 @@ def update_plugin_pool(): from .component_pool import components # Loop through the values in the components' registry - for _, plugin, slot_plugins in components._registry.values(): + for key, (_, plugin, slot_plugins) in components._registry.items(): if plugin.__name__ not in plugin_pool.plugins: # Add the plugin to the global namespace globals()[plugin.__name__] = plugin @@ -24,3 +25,8 @@ def update_plugin_pool(): globals()[slot_plugin.__name__] = slot_plugin # Register the slot plugin with the plugin pool plugin_pool.register_plugin(slot_plugin) + else: + raise ImproperlyConfigured( + f"Cannot register frontend component {key} since a plugin {plugin.__name__} " + f"is already registered by {plugin_pool.plugins[plugin.__name__].__module__}." + ) diff --git a/djangocms_frontend/component_base.py b/djangocms_frontend/component_base.py index a86dc0a6..d581904b 100644 --- a/djangocms_frontend/component_base.py +++ b/djangocms_frontend/component_base.py @@ -174,7 +174,7 @@ def plugin_factory(cls) -> type: if hasattr(cls, "get_render_template") else {} ), - "__module__": cls.__module__, + "__module__": "djangocms_frontend.cms_plugins", }, ) return cls._plugin diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index f8746f40..3d6441f7 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -65,7 +65,8 @@ def get_plugin_class(settings_string: str | type) -> type: if isinstance(settings_string, str): if "." in settings_string: # import the class if a dotted oath is given - return importlib.import_module(*settings_string.rsplit(".", 1)) + module_name, class_name = settings_string.rsplit(".", 1) + return getattr(importlib.import_module(module_name), class_name, None) # Get the plugin class from the plugin pool by its name return plugin_pool.get_plugin(settings_string) return settings_string From 144994754202898dd367abe9f9cc9a7467fc3989 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:54:42 +0200 Subject: [PATCH 03/12] Update djangocms_frontend/plugin_tag.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- djangocms_frontend/plugin_tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index 3d6441f7..8a22d256 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -64,7 +64,7 @@ def get_plugin_class(settings_string: str | type) -> type: """Get the plugin class from the settings string or import it if it's a dotted path.""" if isinstance(settings_string, str): if "." in settings_string: - # import the class if a dotted oath is given + # import the class if a dotted path is given module_name, class_name = settings_string.rsplit(".", 1) return getattr(importlib.import_module(module_name), class_name, None) # Get the plugin class from the plugin pool by its name From cf4c4efedbd91a5923c9c6a16301707b2bf49174 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:55:07 +0200 Subject: [PATCH 04/12] Update djangocms_frontend/plugin_tag.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- djangocms_frontend/plugin_tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index 8a22d256..a70cd7e0 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -66,7 +66,7 @@ def get_plugin_class(settings_string: str | type) -> type: if "." in settings_string: # import the class if a dotted path is given module_name, class_name = settings_string.rsplit(".", 1) - return getattr(importlib.import_module(module_name), class_name, None) + return getattr(importlib.import_module(module_name), class_name) # Get the plugin class from the plugin pool by its name return plugin_pool.get_plugin(settings_string) return settings_string From b3a6876420bbf3cac17c2e2563b93596ba443aa4 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:55:38 +0200 Subject: [PATCH 05/12] Update djangocms_frontend/cms_plugins.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- djangocms_frontend/cms_plugins.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/djangocms_frontend/cms_plugins.py b/djangocms_frontend/cms_plugins.py index 04684a52..da27c29e 100644 --- a/djangocms_frontend/cms_plugins.py +++ b/djangocms_frontend/cms_plugins.py @@ -22,9 +22,17 @@ def update_plugin_pool(): # Loop through the slot plugins associated with the current plugin for slot_plugin in slot_plugins: # Add the slot plugin to the global namespace - globals()[slot_plugin.__name__] = slot_plugin - # Register the slot plugin with the plugin pool - plugin_pool.register_plugin(slot_plugin) + # Register slot plugins, checking for name conflicts + for slot_plugin in slot_plugins: + if slot_plugin.__name__ not in plugin_pool.plugins: + globals()[slot_plugin.__name__] = slot_plugin + plugin_pool.register_plugin(slot_plugin) + else: + raise ImproperlyConfigured( + f"Cannot register slot plugin {slot_plugin.__name__} " + f"since a plugin {slot_plugin.__name__} is already registered " + f"by {plugin_pool.plugins[slot_plugin.__name__].__module__}." + ) else: raise ImproperlyConfigured( f"Cannot register frontend component {key} since a plugin {plugin.__name__} " From bbbf003fc2c76ecd61f118b69f27f2b40ec4a5d5 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:56:32 +0200 Subject: [PATCH 06/12] Update docs/source/how-to/use-frontend-as-component.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- docs/source/how-to/use-frontend-as-component.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/how-to/use-frontend-as-component.rst b/docs/source/how-to/use-frontend-as-component.rst index b92e3a48..c3b0bd18 100644 --- a/docs/source/how-to/use-frontend-as-component.rst +++ b/docs/source/how-to/use-frontend-as-component.rst @@ -23,7 +23,7 @@ repetition. To make plugins available as components, ensure that the ``CMS_COMPONENT_PLUGINS`` setting in your project's ``settings.py`` is a list that includes the necessary plugin names or dotted path to - a plugin parent class . Only plugins named in the listing or their + a plugin parent class. Only plugins named in the listing or their child classes can be used directly in templates without creating database entries. From d1bf9ad2009a9c46dda00e7b5de77d5e6a6f0dcf Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:58:06 +0200 Subject: [PATCH 07/12] Update djangocms_frontend/cms_plugins.py --- djangocms_frontend/cms_plugins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/djangocms_frontend/cms_plugins.py b/djangocms_frontend/cms_plugins.py index da27c29e..4c4b24a8 100644 --- a/djangocms_frontend/cms_plugins.py +++ b/djangocms_frontend/cms_plugins.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from cms.plugin_pool import plugin_pool from django.core.exceptions import ImproperlyConfigured From 05a8341ccb3ae4fba9f26d818ab0a003b518be14 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 18:58:50 +0200 Subject: [PATCH 08/12] Update djangocms_frontend/plugin_tag.py --- djangocms_frontend/plugin_tag.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index a70cd7e0..08ac7842 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -74,7 +74,9 @@ def get_plugin_class(settings_string: str | type) -> type: def setup(): allowed_plugin_types = tuple(get_plugin_class(cls) for cls in getattr(settings, "CMS_COMPONENT_PLUGINS", [])) - + if not allowed_plugin_types: + return + for plugin in plugin_pool.get_all_plugins(): if not issubclass(plugin, allowed_plugin_types): continue From a459a060bc0cfe5e66c23351b9dfdd020c42298e Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 19:13:18 +0200 Subject: [PATCH 09/12] fix indentation --- djangocms_frontend/cms_plugins.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/djangocms_frontend/cms_plugins.py b/djangocms_frontend/cms_plugins.py index 4c4b24a8..41fc993b 100644 --- a/djangocms_frontend/cms_plugins.py +++ b/djangocms_frontend/cms_plugins.py @@ -24,17 +24,17 @@ def update_plugin_pool(): # Loop through the slot plugins associated with the current plugin for slot_plugin in slot_plugins: # Add the slot plugin to the global namespace - # Register slot plugins, checking for name conflicts - for slot_plugin in slot_plugins: - if slot_plugin.__name__ not in plugin_pool.plugins: - globals()[slot_plugin.__name__] = slot_plugin - plugin_pool.register_plugin(slot_plugin) - else: - raise ImproperlyConfigured( - f"Cannot register slot plugin {slot_plugin.__name__} " - f"since a plugin {slot_plugin.__name__} is already registered " - f"by {plugin_pool.plugins[slot_plugin.__name__].__module__}." - ) + # Register slot plugins, checking for name conflicts + for slot_plugin in slot_plugins: + if slot_plugin.__name__ not in plugin_pool.plugins: + globals()[slot_plugin.__name__] = slot_plugin + plugin_pool.register_plugin(slot_plugin) + else: + raise ImproperlyConfigured( + f"Cannot register slot plugin {slot_plugin.__name__} " + f"since a plugin {slot_plugin.__name__} is already registered " + f"by {plugin_pool.plugins[slot_plugin.__name__].__module__}." + ) else: raise ImproperlyConfigured( f"Cannot register frontend component {key} since a plugin {plugin.__name__} " From 9372e7b126765d4a7ac04372214aff3dce7d3830 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Sun, 18 May 2025 20:04:16 +0200 Subject: [PATCH 10/12] Fix: Allow multiple updates of components --- djangocms_frontend/cms_plugins.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/djangocms_frontend/cms_plugins.py b/djangocms_frontend/cms_plugins.py index 41fc993b..037e6aad 100644 --- a/djangocms_frontend/cms_plugins.py +++ b/djangocms_frontend/cms_plugins.py @@ -1,7 +1,6 @@ from __future__ import annotations from cms.plugin_pool import plugin_pool -from django.core.exceptions import ImproperlyConfigured from .ui_plugin_base import CMSUIPluginBase @@ -14,7 +13,7 @@ def update_plugin_pool(): from .component_pool import components # Loop through the values in the components' registry - for key, (_, plugin, slot_plugins) in components._registry.items(): + for _, plugin, slot_plugins in components._registry.values(): if plugin.__name__ not in plugin_pool.plugins: # Add the plugin to the global namespace globals()[plugin.__name__] = plugin @@ -29,14 +28,3 @@ def update_plugin_pool(): if slot_plugin.__name__ not in plugin_pool.plugins: globals()[slot_plugin.__name__] = slot_plugin plugin_pool.register_plugin(slot_plugin) - else: - raise ImproperlyConfigured( - f"Cannot register slot plugin {slot_plugin.__name__} " - f"since a plugin {slot_plugin.__name__} is already registered " - f"by {plugin_pool.plugins[slot_plugin.__name__].__module__}." - ) - else: - raise ImproperlyConfigured( - f"Cannot register frontend component {key} since a plugin {plugin.__name__} " - f"is already registered by {plugin_pool.plugins[plugin.__name__].__module__}." - ) From a7e5e6fcf781ec3b5b5eb2deff16c5ffc6d9868b Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Mon, 19 May 2025 12:36:43 +0200 Subject: [PATCH 11/12] Fix flake8 error and improve coverage --- djangocms_frontend/plugin_tag.py | 4 ++-- tests/test_settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index 08ac7842..77852493 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -64,7 +64,7 @@ def get_plugin_class(settings_string: str | type) -> type: """Get the plugin class from the settings string or import it if it's a dotted path.""" if isinstance(settings_string, str): if "." in settings_string: - # import the class if a dotted path is given + # import the class if a dotted path is given - raise can exception if not found module_name, class_name = settings_string.rsplit(".", 1) return getattr(importlib.import_module(module_name), class_name) # Get the plugin class from the plugin pool by its name @@ -76,7 +76,7 @@ def setup(): allowed_plugin_types = tuple(get_plugin_class(cls) for cls in getattr(settings, "CMS_COMPONENT_PLUGINS", [])) if not allowed_plugin_types: return - + for plugin in plugin_pool.get_all_plugins(): if not issubclass(plugin, allowed_plugin_types): continue diff --git a/tests/test_settings.py b/tests/test_settings.py index 7c76d6e5..a4a41f1d 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -129,5 +129,5 @@ CMS_COMPONENT_PLUGINS = [ "djangocms_frontend.cms_plugins.CMSUIPlugin", - "djangocms_text.cms_plugins.TextPlugin", + "TextPlugin", ] From a65a8607cfd72d022845adc2f718bded8af83c39 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Mon, 19 May 2025 12:53:43 +0200 Subject: [PATCH 12/12] Add tests --- djangocms_frontend/plugin_tag.py | 2 -- tests/test_plugin_tag.py | 10 ++++++++++ tests/test_settings.py | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/djangocms_frontend/plugin_tag.py b/djangocms_frontend/plugin_tag.py index 77852493..e427ec41 100644 --- a/djangocms_frontend/plugin_tag.py +++ b/djangocms_frontend/plugin_tag.py @@ -74,8 +74,6 @@ def get_plugin_class(settings_string: str | type) -> type: def setup(): allowed_plugin_types = tuple(get_plugin_class(cls) for cls in getattr(settings, "CMS_COMPONENT_PLUGINS", [])) - if not allowed_plugin_types: - return for plugin in plugin_pool.get_all_plugins(): if not issubclass(plugin, allowed_plugin_types): diff --git a/tests/test_plugin_tag.py b/tests/test_plugin_tag.py index c562110f..cfdd0afb 100644 --- a/tests/test_plugin_tag.py +++ b/tests/test_plugin_tag.py @@ -119,3 +119,13 @@ def test_non_frontend_plugin(self): result = template.render({"request": None}) self.assertInHTML(expected_result, result) + + def test_autohero_component_registered_for_plugin_tag(self): + from cms.plugin_pool import plugin_pool + from djangocms_frontend.plugin_tag import plugin_tag_pool + + # Check that the AutoHero plugin is registered + self.assertIn("AutoHeroPlugin", plugin_pool.plugins) + + # Check for the AutoHero plugin registration in the plugin_tag_pool + self.assertIn("autohero", plugin_tag_pool) diff --git a/tests/test_settings.py b/tests/test_settings.py index a4a41f1d..0b13d57a 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -129,5 +129,6 @@ CMS_COMPONENT_PLUGINS = [ "djangocms_frontend.cms_plugins.CMSUIPlugin", + "djangocms_frontend.cms_plugins.AutoHeroPlugin", "TextPlugin", ]