Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load cms_tags frontend %}
<{{ instance.tag_type }}{{ instance.get_attributes }} id="parent-{{ instance.uuid }}">
<{{ instance.tag_type }}{{ instance.get_attributes }} {% set_html_id instance as html_id %}id="parent-{{ html_id }}">
{% for plugin in instance.child_plugin_instances %}
{% with parentloop=forloop parent=instance %}{% render_plugin plugin %}{% endwith %}
{% empty %}{% user_message _("Add accordion items here") %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{% load cms_tags frontend %}
{% spaceless %}
<div class="accordion-item">
<div class="accordion-item">{% set_html_id instance as html_id %}
<{{ parent.accordion_header_type|default:"h2" }} class="accordion-header"
id="heading-{{ instance.uuid }}"><button
id="heading-{{ instance.html_id }}"><button
class="accordion-button{% if not instance.accordion_item_open %} collapsed{% endif %} {{ instance.font_size }}"
type="button"
data-bs-toggle="collapse"
data-bs-target="#item-{{ instance.uuid }}"
data-bs-target="#item-{{ instance.html_id }}"
aria-expanded="{{ instance.accordion_item_open|lower }}"
aria-controls="item-{{ instance.uuid }}">{% inline_field instance "accordion_item_header" %}</button>
aria-controls="item-{{ instance.html_id }}">{% inline_field instance "accordion_item_header" %}</button>
</{{ parent.accordion_header_type|default:"h2" }}>
<{{ instance.tag_type }}{{ instance.get_attributes }} id="item-{{ instance.uuid }}" aria-labelledby="heading-{{ instance.uuid }}" data-bs-parent="#parent-{{ parent.uuid }}">
<{{ instance.tag_type }}{{ instance.get_attributes }} id="item-{{ instance.html_id }}" aria-labelledby="heading-{{ instance.html_id }}" data-bs-parent="#parent-{{ parent.html_id }}">
<div class="accordion-body">{% endspaceless %}
{% with parent=instance %}
{% for plugin in instance.child_plugin_instances %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<{{ instance.tag_type }}{{ instance.get_attributes }}
id="{{ instance.container_identifier }}"
role="tabpanel"
data-bs-parent="#collapse-{{ parent.uuid }}"
data-bs-parent="#collapse-{{ parent.html_id }}"
aria-labelledby="trigger-{{ instance.container_identifier }}">
{% for plugin in instance.child_plugin_instances %}
{% with forloop as parentloop %}{% render_plugin plugin %}{% endwith %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load cms_tags frontend %}
<{{ instance.tag_type }}{{ instance.get_attributes }}
id="collapse-{{ instance.uuid }}"
{% set_html_id instance as html_id %}id="collapse-{{ html_id }}"
data-bs-children="{{ instance.collapse_siblings }}"
role="tablist"
>
Expand Down
4 changes: 1 addition & 3 deletions djangocms_frontend/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import uuid

from cms.models import CMSPlugin
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
Expand Down Expand Up @@ -48,7 +46,7 @@ class Meta:

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

def __getattr__(self, item):
Expand Down
17 changes: 17 additions & 0 deletions djangocms_frontend/templatetags/frontend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import typing
import uuid

from classytags.arguments import Argument, MultiKeywordArgument
from classytags.core import Options, Tag
Expand All @@ -11,6 +12,7 @@
from django.contrib.contenttypes.models import ContentType
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import HttpRequest
from django.template.defaultfilters import safe
from django.utils.encoding import force_str
from django.utils.functional import Promise
Expand All @@ -20,6 +22,7 @@
from djangocms_frontend import settings
from djangocms_frontend.fields import HTMLsanitized
from djangocms_frontend.helpers import get_related_object as related_object
from djangocms_frontend.models import FrontendUIItem

register = template.Library()

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


@register.simple_tag(takes_context=True)
def set_html_id(context: template.Context, instance: FrontendUIItem) -> str:
if instance.html_id is None:
request = context.get("request")
if isinstance(request, HttpRequest):
key = "frontend_plugins_counter"
counter = getattr(request, key, 0) + 1
instance.html_id = f"frontend-plugins-{counter}"
setattr(request, key, counter)
else:
instance.html_id = f"uuid4={uuid.uuid4()}"
return instance.html_id


@register.filter
def get_related_object(reference):
return related_object(dict(obj=reference), "obj")
Expand Down
38 changes: 38 additions & 0 deletions tests/collapse/test_plugins.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from unittest.mock import patch

from cms.api import add_plugin
from cms.test_utils.testcases import CMSTestCase
from django.template import Context

from djangocms_frontend.contrib.collapse.cms_plugins import (
CollapseContainerPlugin,
Expand All @@ -11,6 +14,8 @@
CollapseForm,
CollapseTriggerForm,
)
from djangocms_frontend.models import FrontendUIItem
from djangocms_frontend.templatetags.frontend import set_html_id

from ..fixtures import TestFixture

Expand Down Expand Up @@ -75,3 +80,36 @@ def test_collapse_container_plugin(self):
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'aria-labelledby="trigger-10"')
self.assertContains(response, "10")

def test_collapse_html_id(self):
def create_plugin():
plugin = add_plugin(
placeholder=self.placeholder,
plugin_type=CollapsePlugin.__name__,
language=self.language,
)
plugin.initialize_from_form(CollapseForm).save()
self.publish(self.page, self.language)

create_plugin()
create_plugin()
with self.login_user_context(self.superuser):
response = self.client.get(self.request_url)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
"""<div id="collapse-frontend-plugins-1" data-bs-children=".card" role="tablist"></div>""",
html=True)
self.assertContains(
response,
"""<div id="collapse-frontend-plugins-2" data-bs-children=".card" role="tablist"></div>""",
html=True)

def test_set_html_id(self):
instance = FrontendUIItem()
context = Context()
with patch("os.urandom", lambda n: b'\x1bB\x96\xabyI\xf6`\xd0\xc0,\xf8\x83\xe8,\xb8'):
html_id = set_html_id(context, instance)
identifier = "uuid4=1b4296ab-7949-4660-90c0-2cf883e82cb8"
self.assertEqual(html_id, identifier)
self.assertEqual(instance.html_id, identifier)