Fixing template parsing and implications #900
Replies: 6 comments 17 replies
-
On a second thought, and as a counter-argument to Idea #3, I think the So I'm ok with keeping the |
Beta Was this translation helpful? Give feedback.
-
@EmilStenstrom Let me know what you think! |
Beta Was this translation helpful? Give feedback.
-
@JuroOravec Here's some quick thoughts: Idea 1: Yes, this feels much cleaner to do it that way. Do we need to maintain a full copy, or is it possible to monkey-patch just parts of it? I still have a feeling that Django folks might be violently against this syntax, nesting template tags in template tags. But I personally like it a lot and think it's needed. Idea 2: I'm personally not a big fan of HTML syntax as part of a django template. I think it mixes the worlds in a way that is very likely confusing for people not already deeply embedded into the code. Idea 3: I see that point here, that you wouldn't really need the first syntax when we add support for dicts. But as you say in the second comment, it just reads nicely. |
Beta Was this translation helpful? Give feedback.
-
Btw @EmilStenstrom hope you had a pleasant weekend! 3 more ideas to start the new week 😄 Idea 4 - Allow to use comments
|
Beta Was this translation helpful? Give feedback.
-
Since we're discussing templates, there's been one more thing that I had at the back of my mind for quite some time ❌ Idea 7 - Automatically load django-components library for Component template.In other words, automatically prefix Component's template with class Table(Component):
template = """
<table>
...
</table>
""" Effectively becomes class Table(Component):
template = """
{% load component_tags %}
<table>
...
</table>
""" Personally it happens fairly often that I forgot to load the library, especially when writing tests, where the library is not auto-loaded. This idea comes from Tetra, though I can't find the docs that say so. However, the |
Beta Was this translation helpful? Give feedback.
-
Btw @EmilStenstrom one more thing I want to check with you: I played a bit and made a tweak to make the template tag's input ACTUALLY like python functions, so args are no longer allowed after kwargs (what we discussed here). However, a side effect of that the keys defined on the template tag inputs must be valid identifiers. This might be a breaking change for those who might have wanted to pass HTML attributes directly on the template tag. Previously, one might have done: class Table(Component):
def get_context_data(self, **kwargs):
return kwargs And use the component like so: {% component "table"
class="pt-4"
data-id="123"
%} But with the new changes, neither
So now, instead, people would have to do something like this: class Table(Component):
def get_context_data(self, attrs: Dict):
return {
"attrs": attrs,
} And use the component like so: {% component "table"
attrs:class="pt-4"
attrs:data-id="123"
%} Now, the Now, I think this is a good change, to have the template tags behave as functions. IMO it makes it more predictable, fewer exceptions. I was thinking that for backwards compatibility to somehow pass around keys like So instead I'm in favour of removing the support for non-identifier keys, and rather have more robust / simpler interactions with the template tags. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is more of an open-ended discussion, hence this format.
Context - Nested template tags workaround
Right now it's possible to use
{{ }}
or{% %}
inside a component input, e.g. to format a value from within the template:However, to make it work, we have a workaround that tries to estimate whether the the template tag was cut short based on whether the last part of the component tag contains an unterminated string.
E.g. if we had a template:
Then Django would parse that as
What we're able to do is check for the strings between the
{% %}
tags, and we see that the last string"{% format_date date %}
starts with double quote, but is not finished. So we assume that that's where Django has cut us off, and we join up the current block with the next one, which then correctly gets us:The problem with this is that we cannot be 100% sure if our fix is what the user truly intended to write. It's just our guess.
✅ Idea 1 - Fixing the problem at its root
As I was updating the template tag parser to add support for literal lists and dicts, it got me thinking - instead of maintaining a workaround, what if we tried to fix this issue at the core? If we're already parsing the contents of template tags (
{% ... %}
), in such a way that it's a superset of Django's own template tag parsing, why can't we do the same for the parsing of the whole templates?When it comes to parsing whole templates, Django receives a string like this:
And splits out (roughly):
The only change that we need to do is to allow nested
{% ... %}
inside{% %}
tags, but only as long as those nested tags are wrapped in quotes:I already wrote this parser, because, well... it's me.
To make Django use this template parser, we would have to overwrite the
Template.compile_nodelist()
method, replacing the original tokenizer function with ours.If you look at the blame, then there was a small addition 2 years ago, but otherwise the rest of the function is 6-10 years old. So this monkeypatch feels fairly safe to do.
❌ Idea 2 - First class support for HTML-compatible syntax
Once we use our own template parser, this actually opens up doors for an HTML-compatible syntax (think django-cotton). In general*, one could replace
{%
with<d-
and%}
with>
, and the example above could be written as:* There would have to be some edge cases thought out around self-closing tags vs
{% end... %}
, and more.Up until now, my thought on adding support for HTML-compatible Django syntax was by adding a plugin that would do it.
But adding it as an integrated support could save the system from duplicate parsing / compiling. And I think this would even play well with the possibility of merging django-components onto Django. Because we're not using some hacks to make it work, but enable it by a proper parser that could be potentially even extended in the future.
❌ Idea 3 - Deprecating the
attr:key=val
syntax?Ok, this is not related to the other two ideas, nor the context, but wanted to raise it somewhere.
With #872, we theoretically no longer would be in need of the
attr:key=val
syntax, e.g.Because instead we could use
There's nothing wrong with the first syntax. But if we added support for the HTML-compatible syntax, then one approach may be to do what django-cotton does, which is to prefix
:
the key to mark non-static attributes. And then the use of:
may be already confusing.To given an idea, if we have a component like this
Then we need to wrap the `[1, 2,3] in quotes, because otherwise HTML linters / syntax highlight will have problem with it.
HOWEVER also one of the reasons for HTML-compatible syntax is to allow intellisense for HTML attributes. That means that if you're writing an HTML element
Then intellisense or AI assistant may suggest an HTML attribute:
So, for that reason, the
attr="value"
syntax should be reserved for native HTML attributes (e.g.id="123"
, class="red"`, etc..).I believe that's why e.g. django-cotton uses colon
:
prefix, to signify Django variables. In other words, thatdates=[1, 2, 3]
gets converted to:dates="[1, 2, 3]". Because the value MUST be wrapped in quotes. so the leading
:` is used to distinguish between static and Django attributes. E.g.:Gets translated to
Now, assuming all of the above, the
attr:key=val
syntax is still fine, but in the HTML-compatible mode, it could look like this:First
:
to mark the attr as Django, second:
to mark thatattrs
is an object, and thatclass
is a key on that object.And, furthermore, with the addition of AlpineJS, one could end up with 3
:
in the same declaration:First
:
to mark the attr as Django, second:
to mark thatattrs
is an object, and thatclass
is a key on that object. And third as part of an AlpineJS-bound name:href
.Beta Was this translation helpful? Give feedback.
All reactions