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
4 changes: 4 additions & 0 deletions djangocms_frontend/templatetags/cms_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class based on the implate it is part of. The component class is generated
if "_cms_components" in context:
if len(args) != 1: # pragma: no cover
raise ValueError("The cms_component tag requires exactly one positional argument: the component name.")
if not isinstance(args[0], str):
raise ValueError("The component name must be a string.")
if not args[0].isidentifier():
raise ValueError("The component name must be a valid Python identifier.")
context["_cms_components"]["cms_component"].append((args, kwargs))
return ""

Expand Down
11 changes: 8 additions & 3 deletions docs/source/tutorial/template_components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Simplified Component Creation with Templates

.. versionadded:: 2.1

**Template components** are the easiest approach to creating or porting your own custom
frontend components, allowing you to define custom components **using django templates,
without needing to write any Python code**.
**Template components** are the easiest approach to creating or porting your own custom
frontend components, allowing you to define custom components **using Django templates,
without needing to write any Python code**.


Example Hero Template Component
Expand Down Expand Up @@ -104,6 +104,11 @@ by django CMS to identify the plugin later. The ``name`` parameter is used to di
component in the CMS admin interface. Internally the command declares a ``CMSFrontendComponent``
class. All named arguments are added to the component's Meta class.

.. note::
The component name (the first argument to ``{% cms_component %}``) must be a valid Python identifier.
This means it should start with a letter or underscore, followed by letters, digits, or underscores,
and cannot contain spaces or special characters like hyphens.

Only one ``{% cms_component %}`` tag is allowed per template file.

The first part is the declarative part of the template:
Expand Down
33 changes: 33 additions & 0 deletions tests/test_autocomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,39 @@ def test_multiple_cms_component_tags_error(self):
with self.assertRaises(TemplateSyntaxError):
Template(invalid_template)

def test_cms_component_invalid_identifier(self):
# Test that cms_component tag raises ValueError for invalid identifiers
from django.template import Context
from djangocms_frontend.templatetags.cms_component import cms_component

context = Context({"_cms_components": {"cms_component": []}})

# Valid identifier should work
cms_component(context, "valid_name")
self.assertEqual(len(context["_cms_components"]["cms_component"]), 1)

# Non-string identifiers should raise ValueError
with self.assertRaises(ValueError) as cm:
cms_component(context, 123)
self.assertIn("component name must be a string.", str(cm.exception))

# Invalid identifiers should raise ValueError
with self.assertRaises(ValueError) as cm:
cms_component(context, "invalid-name")
self.assertIn("valid Python identifier", str(cm.exception))

with self.assertRaises(ValueError) as cm:
cms_component(context, "123invalid")
self.assertIn("valid Python identifier", str(cm.exception))

with self.assertRaises(ValueError) as cm:
cms_component(context, "invalid name")
self.assertIn("valid Python identifier", str(cm.exception))

with self.assertRaises(ValueError) as cm:
cms_component(context, "")
self.assertIn("valid Python identifier", str(cm.exception))

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

Expand Down
Loading