Skip to content

Commit

Permalink
feat: Add re-usable components (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun authored Oct 7, 2024
1 parent d540b1e commit a50d679
Show file tree
Hide file tree
Showing 122 changed files with 2,240 additions and 501 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ insert_final_newline = false

[*plugins/image.html]
insert_final_newline = false

[*.html]
indent_size = 2
13 changes: 4 additions & 9 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,12 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ 3.8, 3.9, "3.10", "3.11"] # latest release minus two
python-version: ["3.10", "3.11", "3.12"] # latest release minus two
requirements-file: [
dj32_cms38.txt,
dj32_cms39.txt,
dj32_cms41.txt,
dj40_cms311.txt,
dj41_cms311.txt,
dj42_cms311.txt,
dj40_cms41.txt,
dj41_cms41.txt,
dj42_cms41.txt,
dj50_cms41.txt,
dj51_cms41.txt,
]
os: [
ubuntu-20.04,
Expand All @@ -40,6 +35,6 @@ jobs:
- name: Generate Report
run: |
pip install -r tests/requirements/${{ matrix.requirements-file }}
coverage run setup.py test
coverage run run_tests.py
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.in') }}
restore-keys: |
${{ runner.os }}-pip-
- run: python -m pip install -r docs/requirements.in
- run: python -m pip install -r docs/requirements.txt
- name: Build docs
run: |
cd docs
Expand All @@ -50,7 +50,7 @@ jobs:
key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.in') }}
restore-keys: |
${{ runner.os }}-pip-
- run: python -m pip install -r docs/requirements.in
- run: python -m pip install -r docs/requirements.txt
- name: Check spelling
run: |
cd docs
Expand Down
36 changes: 22 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,31 @@ currently used frontend framework such as Bootstrap, or its specific version.
Key features
============

- Support of `Bootstrap 5 <https://getbootstrap.com>`_, django CMS 3.8+
and the new upcoming major django CMS 4.
- Support of `Bootstrap 5 <https://getbootstrap.com>`_, django CMS 3.8+
and django CMS 4.

- **Separation of plugins from css framework**, i.e. no need to
rebuild you site's plugin tree if css framework is changed in the
future, e.g. from Bootstrap 5 to a future version.
- **Separation of plugins from css framework**, i.e. no need to
rebuild you site's plugin tree if css framework is changed in the
future, e.g. from Bootstrap 5 to a future version.

- **New link plugin** allowing to link to internal pages provided by
other applications, such as `djangocms-blog
<https://github.com/nephila/djangocms-blog>`_.
- **New link plugin** allowing to link to internal pages provided by
other applications, such as `djangocms-blog
<https://github.com/nephila/djangocms-blog>`_.

- **Nice and well-arranged admin frontend** of `djangocms-bootstrap4
<https://github.com/django-cms/djangocms-bootstrap4>`_
- **Nice and well-arranged admin frontend** of `djangocms-bootstrap4
<https://github.com/django-cms/djangocms-bootstrap4>`_

- **Extensible** within the project and with separate project (e.g. a
theme app). Create your own components with a few lines of code only.

- **Plugins are re-usable as UI components** anywhere in your project
(e.g. in a custom app) giving your whole project a more consistent
user experience.

- A management command to **migrate from djangocms-bootstrap4**. This
command automatically migrates all ``djangocms-bootstrap4`` plugins to
``djangocms-frontend``.

- **Extensible** within the project and with separate project (e.g. a
theme app)


Description
===========
Expand All @@ -55,6 +59,10 @@ The link plugin has been rewritten to not only allow internal links to other
CMS pages, but also to other django models such as, e.g., posts of
`djangocms-blog <https://github.com/nephila/djangocms-blog>`_.

The plugins are designed to be re-usable as UI components in your
project, e.g. in a custom app, giving your whole project a more
consistent user experience.

Contributing
============

Expand Down Expand Up @@ -150,7 +158,7 @@ See readthedocs for the `documentation
License
=======

See `LICENSE <https://github.com/django-cms/djangocms-frontend/blob/master/LICENSE>`_.
See `LICENSE <https://github.com/django-cms/djangocms-frontend/blob/master/LICENSE>`_.

.. |pypi| image:: https://badge.fury.io/py/djangocms-frontend.svg
:target: http://badge.fury.io/py/djangocms-frontend
Expand Down
2 changes: 1 addition & 1 deletion djangocms_frontend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
13. Github actions will publish the new package to pypi
"""

__version__ = "1.3.4"
__version__ = "2.0.0a"
11 changes: 11 additions & 0 deletions djangocms_frontend/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django import apps


class DjangocmsFrontendConfig(apps.AppConfig):
name = "djangocms_frontend"
verbose_name = "DjangoCMS Frontend"

def ready(self):
from .component_pool import setup

setup()
36 changes: 35 additions & 1 deletion djangocms_frontend/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
from cms.constants import SLUG_REGEXP
from cms.plugin_base import CMSPluginBase
from django.utils.encoding import force_str

from djangocms_frontend.helpers import get_related

class CMSUIPlugin(CMSPluginBase):
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(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__())

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)
40 changes: 40 additions & 0 deletions djangocms_frontend/common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from importlib import import_module

from djangocms_frontend import settings

from .title import TitleFormMixin, TitleMixin

__common = {
"attributes": ("AttributesMixin",),
"background": ("BackgroundFormMixin", "BackgroundMixin"),
"responsive": ("ResponsiveFormMixin", "ResponsiveMixin"),
"sizing": ("SizingFormMixin", "SizingMixin"),
"spacing": ("SpacingFormMixin", "SpacingMixin", "MarginFormMixin", "MarginMixin", "PaddingFormMixin", "PaddingMixin"),
}

for module, classes in __common.items():
try:
module = import_module(f"{__name__}.{settings.framework}.{module}", module)
for cls in classes:
globals()[cls] = getattr(module, cls)
except ModuleNotFoundError:
for cls in classes:
globals()[cls] = type(cls, (object,), {})

__all__ = [
"TitleMixin",
"TitleFormMixin",
"AttributesMixin",
"BackgroundFormMixin",
"BackgroundMixin",
"ResponsiveFormMixin",
"ResponsiveMixin",
"SizingFormMixin",
"SizingMixin",
"SpacingFormMixin",
"SpacingMixin",
"MarginFormMixin",
"MarginMixin",
"PaddingFormMixin",
"PaddingMixin",
]
15 changes: 0 additions & 15 deletions djangocms_frontend/common/background.py

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def compress(self, data_list):
return ""

def clean(self, value):
value = value or ["", ""]
if value[1] and not value[0]:
raise ValidationError(
_("Please choose a side to which the spacing should be applied."),
Expand Down
15 changes: 0 additions & 15 deletions djangocms_frontend/common/responsive.py

This file was deleted.

15 changes: 0 additions & 15 deletions djangocms_frontend/common/sizing.py

This file was deleted.

97 changes: 97 additions & 0 deletions djangocms_frontend/component_pool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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

django_engine = engines["django"]

plugin_tag_pool = {}


IGNORED_FIELDS = (
"id",
"cmsplugin_ptr",
"language",
"plugin_type",
"position",
"creation_date",
"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 = {
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)
4 changes: 3 additions & 1 deletion djangocms_frontend/contrib/accordion/cms_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ... import settings
from ...cms_plugins import CMSUIPlugin
from ...common.attributes import AttributesMixin
from ...common import AttributesMixin
from ...helpers import add_plugin
from .. import accordion
from . import forms, models
Expand Down Expand Up @@ -98,3 +98,5 @@ class AccordionItemPlugin(mixin_factory("AccordionItem"), CMSUIPlugin):
},
),
]

frontend_editable_fields = ("accordion_item_header",)
Loading

0 comments on commit a50d679

Please sign in to comment.