Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optionally use a filter based on its existence #842

Closed
nixjdm opened this issue Apr 21, 2018 · 6 comments · Fixed by #1248 or #1383
Closed

optionally use a filter based on its existence #842

nixjdm opened this issue Apr 21, 2018 · 6 comments · Fixed by #1248 or #1383
Milestone

Comments

@nixjdm
Copy link

nixjdm commented Apr 21, 2018

I propose a feature, where within a template, you can test for the existence of a filter so that you can then use that filter if it exists, and not if it doesn't.

For example,

{% if filter is callable %}
  {{ var|filter }}
{% else %}
  {{ var }} {# no use of 'filter' #}
{% endif %}

Currently, if the filter does not exist, Jinja would throw a jinja2.exceptions.TemplateAssertionError: no filter named 'filter' because of the line {{ var|filter }}. The desired output is what is rendered in the else. You can test if the filter is callable, but you cannot then optionally use it. At a high level this is similar to how one might commonly use a var, if it exists.

{% if myvar is defined %}
  {{ myvar }}
{% else %}
  No myvar {# no use of 'myvar' #}
{% endif %}

Example Use Case:

I would like to be able to write Lektor themes such that they can take advantage of filters provided by various Lektor plugins if they are installed. I do not want to require those plugins, and thus also their provided filters. I'd want a Lektor theme to flexibly handle either situation, as per the users' desire.

More generally, Jinja templates in any Flask or Django project could be written more flexibly to handle the optional presence filters. Adding custom or 3rd party filters is a common thing to do. Passing templates around between projects would be easier if they didn't force implementation of custom filters.

Implementation:

The desired effect could be achieved more than one way. For example, at least

  1. not raising an exception when evaluating {{ var|filter }} and instead returning (basically anything) would allow the above snippet to be rendered.

  2. change the way the entire if is evaluated, such that the expressions contained in the contents of the "true" section are evaluated only if the test passes. Evaluation would resume upon reaching an else or endif.

My intuition was that the second option was already happening, but it is not. Though the contents of the passing section of the if never get to the final rendered product, the expressions are still evaluated anyway. Otherwise no exception would be rendered. If 2. happened, that could mean much nonsense could be included in between the if and the else, and not trip exceptions. That sounds like a change that's worth some careful thought, but I can't immediately think of a problem with it. I am not intimately familiar with Jinja's internals. Exceptions would still be raised if the test ever actually passes.
Skipping that section may also mean performance improvements, which is a nice side effect.

@ThiefMaster
Copy link
Member

ThiefMaster commented Apr 21, 2018

To me it sounds like undefined filters/tests should behave likes undefined function calls: i.e. fail when they are actually executed, but not when they are skipped by an if block around them.
Might actually be a bug that this is not the case already...

@nixjdm
Copy link
Author

nixjdm commented Apr 21, 2018

Regardless of whether it's considered a feature or bug I guess, in 2.10 and master I also get TemplateSyntaxErrors easily. If I put many things that shouldn't work inside {% if false%}{% endif %}, it breaks, which is conceptually similar to just using a non-existent filter. Jinja is trying to interpret everything, in order to find the next else or endif I think, instead of literally matching against those special strings and skipping whatever is in between.

Examples of things that fail right now:

  • {% if false %}{% if foo bar %}{% endif %}; TemplateSyntaxError: expected token 'end of statement block', got 'bar'
  • {% if false %}{% foo %}{% endif %}; TemplateSyntaxError: Encountered unknown tag 'foo'.
  • {% if false %}{{ 'foo'|bar }}{% endif %}; TemplateAssertionError: no filter named 'bar'

An argument against changing this behavior is that the errors may be useful for debugging during normal template development, i.e., not when taking advantage of ignoring things inside an if block.

Personally, unless there's a good reason I haven't thought of yet, I'm in favor of changing the behavior. I'd say skip the execution of the if blocks when the test fails.

@ThiefMaster
Copy link
Member

ThiefMaster commented Apr 21, 2018

The first two failing make sense - invalid nesting is an obvious syntax error, and since {% foo %} can affect the generated code that needs to be evaluated at compile time as well.

But for a filter this doesn't seem to be needed, since foo|bar is pretty much the same as bar(foo) (except that bar is looked up in the filter list instead of the normal scope)

@nixjdm
Copy link
Author

nixjdm commented Apr 21, 2018

since {% foo %} can affect the generated code that needs to be evaluated at compile time as well.

Should that be the case though, if it's within a failing test block like {% if false %}? I may be missing something, but I don't think I understand that. Is there a use case you have in mind?

I don't mean to belabor this point; it's fine if that shouldn't change. I may just misunderstand.

@ThiefMaster
Copy link
Member

ThiefMaster commented Apr 21, 2018

What I mean is that the Python code for {% foo %} needs to be generated before the template gets rendered - so it's impossible to know whether it's needed or not. ({% if false %} is an obvious exception since for that the outcome is known ;))

@davidism davidism changed the title Feature request: ability to optionally use a filter based on its existence optionally use a filter based on its existence Dec 3, 2019
@davidism davidism added this to the 3.0.0 milestone Mar 27, 2021
@davidism
Copy link
Member

davidism commented Apr 5, 2021

In 3.0 it will be possible to do the following:

{% if 'markdown' is filter %}
    {{ value | markdown }}
{% else %}
    {{ value }}
{% endif %}

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
3 participants