Apply if statement inside for loop in Python Jinja Templates - python

I got this simple HTML template:
{% block html_page %}
{% set counter = 1 %}
{% for field in fields %}
COUNTER: {{ counter }} <br>
{% set counter = counter+1 %}
{% endfor %}
{% endblock %}
Where fieldscontains 4 items.
The output is:
COUNTER: 1
COUNTER: 1
COUNTER: 1
COUNTER: 1
While the output i want should be with the counter increasing:
COUNTER: 1
COUNTER: 2
COUNTER: 3
COUNTER: 4
The counter assignment is being done INSIDE the for loop, so I don't get why does it keep returning to 1.
Any suggestions?

This doesn't work due to scoping rules in Jinja.
After Jinja 2.10, to solve the scope problem, you can do something like this:
{% set count = namespace(a=0) %}
{% for field in fields %}
{{ count.a }}
{% set count.a = count.a + 1 %}
{% endfor %}
Or you could use loop.index:
{% for field in fields %}
{{ loop.index }}
{% endfor %}

Related

Forloop.counter in Django

{% for each_item in item.artifacts %}
{% if each_item.scanner_count > 0 and each_item.scanner_match > 0 %}
{% if forloop.counter <= 5 %}
<tr>
<td>{{each_item.threat_name}}</td>
</tr>
{% else %}
{% if forloop.last %}
<p><b><i> {{ forloop.counter|add:"-5" }} rows were truncated. See full report for more details. </i></b></p>
{% endif %}
{% endif %}
{% else forloop.counter -=1 %}
{% endif %}
{% endfor %}
ERROR:Malformed template tag at line 171: "else forloop.counter -=1"
I want to increment the counter only when if condition is successful. Dont know how to do it with forloop.counter. Goal is to print 5 rows of valid output(scanner count >0 and Scanner match >0)
You can use combination of add and forloop counter to achieve what you want to achieve. But remember you need to pass some variable (I've used rank here for kinda storing the increment variable in template.
views.py
rank = 0
return render(request, "base.html", {"items": items, "rank": rank})
html
{% for each_item in items %}
{% if each_item.scanner_count > 0 and each_item.scanner_match > 0 %}
{% if forloop.counter|add:rank <= 5 %}
<tr><td>{{each_item.threat_name}}</td></tr>
<br>
{% else %}
{% if forloop.last %}
<p><b><i> {{ forloop.counter|add:"-5" }} rows were truncated. See full report for more details. </i></b></p>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
As other commenters have noted, you cannot make an assignment of the forloop.counter in the template, as that would be considered logic that should be in the view (controller). Remove the {% else forloop.counter -= 1 %} from your code and it should work as I think you intended. If not, either add logic in the object (context) being passed to the template or user other forloop attributes/variables as found in the Django documentation here: https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#for

How can i access a jinja2 variable outside the for loop?

im trying to use a for loop to add up some numbers for each day
and i would like to access the variable outside the for loop im not sure how to go about this I am using the flask framework with python and just come from weppy where this was not a problem is there a way to make it work the same way in flask?
here is some simple code
{% set newtotal = 0 %}
{% for item in daily: %}
{% set newtotal = newtotal + item[10]|float %}
{% endfor %}
<div class="bottom">
<span>Total: {{ newtotal }}</span>
</div>
the numbers collected by item[10] are dollar values
if i place the {{ newtotal }} before the endfor it shows every value as its being added up this is not what I want
EDIT:
if it helps daily is a list of 8 tuples
Please keep in mind that it is not possible to set variables inside a block or loop and have them show up outside of it.
As of version 2.10 this can be handled using namespace objects which allow propagating of changes across scopes.
Here is your code using namespace:
{% set ns = namespace (newtotal = 0) %}
{% for item in daily: %}
{% set ns.newtotal = ns.newtotal + item[10]|float %}
{% endfor %}
<div class="bottom">
<span>Total: {{ ns.newtotal }}</span>
</div>
One solution (probably the "simplest") would be to change your Python script to pass in a variable named newtotal that would simply be the length of the daily list!
Alternatively, you could use the length filter:
{{things|length}}
In which case your code could look something like this:
{% set newtotal = 0 %}
{% for item in daily: %}
{% set newtotal = newtotal + item[10]|float %}
{% endfor %}
<div class="bottom">
<span>Total: {{daily|length}}</span>
</div>
Hope it helps!
Additional Sources:
jinja2: get lengths of list
How do I access Jinja2 for loop variables outside the loop?
EDIT
Sorry, I misread the question!
You can use the sum filter instead ({{ list | sum() }}).
So your code could look like:
{% set newtotal = 0 %}
{% for item in daily: %}
{% set newtotal = newtotal + item[10]|float %}
{% endfor %}
<div class="bottom">
<span>Total: {{ daily | sum() }}</span>
</div>
New sources:
Documentation
Sum elements of the list in Jinja 2
Use the namespace object.
https://jinja.palletsprojects.com/en/master/templates/#assignments
Here’s a working example from my config.
{% set i= namespace(fxp0_ip=0) %}
{% set i= namespace(mgmt_ip = 0) %}
{% set i= namespace(loopback_ip = 0) %}
{% set i= namespace(lan_ip = 0) %}
{% set i= namespace(wan_ip = 0) %}
{% for interface in device_vars.interfaces %}
{% elif interface.name == "ge-0/0/0" %}
{% set i.mgmt_ip = interface.ip_addr %}
{% elif interface.name == "lo0" %}
{% set i.loopback_ip = interface.ip_addr %}
{% elif interface.name == "ge-0/0/2" %}
{% set i.lan_ip = interface.ip_addr %}
{% elif interface.name == "ge-0/0/1" %}
{% set i.wan_ip = interface.ip_addr %}
{% endif %}
{% endfor %}
{{i.mgmt_ip}}
{{i.wan_ip}}

Get the current and next value in a Jinja for loop

Is there a way to iterate over a list in Jinja2 that can access the nth and (n+1) th element in a single iteration?
{% for x in my_list %}
<!-- print the current and the (current+1) -->
{{x}}, {{x+1}} <-- this is wrong but hopefully highlights the problem
{% endfor %}
Also, does the for loop allow for a "step" increment?
Jinja has a loop.index variable that you can use to access the next value in your list:
#app.route('/')
def index():
return render_template_string("""
<body>
{% for x in my_list %}
<p>
{{ x }} {{ my_list[loop.index] }}<br>
</p>
{% endfor %}
</body>
""", my_list=range(10))
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9
As of Jinja v2.10, we can use loop.nextitem instead of the my_list[loop.index] trick. Similarly loop.previtem is also available. :-)
From the above hyperlinked changelog:
Added previtem and nextitem to loop contexts, providing access to the previous/next item in the loop. If such an item does not exist, the value is undefined.
Maybe an example with loop.nextitem would help.
The value I'm monitoring is in the first column of my list of rows.
Try something like this:
{% for row in mylist %}
{% if row.rowtype == "Selection" %}
{% if loop.changed(row.rowid) %}
<br/>{{ row.rowprompt }}:
<select name="{{ row.rowname }}" id="{{ row.rowname }}">
<option value="{{ row.rowvalue }}">{{ row.rowvalue }}</option>
{% set isSelectStart = 1 %}
{% else %}
<option value="{{ row.rowvalue }}">{{ row.rowvalue }}</option>
{% endif %}
{% if loop.nextitem is defined and row.rowid != loop.nextitem[0] %}
</select>
{% endif %}
{% if loop.nextitem is not defined %}
</select>
{% endif %}
{% endif %}
{% endfor %}

Convert negative number to positive number in django template?

How to convert negative number to positive number in django template?
{% for balance in balances %}
{{ balance.amount }}
{% endfor %}
If balance.amount is negative number, I want to convert it to positive number.
I would like to suggest installing django-mathfilters.
Then you can simply use the abs filter like this:
{% for balance in balances %}
{{ balance.amount|abs }}
{% endfor %}
If you don't want/can't install django-mathfilters
You can make a custom filter quite easily:
from django import template
register = template.Library()
#register.filter(name='abs')
def abs_filter(value):
return abs(value)
This works without adding django-mathfilters but it's not a very good practice.
{% if balance.amount < 0 %}
{% widthratio balance.amount 1 -1 %}
{% else %}
{{ balance.amount }}
{% endif %}
Widthratio is meant for creating bar charts but can be used for multiplication
from this SO:
{% if qty > 0 %}
Please, sell {{ qty }} products.
{% elif qty < 0 %}
Please, buy {{ qty|slice:"1:" }} products.
{% endif %}

Jinja2: Change the value of a variable inside a loop

I want to change the value of the variable declared outside the loop within a loop. But always changing, 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 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, its is very ugly! Is there another more elegant solution?
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)!
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 %}
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)

Categories