diff --git a/djangocms_frontend/templatetags/cms_component.py b/djangocms_frontend/templatetags/cms_component.py index 47c63cc4..a10dd545 100644 --- a/djangocms_frontend/templatetags/cms_component.py +++ b/djangocms_frontend/templatetags/cms_component.py @@ -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 "" diff --git a/docs/source/tutorial/template_components.rst b/docs/source/tutorial/template_components.rst index 792d73fd..d9472d84 100644 --- a/docs/source/tutorial/template_components.rst +++ b/docs/source/tutorial/template_components.rst @@ -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 @@ -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: diff --git a/tests/test_autocomponent.py b/tests/test_autocomponent.py index b9abf8c2..cc5e8d8c 100644 --- a/tests/test_autocomponent.py +++ b/tests/test_autocomponent.py @@ -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