Skip to content

Commit

Permalink
Refactor components
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun committed Nov 5, 2024
1 parent 3793655 commit d65ed70
Show file tree
Hide file tree
Showing 18 changed files with 213 additions and 220 deletions.
4 changes: 2 additions & 2 deletions djangocms_frontend/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ class DjangocmsFrontendConfig(apps.AppConfig):
verbose_name = "django CMS Frontend"

def ready(self):
from .component_pool import setup
from . import plugin_tag

register(check_settings)
setup()
plugin_tag.setup()


def check_settings(*args, **kwargs):
Expand Down
55 changes: 17 additions & 38 deletions djangocms_frontend/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,23 @@
from cms.constants import SLUG_REGEXP
from cms.plugin_base import CMSPluginBase
from django.utils.encoding import force_str
from cms.plugin_pool import plugin_pool

from djangocms_frontend.helpers import get_related
from .component_pool import components
from .ui_plugin_base import CMSUIPluginBase

if hasattr(CMSPluginBase, "edit_field"):
# FrontendEditable functionality already implemented in core?
FrontendEditableAdminMixin = object
else:
# If not use our own version of the plugin-enabled mixin
from .helpers import FrontendEditableAdminMixin

class CMSUIPlugin(CMSUIPluginBase):
pass

class CMSUIPlugin(FrontendEditableAdminMixin, CMSPluginBase):
render_template = "djangocms_frontend/html_container.html"
change_form_template = "djangocms_frontend/admin/base.html"

def __str__(self):
return force_str(super().__str__())
# Loop through the values in the components' registry
for _, plugin, slot_plugins in components._registry.values():
# Add the plugin to the global namespace
globals()[plugin.__name__] = plugin
# Register the plugin with the plugin pool
plugin_pool.register_plugin(plugin)

def render(self, context, instance, placeholder):
for key, value in instance.config.items():
if isinstance(value, dict) and set(value.keys()) == {"pk", "model"}:
if key not in instance.__dir__(): # hasattr would return the value in the config dict
setattr(instance.__class__, key, get_related(key))
return super().render(context, instance, placeholder)

def get_plugin_urls(self):
from django.urls import re_path

info = f"{self.model._meta.app_label}_{self.model._meta.model_name}"

def pat(regex, fn):
return re_path(regex, fn, name=f"{info}_{fn.__name__}")

return [
pat(r'edit-field/(%s)/([a-z\-]+)/$' % SLUG_REGEXP, self.edit_field),
]

def _get_object_for_single_field(self, object_id, language):
from .models import FrontendUIItem

return FrontendUIItem.objects.get(pk=object_id)
# 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)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.utils.translation import gettext_lazy as _
from entangled.forms import EntangledModelForm

from .ui_plugin_base import CMSUIPluginBase


def _get_mixin_classes(mixins: list, suffix: str = "") -> list[type]:
"""Find and import mixin classes from a list of mixin strings"""
Expand Down Expand Up @@ -112,8 +114,6 @@ def plugin_model_factory(cls) -> type:
@classmethod
def plugin_factory(cls) -> type:
if cls._plugin is None:
from djangocms_frontend.cms_plugins import CMSUIPlugin

mixins = getattr(cls._component_meta, "mixins", [])
slots = cls.get_slot_plugins()
mixins = _get_mixin_classes(mixins)
Expand All @@ -123,16 +123,18 @@ def plugin_factory(cls) -> type:
(
*mixins,
*cls._plugin_mixins,
CMSUIPlugin,
CMSUIPluginBase,
),
{
"name": getattr(cls._component_meta, "name", cls.__name__),
"module": getattr(cls._component_meta, "module", _("Component")),
"module": getattr(cls._component_meta, "module", _("Components")),
"model": cls.plugin_model_factory(),
"form": cls.admin_form_factory(),
"allow_children": getattr(cls._component_meta, "allow_children", False) or slots,
"require_parent": getattr(cls._component_meta, "require_parent", False),
"child_classes": getattr(cls._component_meta, "child_classes", []) + list(slots.keys()),
"render_template": getattr(cls._component_meta, "render_template", CMSUIPlugin.render_template),
"parent_classes": getattr(cls._component_meta, "parent_classes", []),
"render_template": getattr(cls._component_meta, "render_template", CMSUIPluginBase.render_template),
"fieldsets": getattr(cls, "fieldsets", cls._generate_fieldset()),
"change_form_template": "djangocms_frontend/admin/base.html",
"slots": slots,
Expand Down Expand Up @@ -182,9 +184,7 @@ def get_registration(cls) -> tuple[type, type, list[type]]:
@classmethod
@property
def _component_meta(cls) -> typing.Optional[type]:
if hasattr(cls, "Meta"):
return cls.Meta
return None
return getattr(cls, "Meta", None)

@classmethod
def _generate_fieldset(cls) -> list[tuple[typing.Optional[str], dict]]:
Expand All @@ -194,20 +194,9 @@ def get_short_description(self) -> str:
return self.config.get("title", "")

def save_model(self, request, obj, form: forms.Form, change: bool) -> None:
"""Auto-createas slot plugins upon creation of component plugin instance"""
from djangocms_frontend.cms_plugins import CMSUIPlugin
"""Auto-creates slot plugins upon creation of component plugin instance"""

super(CMSUIPlugin, self).save_model(request, obj, form, change)
super(CMSUIPluginBase, self).save_model(request, obj, form, change)
if not change:
for slot in self.slots.keys():
add_plugin(obj.placeholder, slot, obj.language, target=obj)


class ComponentLinkMixin:
from djangocms_frontend.contrib.link.cms_plugins import LinkPluginMixin
from djangocms_frontend.contrib.link.forms import AbstractLinkForm
from djangocms_frontend.contrib.link.helpers import GetLinkMixin

_base_form = AbstractLinkForm
_model_mixins = [GetLinkMixin]
_plugin_mixins = [LinkPluginMixin]
104 changes: 16 additions & 88 deletions djangocms_frontend/component_pool.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,25 @@
import copy
import importlib
import warnings

from cms.plugin_pool import plugin_pool
from cms.templatetags.cms_tags import render_plugin
from django.conf import settings
from django.contrib.admin.sites import site as admin_site
from django.template import engines
from django.template.library import SimpleNode
from django.template.loader import get_template
from django.utils.module_loading import autodiscover_modules

django_engine = engines["django"]

plugin_tag_pool = {}
class Components:
_registry: dict = {}
_discovered: bool = False

def register(self, component):
if component.__name__ in self._registry:
warnings.warn(f"Component {component.__name__} already registered", stacklevel=2)
return component

Check warning on line 13 in djangocms_frontend/component_pool.py

View check run for this annotation

Codecov / codecov/patch

djangocms_frontend/component_pool.py#L12-L13

Added lines #L12 - L13 were not covered by tests
self._registry[component.__name__] = component.get_registration()
return component

IGNORED_FIELDS = (
"id",
"cmsplugin_ptr",
"language",
"plugin_type",
"position",
"creation_date",
"ui_item",
)
def __getitem__(self, item):
return self._registry[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", [])
)

components = Components()

def _get_plugindefaults(instance):
defaults = {
field.name: getattr(instance, field.name)
for field in instance._meta.fields
if field.name not in IGNORED_FIELDS and bool(getattr(instance, field.name))
}
defaults["plugin_type"] = instance.__class__.__name__
return defaults


class _DummyUser:
is_superuser = True
is_staff = True


class _DummyRequest:
user = _DummyUser()


def render_dummy_plugin(context, dummy_plugin):
return dummy_plugin.nodelist.render(context)


def patch_template(template):
"""Patches the template to use the dummy plugin renderer instead of the real one."""
copied_template = copy.deepcopy(template)
patch = False
for node in copied_template.template.nodelist.get_nodes_by_type(SimpleNode):
if node.func == render_plugin:
patch = True
node.func = render_dummy_plugin
return copied_template if patch else template


def setup():
global plugin_tag_pool

for plugin in plugin_pool.get_all_plugins():
if not issubclass(plugin, allowed_plugin_types):
continue
tag_name = plugin.__name__.lower()
if tag_name.endswith("plugin"):
tag_name = tag_name[:-6]
try:
instance = plugin.model() # Create instance with defaults
plugin_admin = plugin(admin_site=admin_site)
if hasattr(instance, "initialize_from_form"):
instance.initialize_from_form(plugin.form)
if tag_name not in plugin_tag_pool:
template = get_template(plugin_admin._get_render_template({"request": None}, instance, None))
plugin_tag_pool[tag_name] = {
"defaults": {
**_get_plugindefaults(instance),
**dict(plugin_type=plugin.__name__),
},
"template": patch_template(template),
"class": plugin,
}
else: # pragma: no cover
warnings.warn(
f"Duplicate candidates for {{% plugin \"{tag_name}\" %}} found. "
f"Only registered {plugin_tag_pool[tag_name]['class'].__name__}.", stacklevel=1)
except Exception as exc: # pragma: no cover
warnings.warn(f"{plugin.__name__}: \n{str(exc)}", stacklevel=1)
if not components._discovered:
autodiscover_modules("cms_components", register_to=components)
components._discovered = True
Empty file.
18 changes: 0 additions & 18 deletions djangocms_frontend/contrib/component/cms_plugins.py

This file was deleted.

25 changes: 0 additions & 25 deletions djangocms_frontend/contrib/component/registry.py

This file was deleted.

2 changes: 1 addition & 1 deletion djangocms_frontend/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,5 @@ def edit_field(self, request, object_id, language):
obj.placeholder.mark_as_dirty(obj.language, clear_cache=False)
# Update the structure board by populating the data bridge
return self.render_close_frame(request, obj)
render(request, 'admin/cms/page/plugin/confirm_form.html', context)
return render(request, 'admin/cms/page/plugin/confirm_form.html', context)

Check warning on line 259 in djangocms_frontend/helpers.py

View check run for this annotation

Codecov / codecov/patch

djangocms_frontend/helpers.py#L259

Added line #L259 was not covered by tests
return render(request, 'admin/cms/page/plugin/change_form.html', context)
Loading

0 comments on commit d65ed70

Please sign in to comment.