Skip to content

Conversation

@zbohm
Copy link
Contributor

@zbohm zbohm commented Nov 27, 2025

Proposal for a solution for a unique and non-random html id attribute

Generating a random uuid is not suitable for version comparison. It is better to define the attribute so that it is the same every time.

The id attribute is set only when rendering HTML tags, according to the order of the tag in the DOM document. This maintains its consistency with respect to its position in the document.

Summary by Sourcery

Introduce a deterministic HTML id mechanism for frontend UI items and update Bootstrap 5 accordion and collapse templates to use it instead of per-instance UUIDs.

New Features:

  • Add a set_html_id template tag that assigns stable HTML id attributes to FrontendUIItem instances based on their render order.

Enhancements:

  • Replace random UUID-based ids with template-assigned html_id values in accordion and collapse templates to ensure consistent, comparable DOM ids.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 27, 2025

Reviewer's Guide

Introduces a deterministic HTML id assignment mechanism for frontend UI items via a new template tag, replacing per-instance UUID generation and updating accordion and collapse templates to use the new IDs and parent references consistently.

Sequence diagram for HTML id assignment during template rendering

sequenceDiagram
    participant Browser
    participant DjangoTemplateEngine as DjangoTemplateEngine
    participant TemplateTag as set_html_id
    participant Request
    participant Item as FrontendUIItem

    Browser->>DjangoTemplateEngine: HTTP GET page
    DjangoTemplateEngine->>Item: prepare instance
    DjangoTemplateEngine->>TemplateTag: set_html_id(context, Item)
    TemplateTag->>Item: check html_id is None
    alt html_id is None
        TemplateTag->>Request: get attribute frontend_plugins_counter
        Request-->>TemplateTag: current_counter or 0
        TemplateTag->>TemplateTag: counter = current_counter + 1
        TemplateTag->>Item: set html_id = frontend-plugins-counter
        TemplateTag->>Request: set attribute frontend_plugins_counter = counter
    else html_id already set
        TemplateTag-->>DjangoTemplateEngine: return existing html_id
    end
    TemplateTag-->>DjangoTemplateEngine: return html_id
    DjangoTemplateEngine->>DjangoTemplateEngine: render HTML with id attributes from html_id
    DjangoTemplateEngine-->>Browser: HTML response with deterministic ids
Loading

Class diagram for FrontendUIItem and set_html_id template tag

classDiagram
    class FrontendUIItem {
        +list _additional_classes
        +str html_id
        +__init__(*args, **kwargs)
        +__getattr__(item)
    }

    class FrontendTemplateTags {
        +set_html_id(context, instance) str
    }

    FrontendTemplateTags ..> FrontendUIItem : assigns_html_id
Loading

Flow diagram for deterministic html_id generation

flowchart TD
    A["Start rendering template with FrontendUIItem instance"] --> B["Check if instance.html_id is None"]
    B -->|Yes| C["Get request from context"]
    C --> D["Read request.frontend_plugins_counter or use 0"]
    D --> E["Increment counter by 1"]
    E --> F["Set instance.html_id to frontend-plugins-counter"]
    F --> G["Store updated counter in request.frontend_plugins_counter"]
    G --> H["Return instance.html_id for use in HTML id attributes"]
    B -->|No| H
Loading

File-Level Changes

Change Details Files
Add a context-aware template tag to assign deterministic HTML ids per request-render order instead of using random UUIDs on model instances.
  • Import FrontendUIItem model into the frontend template tags module to type-annotate the new tag helper.
  • Implement set_html_id simple_tag that checks instance.html_id and, if unset, derives a sequential id based on a per-request counter stored on the request object, then returns the id.
  • Ensure the id value is stable for the duration of a single render by storing it on the instance instead of recomputing it.
djangocms_frontend/templatetags/frontend.py
Refactor accordion templates to use the new deterministic html_id field instead of the previous uuid-based ids for headers, items, and parent container references.
  • Invoke set_html_id in the accordion item and accordion container templates to ensure each instance gets an html_id during rendering.
  • Replace references to instance.uuid with instance.html_id for element ids and aria attributes in accordion_item.html.
  • Update accordion.html to set the parent element id using the generated html_id rather than a uuid-based id string.
djangocms_frontend/contrib/accordion/templates/djangocms_frontend/bootstrap5/accordion_item.html
djangocms_frontend/contrib/accordion/templates/djangocms_frontend/bootstrap5/accordion.html
Update collapse templates so parent container references rely on the new html_id mechanism rather than uuid-derived ids.
  • Change data-bs-parent in the collapse-container template to target the parent’s html_id-based collapse id instead of a uuid-based one.
  • Call set_html_id in the main collapse template and use the generated html_id to build the collapse container id.
djangocms_frontend/contrib/collapse/templates/djangocms_frontend/bootstrap5/collapse-container.html
djangocms_frontend/contrib/collapse/templates/djangocms_frontend/bootstrap5/collapse.html
Change the FrontendUIItem model to stop generating a random UUID at instantiation and instead defer HTML id assignment to the template tag.
  • Remove the automatic uuid field initialization from the model’s init method.
  • Introduce an html_id attribute initialized to None, which will be populated lazily by the set_html_id template tag when rendering.
djangocms_frontend/models.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov
Copy link

codecov bot commented Nov 27, 2025

Codecov Report

❌ Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 89.01%. Comparing base (b4575df) to head (d0cf701).

Files with missing lines Patch % Lines
djangocms_frontend/templatetags/frontend.py 92.85% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #327      +/-   ##
==========================================
+ Coverage   89.00%   89.01%   +0.01%     
==========================================
  Files         124      124              
  Lines        3383     3396      +13     
  Branches      287      289       +2     
==========================================
+ Hits         3011     3023      +12     
  Misses        256      256              
- Partials      116      117       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Replacing uuid with html_id = None on FrontendUIItem while only updating a subset of templates risks breaking any remaining references to instance.uuid; consider either keeping uuid as a backwards-compatible attribute or auditing all usages to avoid runtime AttributeErrors.
  • The set_html_id template tag assumes context['request'] is always present and mutable; if this tag can be used in contexts without a request (e.g. offline rendering or custom contexts), you may want to guard access or fall back gracefully.
  • The per-request counter key 'frontend_plugins_counter' is a magic string stored directly on the request; consider extracting it as a module-level constant or using a namespaced attribute (e.g. a dict on request) to reduce the risk of attribute name collisions and make the behavior clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Replacing `uuid` with `html_id = None` on `FrontendUIItem` while only updating a subset of templates risks breaking any remaining references to `instance.uuid`; consider either keeping `uuid` as a backwards-compatible attribute or auditing all usages to avoid runtime `AttributeError`s.
- The `set_html_id` template tag assumes `context['request']` is always present and mutable; if this tag can be used in contexts without a `request` (e.g. offline rendering or custom contexts), you may want to guard access or fall back gracefully.
- The per-request counter key `'frontend_plugins_counter'` is a magic string stored directly on the `request`; consider extracting it as a module-level constant or using a namespaced attribute (e.g. a dict on `request`) to reduce the risk of attribute name collisions and make the behavior clearer.

## Individual Comments

### Comment 1
<location> `djangocms_frontend/templatetags/frontend.py:77-85` </location>
<code_context>
     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["request"]
+        key = "frontend_plugins_counter"
+        counter = getattr(request, key, 0) + 1
+        instance.html_id = f"frontend-plugins-{counter}"
+        setattr(request, key, counter)
+    return instance.html_id
+
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Accessing `context["request"]` directly risks a `KeyError` if the request is missing from the template context.

This tag assumes `"request"` is always present, which is typical for CMS plugin rendering but not guaranteed in all template contexts. To make it more robust, use `context.get("request")` and either return a deterministic fallback ID or raise a clear error when no request is available, instead of allowing a `KeyError` at runtime.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zbohm zbohm changed the title Add the set_html_id template tag to resolve the random html.id attribute. feat: Add the set_html_id template tag to resolve the random html.id attribute. Nov 27, 2025
@fsbraun
Copy link
Member

fsbraun commented Nov 27, 2025

@zbohm Interesting approach!

Now, moving one of the plugins to the bottom of the page will make every plugin inbetween show up in the comparison. Maybe a combined approach solves the problem: By default html_id returns the plugin pk, but then starts counting up (id{pk}-1´, id{pk}-2`, ...).

This way changing the order only starts showing differences if an instance is rendered more than once.

What do you think?

@zbohm
Copy link
Contributor Author

zbohm commented Nov 27, 2025

Yes, moving the plugin down will cause the ids to be different. That is the weakness of this solution.

You cannot use instance.pk because when comparing versions, plugins with different pk are compared, so the id will not be the same.

@fsbraun
Copy link
Member

fsbraun commented Nov 27, 2025

That's right... and makes your proposal even more tempting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants