TemplateSyntaxError at /
Could not parse the remainder: '@click=''' from '@click='''
This errors regardless of the greetings implementation, since the decorator makes it fail upstream.
Alpine.js, htmx, and similar libraries are fairly popular these days. I believe it only makes sense to simplify the use of their attributes in cases that wouldnât require a custom template.Node otherwise.
That seems like a different thing than what OP is asking for. Heâs saying it would be useful to loosen the restriction of the syntax for keyword arguments in simple tags from <python_style_identifier>= to <printable_word>=.
Iâm not sure I agree, but I can see why if you want to map JS libs that do this type of thing directly, and you have to mix @foo= with foo=.
Yes, I can see where that could be whatâs being looked for, but given how those identifiers are (can be) used as a keyword arg, the limitation here is more one of python than Django - unless you want to completely change the semantics of that tag - in which case this becomes a much wider issue.
Yes, but you canât accept it as a named parameter: def foo(@hello!!): isnât going to work, which means the function must access these by using kwargs, creating the situation where some of the template variables could be received directly, but some must not.
I am +1 to this proposal, perhaps with an opt-in argument like @simple_tag(allow_all_attrs=True).
My heroicons package provides template tags made with simple_tag that allow setting hyphenated attributes, but it requires accepting attributes with _ and translating them to -
Attribute names must consist of one or more characters other than controls, U+0020 SPACE, U+0022 ("), U+0027 ('), U+003E (>), U+002F (/), U+003D (=), and noncharacters.
I donât think we can provide that level of flexibility without breaking parsing, but allowing some common punctuation (@, :, -) should be feasibleâŚ
The question now is how to handle the function arguments.
I think the simpler, and probably most sensible, solution is to not support named arguments for attributes that contain python-disallowed argument characters. Basically only be able to access their values by kwargs["@hello"].
We could otherwise translate them somehow, as Adam describes. It might be too âmagicalâ of a solution though, particularly for parameters with : and @.
Thanks for the discussion so far. @giannisterzopoulos opened a ticket for this, and before accepting, I wanted to follow up and see if we can dig a bit deeper here.
There are two things Iâm hoping to understand better:
The actual use case: I acknowledge that alpine.js and htmx make heavy use of @, :, =, but I canât see how this related to custom template tags. It would be really, really helpful to see a couple of concrete examples where this proposed parsing behavior makes a big difference. Are there specific template tags that would benefit from this? Seeing how this is being currently worked around it (or what weâd ideally like to write) could clarify things.
The tradeoff: Iâve always seen tag arguments as Python-like named variables, so introducing characters like @, - or : into the syntax feels a bit like an anti-pattern to me. It makes me wonder: whatâs the real value in supporting that syntax, and is it worth making that tradeoff in terms of clarity and consistency?
Looking forward to hearing more thoughts! If thereâs a clearer use case and stronger demand, Iâm definitely open to revisiting.
Food for thought - I think this depends on whether, ultimately, Django template tags are seen as:
To help build HTML
To expose Python function within templates
In django-components, weâve added support for the extra characters in the attribute names. Exactly because itâs common to:
Use @ and : prefixes in AlpineJS
Have HTML attributes that contain dashes, like data-testid
In our case we assume that our âcomponentsâ render HTML, so it was logical to make these adjustments.
Agree with @adamchainz regarding breaking parsing - in case of django-components I ditched completely Djangoâs logic for parsing the template tag inputs.
Slightly off-topic, but I made a Rust-based template tag parser which is a superset of Djangoâs and which supports these extra features. Tho its output is an AST, so then there needs to be extra logic to convert the AST into actual args / kwargs. Happy to share what I know / what I got.
I initially noticed this limitation when using packages like django-avatar and lucide, where I tried to add some Alpine.js functionality like: {% avatar user @click="alert('hey')" %} or {% lucide "circle" @click="alert('hey')" %}
and would end up with the TemplateSyntaxError (similarly with the htmx ones). I was sometimes able to work around this by wrapping them in a parent element and use the special attributes there instead. This doesnât always work though, depending on the expected behavior and the attributes at hand.
Some benefits that I see in addressing this in core:
Users (third-party package maintainers as well) wonât have to deal with translating the attributes case-by-case or come up with more hacky solutions.
They wonât have to resort to writing complicated Node subclasses either,
and they can still benefit from the simplicity of the simple_tag.
I imagine that in many cases the passing of those attributes will be transparent for the tag authors: they donât need to handle them explicitly within the tag code itself, but rather just pass them along to the rendered html.
I think this depends on whether, ultimately, Django template tags are seen as:
To help build HTML
To expose Python function within templates
As far as I can tell, simple_tagâs aim in particular is to help building HTML; judging by the steps itâs taking internally to produce valid HTML (usage of conditional_escape etc. - simple_tag docs)
Now I do see the concerns raised, especially the point on clarity and consistency.
It might be up to the implementation details to avoid any confusion though, and I realize that my proposal in the ticket was probably not up to that standard, or at least incomplete.
Any other ideas on how something like this could be implemented would be more than welcome.
(Niggle looking at this: Those Alpine handlers grow to take any valid JavaScript. If we start down a road here, what possible (non-arbitrary) stopping point would there be? Thereâs a reason the DTL constrains in the way it does.)
Update:
This second example is better because youâve got escaping issues with the nested quotes:
I look at that and my mind is scream Donât do this! at me. But if I were going to, itâs a string I want to pass to the tag, so use a string, no? (The why not there is what Iâm not seeing yet)
TemplateSyntaxError at /
'lucide' received too many positional arguments
and avatar results in wrong urls like https://www.gravatar.com/avatar/sama9r6spyhfcxkgrn58e3ybcgopunhe/?s=%40click%3D%22alert%28%27hey%27%29%22
because it maps whatâs passed to the second argument (width - tag signature)
Support for passing those via kwargs would allow for bigger flexibility I think.
Yea I see where youâre coming from, the arbitrary JavaScript mostly affects the values themselves though no? which we still pass along as (escaped) strings. (The keys should be mostly defined sets that contain a certain amount of special chars)
Of course I could submit PRs/tickets to every package where Iâd like the attrs in, so that at least they handle certain keys, but Iâd really like it if we could come up with a way of dealing with this universally.
So the reason is, existing tags are using kwargs to populate generate HTML attributes directly from the key-value pairs?
I need to ponder about this more to know what I think about the suggestion here. I use these libraries (HTMX/Alpine) a lot and this isnât an issue Iâve hit. (But maybeâŚ)
Something I do quite often is to customise an existing tag by grabbing the underlying function and wrapping it to behave the way I want it to. (Maybe handy here, given that 6.0 is not until December, even if this is a good idea.)
Also, something else to consider: While parsing â@â, â-â or â:â does not work for simple tags, or simple block tags, you can make it work with tags like the following:
class ButtonNode(Node):
def __init__(self, nodelist, options):
self.nodelist = nodelist
self.options = options
def render(self, context):
# Resolve all option values
resolved_options = {
key: value.resolve(context) if hasattr(value, "resolve") else value for key, value in self.options.items()
}
# Get content from nodelist
content = self.nodelist.render(context).strip()
# Collect attributes for the button
attrs = []
# Process all other options as HTML attributes
# This handles normal attributes, HTMX attributes, Alpine.js attributes, etc.
for key, value in resolved_options.items():
if key in ["put", "your", "other", "attributes", "here"]:
continue # Skip options that are used for styling
if value is True:
attrs.append(escape(key))
elif value not in (False, None):
attrs.append(f'{escape(key)}="{escape(str(value))}"')
# Join attributes with spaces
attrs_str = " ".join(attrs)
return mark_safe(f'<button type="button" {attrs_str}>{content}</button>')
@register.tag("button")
def do_button(parser, token):
bits = token.split_contents()[1:]
options = {}
for bit in bits:
try:
name, value = bit.split("=", 1)
options[name] = parser.compile_filter(value)
except ValueError:
raise TemplateSyntaxError(f"button tag parameters must be in name=value format, got {bit}")
nodelist = parser.parse(("endbutton",))
parser.delete_first_token()
return ButtonNode(nodelist, options)