Skip to content

render_field Tag Fails to Parse Bindings with Nested Quotes #160

@Naggafin

Description

@Naggafin

Description

The render_field template tag in django-widget-tweaks fails to parse complex attribute values, such as Alpine.js ::class bindings (e.g., ::class="{'is-invalid': formFields.username.errors.length}"). This results in a TemplateSyntaxError with the message Could not parse the remainder: '"{'' from '"{''. The issue arises because the ATTRIBUTE_RE regex in widget_tweaks/templatetags/widget_tweaks.py stops parsing at the first quote it encounters, causing the regex match to be incomplete.

This issue affects users integrating Django with frontend frameworks like Alpine.js, where such bindings are common. Without quotes around the class key (e.g., ::class="{is-invalid: ...}"), Django parses the template correctly, but Alpine.js fails because it expects a string key, leading to a runtime error.

Steps to Reproduce

  1. Create a Django template with the following code:
    {% load widget_tweaks %}
    {% render_field form.username ::class="{'is-invalid': isInvalid}" %}
  2. Render the template with Django 5.2.1 (or similar).
  3. Observe the error:
    TemplateSyntaxError at /path/
    Could not parse the remainder: '"{'' from '"{''`
    

Expected Behavior

The render_field tag should correctly parse complex attribute values, including JavaScript object literals with nested quotes, and render the attribute as-is for frontend frameworks like Alpine.js to process.

Actual Behavior

The ATTRIBUTE_RE regex ([@\w:_\.-]+\+?=['"]?[^"']*['"]?) stops parsing at the first quote in the value, causing a TemplateSyntaxError for expressions like {'is-invalid': ...}.

Proposed Solution

Modify the ATTRIBUTE_RE regex in widget_tweaks/templatetags/widget_tweaks.py to handle quoted strings, curly-brace-enclosed expressions, and unquoted values more robustly. Here's a proposed regex:

import re

ATTRIBUTE_RE = re.compile(
    r"""
    (?P<attr>
        [@\w:_\.-]+
    )
    (?P<sign>
        \+?=
    )
    (?P<value>
        (?:
            ['"][^'"]*['"] # Match quoted strings
            |
            \{[^}]*\}      # Match content within curly braces
            |
            [^'"\s=]+      # Match unquoted content
        )*
    )
""",
    re.VERBOSE | re.UNICODE,
)

Explanation of Proposed Changes

  • The value group now supports:
    • Quoted strings (['"][^'"]*['"]): Matches single- or double-quoted strings.
    • Curly-brace content (\{[^}]*\}): Matches JavaScript object literals or similar expressions.
    • Unquoted content ([^'"\s=]+): Matches simple values like true, false, or function names.
  • The (...)* allows multiple such patterns to be combined, supporting complex expressions.
  • This change maintains backward compatibility with existing attribute-value pairs (e.g., class="form-control") while enabling support for Alpine.js bindings.

Environment

  • Django Version: 5.2.1
  • Python Version: 3.12.3
  • django-widget-tweaks Version: 1.5.1
  • Frontend Framework: Alpine.js

Additional Notes

  • The proposed regex assumes balanced curly braces. Edge cases with deeply nested or malformed expressions may require further refinement.

Would the maintainers be open to a pull request implementing this change? I can provide a PR with the updated regex and tests if desired.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions