111

I want to change the value of the variable declared outside the loop within a loop. But, even when changing hit inside the loop, it keeps the initial value outside the loop.

{% set foo = False %}

{% for item in items %}
  {% set foo = True %}
  {% if foo %} Ok(1)! {% endif %}
{% endfor %}

{% if foo %} Ok(2)! {% endif %}

This renders:

Ok(1)!

So the only (bad) solution I have found so far was this:

{% set foo = [] %}

{% for item in items %}
  {% if foo.append(True) %} {% endif %}
  {% if foo %} Ok(1)! {% endif %}
{% endfor %}

{% if foo %} Ok(2)! {% endif %}

This renders:

Ok(1)!
Ok(2)!

But, it's is very ugly! Is there another more elegant solution?

β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
Shankar Cabus
  • 9,302
  • 7
  • 33
  • 43
  • 5
    I don't think there is any other way. Perhaps you could restructure the code so that you don't need to set the variable. – Alex Morega Feb 28 '12 at 17:25
  • 3
    +1 for the question, as it became answer for me :) – Boris Zagoruiko Oct 17 '13 at 15:05
  • 1
    @Shankar Cabus: great question. This should probably be classified under `Jinja Annoyances` – dreftymac Sep 14 '15 at 16:19
  • 1
    I think this is question is duplicated in: http://stackoverflow.com/questions/7537439/how-to-increment-a-variable-on-a-for-loop-in-jinja-template and http://stackoverflow.com/questions/4870346/can-a-jinja-variables-scope-extend-beyond-in-an-inner-block/4880398#4880398 (just starting, can't flag the question) You can use Pashka's approach, and add `jinja2.ext.do` to clean it a little bit – Gerardo Roza Mar 22 '17 at 22:28
  • I found this code to be the only way to workaround construct that I was unable to use in salt+jinja: somelist|map(format)|join – Martin Aug 10 '17 at 12:05

3 Answers3

95

Try also dictionary-based approach. It seems to be less ugly.

{% set vars = {'foo': False} %}

{% for item in items %}
  {% if vars.update({'foo': True}) %} {% endif %}
  {% if vars.foo %} Ok(1)! {% endif %}
{% endfor %}

{% if vars.foo %} Ok(2)! {% endif %}

This also renders:

Ok(1)!
Ok(2)!
Pashka
  • 1,358
  • 10
  • 10
  • 13
    Still ugly, but it works. I'm quite surprised there is no Pythonic way of doing this with jinja2. – kramer65 Dec 03 '15 at 09:15
  • 2
    Definitely a bit cleaner especially if you need more than one variable – Ade Miller Jul 20 '16 at 20:16
  • 1
    TLDR, does `set vars` just plain not work in a for loop? – ThorSummoner Feb 15 '17 at 21:10
  • 3
    @kramer65: Solutions appear to be in the works: https://github.com/pallets/jinja/pull/684; https://github.com/pallets/jinja/pull/676 – Michael Scheper May 25 '17 at 15:19
  • 1
    @ThorSummoner seems like it. I have been hearing a lot of people praise python. They are just cooking with water as well. – Toskan Sep 13 '17 at 10:52
  • Doesn't work in Askama 2023. – O'Niel Jan 05 '23 at 01:02
  • @ThorSummoner I haven't checked intensively, but it seems like setting variables inside a loop overshadows the outside loop variable. Once the scopus of the new variable is left, the variable name refers to the old variable again. Weird. – Natan Jan 18 '23 at 09:54
84

As mentioned in the documentation:

Please note that assignments in loops will be cleared at the end of the iteration and cannot outlive the loop scope.

but as of version 2.10 you can use namespaces:

{% set ns = namespace(foo=false) %}      
{% for item in items %}
  {% set ns.foo = True %}
  {% if ns.foo %} Ok(1)! {% endif %}
{% endfor %}

{% if ns.foo %} Ok(2)! {% endif %}
β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
Omer
  • 1,050
  • 11
  • 14
  • 4
    Where you say `namespace(foo=false)` is the lowercase f in False a jinja2 colloquialism or did you mean False, as python requires for boolean values? – mas Jul 12 '18 at 18:16
  • 8
    the lowercase false is part of jinja's convention: " `The special constants true, false, and none are indeed lowercase. Because that caused confusion in the past, (True used to expand to an undefined variable that was considered false), all three can now also be written in title case (True, False, and None). However, for consistency, (all Jinja identifiers are lowercase) you should use the lowercase versions.`" – Omer Jul 16 '18 at 13:52
  • 1
    After trying for hours I thankfully found this answer! This works in Home Assistant templates. – marlar Apr 05 '22 at 12:26
  • Source: https://github.com/pallets/jinja/pull/684 – Toledo May 31 '23 at 16:55
0

You could do this to clean up the template code

{% for item in items %}
  {{ set_foo_is_true(local_vars) }}
  {% if local_vars.foo %} Ok(1)! {% endif %}
{% endfor %}
{% if local_vars.foo %} Ok(2)! {% endif %}

And in the server code use

items = ['item1', 'item2', 'item3']
#---------------------------------------------
local_vars = { 'foo': False }
def set_foo_is_true(local_vars):
  local_vars['foo'] = True
  return ''
env.globals['set_foo_is_true'] = set_foo_is_true    
#---------------------------------------------
return env.get_template('template.html').render(items=items, local_vars=local_vars)

This could be generalized to the following

{{ set_local_var(local_vars, "foo", False) }}
{% for item in items %}
  {{ set_local_var(local_vars, "foo", True) }}
  {% if local_vars.foo %} Ok(1)! {% endif %}
{% endfor %}
{% if local_vars.foo %} Ok(2)! {% endif %}

And in the server code use

items = ['item1', 'item2', 'item3']
#---------------------------------------------
local_vars = { 'foo': False }
def set_local_var(local_vars, name, value):
  local_vars[name] = value
  return ''
env.globals['set_local_var'] = set_local_var
#---------------------------------------------
return env.get_template('template.html').render(items=items, local_vars=local_vars)
Daniel F
  • 13,684
  • 11
  • 87
  • 116