Opened 13 years ago

Closed 13 years ago

Last modified 5 years ago

#16146 closed New feature (wontfix)

Calling functions with arguments inside a template

Reported by: pmandel@… Owned by: nobody
Component: Template system Version: 1.3
Severity: Normal Keywords: template, function
Cc: Matthijs Kooijman Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

It would be incredibly useful to be able to call functions with arguments inside a Django template.

Imagine we are looping through a set of articles:

<table>
{% for a in articles %}
<tr><td>{{ a.title }}</td></tr>
{% endfor %}
</table>

And we want to change the color of articles the current user has already read (at some point prior to the current browser session, presumably). Under the current framework, there's no straightforward way to do this. Ideally, we'd create a function inside the poll class that would perform the check for us:

def has_been_read_by(self, user)

So in the template, we'd get:

<table>
{% for a in articles %}
<tr><td {% if a.has_been_read_by %}style="color:magenta;"{% endif %}>{{ a.title }}</td></tr>
{% endfor %}
</table>

However, since we can't pass an argument to the function in the template, this is currently impossible. The best way I've figured out to do this is to perform all these checks view-side and zip up the information along with the object into a list of dictionaries that the template can unpack. Ugly.

I propose that we consider allowing some form of arguments through functions in the template, be it with the '|arg', the ':arg' or the good old '(arg)' notation.

Change History (5)

comment:1 by Julien Phalip, 13 years ago

Resolution: wontfix
Status: newclosed

Django actually prevents this by design. The idea is to keep most of the logic in views and keep the template language as simple as possible (so that it can easily be used by non tech-savvy designers, for example). Quoting from the doc:

"Because Django intentionally limits the amount of logic processing available in the template language, it is not possible to pass arguments to method calls accessed from within templates. Data should be calculated in views, then passed to templates for display." (https://docs.djangoproject.com/en/1.3/topics/templates/#accessing-method-calls)

So you'll have to do that in the view. Writing a custom template tag or filter might also be of good help. This is a common problem, so don't hesitate to ask on the django-users mailing list for more advice.

comment:2 by anonymous, 13 years ago

Resolution: wontfix
Status: closedreopened

Thanks for the update. I'm mostly worried that people will start writing custom template tags to do just this (especially since it's a common feature in other web templating languages), similar to http://djangosnippets.org/snippets/424/. Is the goal to provide an additional layer of security against people who only have access to the templates? I agree that this is a fine goal, but I think that the alternatives (either pre-packing information you know you're going to want or being forced to write a custom template tag for each additional method you want to use) are un-pythonic and un-djangonic enough to warrant some kind of change.

Perhaps just try to include the request as an argument to the function call, and don't if there's a TypeError for a mismatched number of arguments?

comment:3 by Luke Plant, 13 years ago

Resolution: wontfix
Status: reopenedclosed

This change really would not fit with our template language at all, and there are many corner cases which mean that it simply will not behave the way you'd expect equivalent Python code to behave. For instance, there is the fact that foo.bar can mean foo.bar or foo['bar'] or foo.bar() (or even foo().bar etc). The aim and ethos of Django templates is not to reproduce the full power of Python in the template language - in fact the opposite: you should not have to understand Python semantics to be able to write templates, or, very importantly, maintain templates written by other people.

I don't know what you mean by "just try to include the request as an argument to the function call" - the template does not necessarily have any access to the request object.

Regarding other ways to do this, instead of packing data a separate dictionary, you could directly annotate the objects you're passing into the template with any additional data, either as arbitrary attributes, or in a dictionary called e.g. 'extra' which is then attached to the object. So in the template you'd have {% if a.extra.read %}. This provides a simplified interface for the template to use.

If you still disagree, please bring it up on the django-devs list - although I very strongly suspect that all the other core devs will say the same.

comment:4 by imfletcher, 11 years ago

UI/UX: unset

I think this needs to be revisited. This is a strange limitation that causes a lot of extra confusing code. It leads to views adding transient properties to objects and other such workarounds that are hard to follow, hard to maintain and hard to document. passing an argument to a method is not a 'bridge too far' for a template language. Parameters are additional filters and the need to use them comes up quite often.

This line in the sand drives people to jinja2 and hackery, and it does not need to be that way. there is nothing wrong with () having special meaning just like . has special meaning. foo(bar) vs foo.bar can easily be interpreted as separate items, with foo.bar having the current meaning and foo(bar) being a new way to pass variables. I'm sure there are edge cases to be worked out, but i'm also sure its worth it.

comment:5 by Matthijs Kooijman, 5 years ago

Cc: Matthijs Kooijman added
Note: See TracTickets for help on using tickets.
Back to Top