In particular, I’d love to create inline sequences like this first example:
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
Also, shoud it enable calling arbitrary functions and not just filters with zero or 1 argument?
{% set ns = namespace(found=false) %}
I would really love that too because I often found myself limited by DTL’s filter engine. And limiting this feature to only the {% set %} tag could be a way to experiment on evolving DTL’s syntax in a limited scope.
@adamchainz Can I ask you to clarify exactly what you have in mind here?
Calling arbitrary Python functions in templates would be… erm… something of a departure, shall we say, so I can’t think you’d mean that. (Maybe you do? )
The DTL’s approach here is write a custom tag. (Some kind of helpers to make writing custom tags that update the context easier, I could get behind, if we need those.)
Maybe I didn’t understand @adamchainz correctly but if I did, limiting this to {% set %} is the perfect first setp. Let’s try it and see how the community greets the feature without breaking the rest of DTL.
Well I think that’s a problem. I feel that approach is often getting in my way more often that it helps me. Sometimes, @simple_tag is not enough and writing a tag for a feature that will be used only once or twice in the whole project is kind of a burden, IMHO.
Sure, I understand you want the DTL to be more like Jinja… Just use Jinja is answer there.
Keeping Python out of the template is precisely the reason to use the DTL — that’s its strength. Folks coming Jinja often are adverse to defining custom tags, but it’s something to lean into. They’re easy enough to write (once you get past the first library setup) and it means you end up with an encapsulated, testable units of code — it makes your code better. (That’s the Why not Jinja.)
What I’d like to see here is what @adamchainz’s actual idea here is.
Sometimes yes. But far from always. For example, that to multiply you have to write a filter and instead of {{ foo * 4 }} write {{ foo|multiply_by:4 }} that doesn’t improve the code.
I personally find that the easiest path with DTL is almost always to add methods to the models, since you can call them (assuming no arguments!), which pollutes my models with single use code, which makes the view more coupled to the rest of the code base, and works against code isolation. Writing template tags is a no go in this situation as you can’t have template tags per view.
If I can pass functions into the context and call them in {% set %}, and I can do basic python math, that would be helped a lot I think.
I’m sorry, I really don’t want the discussion to heat up. But i don’t feel like replying to:
I’d like a few features from Jinja that I find would really make the language easier
with
Just use that other language that has a really different syntax and ecosystem and rewrite your existing project with that (or alternatively adopt 2 different templating systems in your project)
sounds like an appropriate answer to user’s requests.
Template languages are ten-a-penny. Along with twitter clients and static site generators, writing your own was once a right of passage.
Despite the choice of syntax — moustaches obviously my favourite — they fall into two camps: those that allow evaluation of expressions from the hosting language and those that don’t. (Both have advantages and disadvantages.) Jinja falls into one, the Django Template Language into the other. If you want such evaluation Just use Jinja is literally, calmly, dispassionately the correct answer. To add such to the DTL is to change the kind of thing it is.
(This keeps coming up, so I guess I might have to blog about it.)
Yes, entirely. That’s what I was asking for in my initial reply:
I find the word “just” here to be doing way too much work. It’s not “just” at all. It’s a big deal in fact. Jinja is incompatible in many ways, so it’s not at all simple to switch, and if you try, you don’t in fact end up switching, but adding a template language, if you for example use the Django admin, or use any third party apps that use templates.
The idea was to add a {% set %} tag that can add to the context without the nesting required by {% with %}. I didn’t mean that we’d add any other syntax from Jinja, like function calling. I believe that is what @apollo13 was suggesting.
I’ve seen a custom {% set %} tag on maybe one or two projects. I also think one of the older “utilities” packages used to provide it, but I can’t remember which or find it.
For example, this {% with %}:
{% with total=engines.count %}
<span data-total={{ total }}>{{ total }}</span> engines.
{% endwith %}
…could be replaced with:
{% set total=engines.count %}
<span data-total={{ total }}>{{ total }}</span> engines.
Filters can still be used in variable declarations, so that allows some flexibility still.
Ok, so leaving function call aside, I really don’t understand the opposition to extending {% with %} (and {% set %} for that matter) syntax to allow declaring common Python iterables (list, dicts, tuples and sets). This is not something that can simply be resolved by creating a new tag or filter. Let’s say I want a dict of tuples in my template:
That doesn’t solve the problem of defining common Python iterables, though. But I understand that this is added comlexity to the DTL and you need a good reason for that. So here is a practical use-case, very common that have met several times when working on Django projects.
Let’s say I have {% my_custom_tag %} that spits out whatever HTML. Now, as I use Stimulus, I want to be able to pass data-* attributes to that custom tag. Then I can’t use @register.simple_tag and pass {% my_custom_tag data-controller="my-controller" %} because this raises TemplateSyntaxError: Could not parse the remainder: '-controller="my-controller"' from 'data-controller="my-controller"'. If I want to be able to pass dashed HTML attributes as tag kwarg, I will have to go as far as writing a custom node that parses "my-controller" as "data-controller"like I did on dj-importmap.
Now, you could answer that I can define this in get_context. And, sure I can. But that breaks both separation of concerns and locality. It’s not data, it’s not logic, it’s HTML. It doesn’t belong in the view, it belongs in the template, like all the other HTML attributes that define the behavior of my controller.
I think that now the Django community has adopted JS frameworks heavily relying on HTML atttributes, like Stimulus or htmx as a standard, it could be a goo idea to support defining standard Python iterables in template variables.
Again: I understand the will to not add uneccessary comlexity to the language, which is why I propose to limit these to only {% with %} and {% set %}.
I’m -1 on this. While I see the appeal and temptation to have this new syntax available, it brings many complications that I’ll illustrate with an analogy:
Suppose the DTL is a recipe book. You have a limited set of recipes—most are great, some you may never try, and others you use often. You might tweak a few ingredients or cooking times, making notes in the same book (hopefully not) or rewriting your variations in a personal notebook. That’s fine. But you wouldn’t expect to find wheat seeds inside the book to grow your own flour. Those seeds can rot, grow uncontrollably, and contaminate your gluten-free kitchen with gluten.
My point is that Python inside DTL is out of place, an outlier. Python, like seeds, is powerful and we love using it to build great things, but there’s a time and place for it, and that isn’t within the recipe book.
If the analogy feels like a stretch (fair enough), my other concern is that allowing Python execution inside the DTL opens a can of worms, particularly regarding security, performance, and compatibility. These issues warrant a deeper examination and could have tangible consequences for maintaining the framework. I would say a change like this requires a DEP at the very least.
Yes, and I answered based on that. Creating iterables still requires Python evaluation in the template, and the whole “can of worms” argument applies (IMHO).
For that kind of case one creates a tag taking whatever parameters it needs and setting the context with the list/dict. (Or a filter that returns the same if that works.)
(For the function call case one could always take inspiration from JavaScript and define an apply tag/filter.)