Skip to content

Commit 647d1d0

Browse files
authored
feat: Add support for choices in template components. (#282)
1 parent 3cd8767 commit 647d1d0

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
lines changed

djangocms_frontend/templatetags/cms_component.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
import re
13
from django import forms, template
24

35
register = template.Library()
@@ -49,10 +51,29 @@ def field(context: template.Context, field_name: str, field_type: forms.Field, *
4951
return ""
5052

5153

54+
def _to_tuple_if_needed(value: str) -> str | tuple[str, str]:
55+
"""
56+
Helper function to convert a string into a tuple if it contains a delimiter.
57+
58+
Args:
59+
value (str): The string to be converted.
60+
61+
Returns:
62+
tuple[str, str]: A tuple containing the two parts of the string if it contains a delimiter,
63+
otherwise returns the original string as a single-element tuple.
64+
"""
65+
match = re.match(r"^(.*?)\s*<([^>]+)>\s?$", value)
66+
if match:
67+
return (match.group(2).strip(), match.group(1).strip())
68+
return value
69+
70+
5271
@register.filter
53-
def split(value: str, delimiter: str = "|") -> list[str]:
72+
def split(value: str, delimiter: str = "|") -> list[str] | list[tuple[str, str]]:
5473
"""
5574
Helper that splits a given string into a list of substrings based on a specified delimiter.
75+
If the substring is of the format "Verbose name <value>" it is turned into a 2-tuple
76+
as used by a form field's choices argument.
5677
5778
Args:
5879
value (str): The string to be split.
@@ -61,4 +82,5 @@ def split(value: str, delimiter: str = "|") -> list[str]:
6182
Returns:
6283
list[str]: A list of substrings obtained by splitting the input string using the delimiter.
6384
"""
64-
return value.split(delimiter)
85+
split_list = value.split(delimiter)
86+
return [_to_tuple_if_needed(item) for item in split_list]

docs/source/tutorial/custom_components.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Building Custom Frontend Components
55
####################################
66

77
.. index::
8-
single: custom frontend components
8+
single: Custom frontend components
99

1010
.. versionadded:: 2.0
1111

docs/source/tutorial/template_components.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,40 @@ Simply replace ``{{ title }}`` and/or ``{{ slogan }}`` with ``{% inline_field "t
250250
template files, you need to restart the server manually to see the changes.
251251

252252

253+
A little helper: the ``split`` filter
254+
-------------------------------------
255+
256+
.. index::
257+
single: split filter
258+
single: choices in template components
259+
260+
If you load the ``cms_component`` template tag library, you can use the ``split`` filter to convert a string into a list.
261+
Some component properties require a list of values, such as the ``parent_classes`` or ``child_classes``.
262+
You can use the ``split`` filter to convert a string into a list. For example, if you want to allow the
263+
**Hero component** to be a child of the **Container or Column component**, you can set the ``parent_classes``
264+
like this::
265+
266+
{% cms_component "Hero" name=_("My Hero Component") parent_classes="ContainerPlugin|ColumnPlugin"|split %}
267+
268+
``split`` splits a string by the pipe character (``|``) and returns a list of strings. If you prefer to use a different
269+
separator, you can pass it as an argument to the filter, like this::
270+
271+
{% cms_component "Hero" name=_("My Hero Component") parent_classes="ContainerPlugin,ColumnPlugin"|split:"," %}
272+
273+
Additionally, ``split`` can be used to create tuples as needed for the ``choices`` parameter of
274+
``forms.ChoiceField``. For example, if you want to create a choice field with two options, you can use the
275+
following code::
276+
277+
{% field "color" forms.ChoiceField choices=_("Red <red>|Green <green>|Default <blue>")|split name=_("Color") %}
278+
279+
The verbose choice label is appended by the actual value of the field between angle brackets (``<...>``).
280+
281+
.. note::
282+
283+
For translators it is important to know, that they **should not translate** the value in angle brackets.
284+
The German translation of the above example string might be ``Rot <red>|Grün <green>|Standard <blue>``.
285+
286+
253287
Limitations of template components
254288
----------------------------------
255289

tests/test_autocomponent.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,23 @@ def test_split_template_tag(self):
6969
self.assertEqual(split("hero.html"), ["hero.html"])
7070
self.assertEqual(split("Mixin1|Mixin2"), ["Mixin1", "Mixin2"])
7171
self.assertEqual(split("hero.html"), ["hero.html"])
72+
# Common alternative:
7273
self.assertEqual(split("hero.html, Mixin1, Mixin2", ", "), ["hero.html", "Mixin1", "Mixin2"])
74+
# Not nice but correct:
75+
self.assertEqual(split("hero.html, Mixin1, Mixin2", ","), ["hero.html", " Mixin1", " Mixin2"])
76+
77+
self.assertEqual(
78+
split("Dark mode <bg-dark>|Light mode <bg-light>"),
79+
[("bg-dark", "Dark mode"), ("bg-light", "Light mode")],
80+
)
81+
self.assertEqual(
82+
split("Dark mode<bg-dark>|Light mode<bg-light>"), # No space before < needed
83+
[("bg-dark", "Dark mode"), ("bg-light", "Light mode")],
84+
)
85+
self.assertEqual(
86+
split("Dark mode <bg-dark|bg-light>"),
87+
["Dark mode <bg-dark", "bg-light>"],
88+
)
7389

7490
def test_invalid_cms_component_usage_missing_required_argument(self):
7591
# The {% cms_component %} tag requires a component name.

0 commit comments

Comments
 (0)