Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion djangocms_frontend/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
module = import_module(f"{__name__}.{settings.framework}.{module}", module)
for cls in classes:
globals()[cls] = getattr(module, cls)
except ModuleNotFoundError:
except ModuleNotFoundError: # pragma: no cover
for cls in classes:
globals()[cls] = type(cls, (object,), {})

Expand Down
10 changes: 5 additions & 5 deletions djangocms_frontend/component_pool.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import defaultdict
from collections.abc import Iterator
import importlib
import os
from collections.abc import Iterator
import warnings

from django import forms
Expand All @@ -15,16 +15,16 @@
from djangocms_frontend.component_base import CMSFrontendComponent


def find_cms_component_templates() -> list[tuple[str, str]]:
def find_cms_component_templates(subfolder: str) -> list[tuple[str, str]]:
templates = []
for app in apps.get_app_configs():
app_template_dir = os.path.join(app.path, "templates", app.label, "cms_components")
app_template_dir = os.path.join(app.path, "templates", app.label, subfolder)
if os.path.exists(app_template_dir):
for root, _, files in os.walk(app_template_dir):
for file in files:
if file.endswith(".html") or file.endswith(".htm"):
relative_path = os.path.relpath(os.path.join(root, file), app_template_dir)
templates.append((app.module.__name__, f"{app.label}/cms_components/{relative_path}"))
templates.append((app.module.__name__, f"{app.label}/{subfolder}/{relative_path}"))
return templates


Expand All @@ -43,7 +43,7 @@ class CMSAutoComponentDiscovery:

def __init__(self, register_to):
self.default_field_context.update(settings.COMPONENT_FIELDS)
templates = find_cms_component_templates()
templates = find_cms_component_templates(settings.COMPONENT_FOLDER)
auto_components = self.scan_templates_for_component_declaration(templates)
for component in auto_components:
register_to.register(component)
Expand Down
2 changes: 1 addition & 1 deletion djangocms_frontend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
FORM_OPTIONS = getattr(django_settings, "DJANGOCMS_FRONTEND_FORM_OPTIONS", {})

COMPONENT_FIELDS = getattr(django_settings, "DJANGOCMS_FRONTEND_COMPONENT_FIELDS", {})

COMPONENT_FOLDER = getattr(django_settings, "DJANGOCMS_FRONTEND_COMPONENT_FOLDER", "cms_components")
framework = getattr(django_settings, "DJANGOCMS_FRONTEND_FRAMEWORK", "bootstrap5")
theme = getattr(django_settings, "DJANGOCMS_FRONTEND_THEME", "djangocms_frontend")

Expand Down
4 changes: 2 additions & 2 deletions djangocms_frontend/templatetags/cms_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def _to_tuple_if_needed(value: str) -> str | tuple[str, str]:
value (str): The string to be converted.

Returns:
str | tuple[str, str]: A tuple containing the two parts of the string if it contains
str | tuple[str, str]: A tuple containing the two parts of the string if it contains
a delimiter, otherwise returns the original string.
"""
match = _TUPLE_RE.fullmatch(value)
Expand All @@ -82,7 +82,7 @@ def split(value: str, delimiter: str = "|") -> list[str | tuple[str, str]]:
delimiter (str, optional): The delimiter to use for splitting the string. Defaults to "|".

Returns:
list[str | tuple[str, str]: A list of substrings or 2-tuples obtained by splitting the
list[str | tuple[str, str]: A list of substrings or 2-tuples obtained by splitting the
input string using the delimiter.
"""
split_list = value.split(delimiter)
Expand Down
1 change: 1 addition & 0 deletions djangocms_frontend/templatetags/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def render_tag(self, context, instance, attribute, **kwargs):
instance = context.get("instance", None) # Use instance from context

if is_registering_component(context) and attribute:
# Autodetect inline field and add it to the component
update_component_properties(context, "frontend_editable_fields", attribute, append=True)
elif is_inline_editing_active(context) and isinstance(instance, CMSPlugin) and instance.pk:
# Only allow inline field to be rendered if inline editing is active and the instance is a CMSPlugin
Expand Down
18 changes: 18 additions & 0 deletions docs/source/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ in your project's ``settings.py``.
]


.. py:attribute:: settings.DJANGOCMS_FRONTEND_COMPONENT_FOLDER

Defaults to ``"cms_components"``

The subfolder where the component templates are discovered. This is used by the
:ref:`template components <template_components>` to find the templates
for the components.

The folder needs to be created in your app's ``templates/<app_name>/`` directory.
If you want to use a different folder, set this to the folder name of your choice.

For example, if you want to use ``"my_components"``, add the following line to your project's settings::

DJANGOCMS_FRONTEND_COMPONENT_FOLDER = "my_components"

This causes djangocms-frontend to search for templates on the following paths: ``templates/<app_name>/my_components/``,
where ``<app_name>`` is the name of any installed app.

.. py:attribute:: settings.DJANGOCMS_FRONTEND_COMPONENT_FIELDS

Defaults to ``{}``
Expand Down
12 changes: 9 additions & 3 deletions docs/source/tutorial/template_components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,21 @@ Hero component
==============

``djangocms-frontend`` allows developers to extend its functionality by creating
template components**. In this tutorial, we will create an **Hero component**
**template components**. In this tutorial, we will create an **Hero component**
with the following fields:

- ``title``: A required text field.
- ``slogan``: A required text area field.
- ``hero_image``: A required image field.

This component will be stored in a template directory named ``<app_name>/cms_components``,
as required for ``djangocms-frontend`` template components.
This component will be stored in a template directory named ``<app_name>/cms_components``
(or any subdirectory thereof).

.. note::
You can change the location of your template components inside your template directory
by setting the :attr:`DJANGOCMS_FRONTEND_COMPONENT_FOLDER` setting. The default is
``cms_components``. If you change it, you need to adjust the directory structure accordingly.


Directory Structure
-------------------
Expand Down
12 changes: 12 additions & 0 deletions tests/content/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,15 @@ def test_figure_plugin(self):
response = self.client.get(self.request_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, '<figcaption class="figure-caption')

def test_safe_caption_tag(self):
from djangocms_frontend.templatetags.frontend import safe_caption

# no paragraphs: unchanged
self.assertEqual(safe_caption("hello world"), "hello world")

# single paragraph: stripped
self.assertEqual(safe_caption("<p>hello world</p>"), "hello world")

# multiple paragraphs: kept as is
self.assertEqual(safe_caption("<p>hello</p><p>world</p>"), "<p>hello</p><p>world</p>")
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions tests/test_autocomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,20 @@ def test_multiple_cms_component_tags_error(self):
invalid_template = "{% load cms_tags %}{% cms_component 'Hero' %}{% cms_component 'Footer' %}"
with self.assertRaises(TemplateSyntaxError):
Template(invalid_template)

def test_component_folder_selection(self):
from djangocms_frontend.component_pool import find_cms_component_templates

all_components = find_cms_component_templates("cms_components")
path_components = find_cms_component_templates("cms_components/ui")
private_components = find_cms_component_templates("my_components")
no_components = find_cms_component_templates("no_components")

assert set(all_components) == {
("tests.test_app", "test_app/cms_components/hero.html"),
("tests.test_app", "test_app/cms_components/with_slots.html"),
("tests.test_app", "test_app/cms_components/ui/button.html"),
}
assert path_components == [("tests.test_app", "test_app/cms_components/ui/button.html")]
assert private_components == [("tests.test_app", "test_app/my_components/test_component.htm")]
assert no_components == []