Skip to content

Commit bacf8fa

Browse files
committed
Add the set_html_id template tag to resolve the random html.id attribute.
1 parent b4575df commit bacf8fa

File tree

7 files changed

+64
-11
lines changed

7 files changed

+64
-11
lines changed

djangocms_frontend/contrib/accordion/templates/djangocms_frontend/bootstrap5/accordion.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% load cms_tags frontend %}
2-
<{{ instance.tag_type }}{{ instance.get_attributes }} id="parent-{{ instance.uuid }}">
2+
<{{ instance.tag_type }}{{ instance.get_attributes }} {% set_html_id instance as html_id %}id="parent-{{ html_id }}">
33
{% for plugin in instance.child_plugin_instances %}
44
{% with parentloop=forloop parent=instance %}{% render_plugin plugin %}{% endwith %}
55
{% empty %}{% user_message _("Add accordion items here") %}

djangocms_frontend/contrib/accordion/templates/djangocms_frontend/bootstrap5/accordion_item.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
{% load cms_tags frontend %}
22
{% spaceless %}
3-
<div class="accordion-item">
3+
<div class="accordion-item">{% set_html_id instance as html_id %}
44
<{{ parent.accordion_header_type|default:"h2" }} class="accordion-header"
5-
id="heading-{{ instance.uuid }}"><button
5+
id="heading-{{ instance.html_id }}"><button
66
class="accordion-button{% if not instance.accordion_item_open %} collapsed{% endif %} {{ instance.font_size }}"
77
type="button"
88
data-bs-toggle="collapse"
9-
data-bs-target="#item-{{ instance.uuid }}"
9+
data-bs-target="#item-{{ instance.html_id }}"
1010
aria-expanded="{{ instance.accordion_item_open|lower }}"
11-
aria-controls="item-{{ instance.uuid }}">{% inline_field instance "accordion_item_header" %}</button>
11+
aria-controls="item-{{ instance.html_id }}">{% inline_field instance "accordion_item_header" %}</button>
1212
</{{ parent.accordion_header_type|default:"h2" }}>
13-
<{{ instance.tag_type }}{{ instance.get_attributes }} id="item-{{ instance.uuid }}" aria-labelledby="heading-{{ instance.uuid }}" data-bs-parent="#parent-{{ parent.uuid }}">
13+
<{{ instance.tag_type }}{{ instance.get_attributes }} id="item-{{ instance.html_id }}" aria-labelledby="heading-{{ instance.html_id }}" data-bs-parent="#parent-{{ parent.html_id }}">
1414
<div class="accordion-body">{% endspaceless %}
1515
{% with parent=instance %}
1616
{% for plugin in instance.child_plugin_instances %}

djangocms_frontend/contrib/collapse/templates/djangocms_frontend/bootstrap5/collapse-container.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<{{ instance.tag_type }}{{ instance.get_attributes }}
33
id="{{ instance.container_identifier }}"
44
role="tabpanel"
5-
data-bs-parent="#collapse-{{ parent.uuid }}"
5+
data-bs-parent="#collapse-{{ parent.html_id }}"
66
aria-labelledby="trigger-{{ instance.container_identifier }}">
77
{% for plugin in instance.child_plugin_instances %}
88
{% with forloop as parentloop %}{% render_plugin plugin %}{% endwith %}

djangocms_frontend/contrib/collapse/templates/djangocms_frontend/bootstrap5/collapse.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% load cms_tags frontend %}
22
<{{ instance.tag_type }}{{ instance.get_attributes }}
3-
id="collapse-{{ instance.uuid }}"
3+
{% set_html_id instance as html_id %}id="collapse-{{ html_id }}"
44
data-bs-children="{{ instance.collapse_siblings }}"
55
role="tablist"
66
>

djangocms_frontend/models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import uuid
2-
31
from cms.models import CMSPlugin
42
from django.core.serializers.json import DjangoJSONEncoder
53
from django.db import models
@@ -48,7 +46,7 @@ class Meta:
4846

4947
def __init__(self, *args, **kwargs):
5048
self._additional_classes = []
51-
self.uuid = str(uuid.uuid4())
49+
self.html_id = None # HTML id attribute will be set in template tag set_html_id.
5250
super().__init__(*args, **kwargs)
5351

5452
def __getattr__(self, item):

djangocms_frontend/templatetags/frontend.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import typing
3+
import uuid
34

45
from classytags.arguments import Argument, MultiKeywordArgument
56
from classytags.core import Options, Tag
@@ -11,6 +12,7 @@
1112
from django.contrib.contenttypes.models import ContentType
1213
from django.core.serializers.json import DjangoJSONEncoder
1314
from django.db import models
15+
from django.http import HttpRequest
1416
from django.template.defaultfilters import safe
1517
from django.utils.encoding import force_str
1618
from django.utils.functional import Promise
@@ -20,6 +22,7 @@
2022
from djangocms_frontend import settings
2123
from djangocms_frontend.fields import HTMLsanitized
2224
from djangocms_frontend.helpers import get_related_object as related_object
25+
from djangocms_frontend.models import FrontendUIItem
2326

2427
register = template.Library()
2528

@@ -73,6 +76,20 @@ def get_attributes(attribute_field, *add_classes):
7376
return mark_safe(" ".join(attrs))
7477

7578

79+
@register.simple_tag(takes_context=True)
80+
def set_html_id(context: template.Context, instance: FrontendUIItem) -> str:
81+
if instance.html_id is None:
82+
request = context.get("request")
83+
if isinstance(request, HttpRequest):
84+
key = "frontend_plugins_counter"
85+
counter = getattr(request, key, 0) + 1
86+
instance.html_id = f"frontend-plugins-{counter}"
87+
setattr(request, key, counter)
88+
else:
89+
instance.html_id = f"uuid4-{uuid.uuid4()}"
90+
return instance.html_id
91+
92+
7693
@register.filter
7794
def get_related_object(reference):
7895
return related_object(dict(obj=reference), "obj")

tests/collapse/test_plugins.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from unittest.mock import patch
2+
13
from cms.api import add_plugin
24
from cms.test_utils.testcases import CMSTestCase
5+
from django.template import Context
36

47
from djangocms_frontend.contrib.collapse.cms_plugins import (
58
CollapseContainerPlugin,
@@ -11,6 +14,8 @@
1114
CollapseForm,
1215
CollapseTriggerForm,
1316
)
17+
from djangocms_frontend.models import FrontendUIItem
18+
from djangocms_frontend.templatetags.frontend import set_html_id
1419

1520
from ..fixtures import TestFixture
1621

@@ -75,3 +80,36 @@ def test_collapse_container_plugin(self):
7580
self.assertEqual(response.status_code, 200)
7681
self.assertContains(response, 'aria-labelledby="trigger-10"')
7782
self.assertContains(response, "10")
83+
84+
def test_collapse_html_id(self):
85+
def create_plugin():
86+
plugin = add_plugin(
87+
placeholder=self.placeholder,
88+
plugin_type=CollapsePlugin.__name__,
89+
language=self.language,
90+
)
91+
plugin.initialize_from_form(CollapseForm).save()
92+
self.publish(self.page, self.language)
93+
94+
create_plugin()
95+
create_plugin()
96+
with self.login_user_context(self.superuser):
97+
response = self.client.get(self.request_url)
98+
self.assertEqual(response.status_code, 200)
99+
self.assertContains(
100+
response,
101+
"""<div id="collapse-frontend-plugins-1" data-bs-children=".card" role="tablist"></div>""",
102+
html=True)
103+
self.assertContains(
104+
response,
105+
"""<div id="collapse-frontend-plugins-2" data-bs-children=".card" role="tablist"></div>""",
106+
html=True)
107+
108+
def test_set_html_id(self):
109+
instance = FrontendUIItem()
110+
context = Context()
111+
with patch("os.urandom", lambda n: b'\x1bB\x96\xabyI\xf6`\xd0\xc0,\xf8\x83\xe8,\xb8'):
112+
html_id = set_html_id(context, instance)
113+
identifier = "uuid4-1b4296ab-7949-4660-90c0-2cf883e82cb8"
114+
self.assertEqual(html_id, identifier)
115+
self.assertEqual(instance.html_id, identifier)

0 commit comments

Comments
 (0)