Get the current and next value in a Jinja for loop - python

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 %}

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}}

Python Jinjna variable doesnt keep value outside loop

I am currently trying to fix an issue by using jinja variables, but somehow the variable does not keep the value outside the loop, even though I declared it before the loop begins:
{% set disablecounter = 0 %}
{% if disablecounter == 0 %}
{% for einzelroom in all_einzelzimmer %}
{% if zimmer.id == einzelroom.zimmer_id %}
{% set price = einzelroom.preis %}
<div class="preis-element">
<p class="preis"> <span class="smallab"> ab </span> {{ price|int }}€ </p>
</div>
{% set disablecounter = disablecounter + 1 %}
{{ disablecounter }}
{% endif %}
{% endfor %}
{% endif %}
{{ disablecounter }}
The variable is disablecounter inside the loop it is 1 but outside it is still 0
Thanks!
EDIT
Surrounding with a with statement also didnt worked:
{% with foo = 42 %}
{{ foo }}
{% endwith %}
{% with %}
{% set foo = 42 %}
{{ foo }}
{% endwith %}
I found a great solution here on SO by #Chris Warth.
Original answer by #Peter Hollingsworth:
https://stackoverflow.com/a/32700975/5291566
{% with disablecounter = [0] %}
{% if disablecounter == [0] %}
{% for einzelroom in all_einzelzimmer %}
{% if zimmer.id == einzelroom.zimmer_id %}
<div class="preis-element">
<p class="preis"> <span class="smallab"> ab </span> {{ einzelroom.preis|int }}€ </p>
</div>
{% if disablecounter.append(disablecounter.pop() + 1) %}{% endif %}
{% endif %}
{% endfor %}
{% endif %}

Python - find first dictionary key in template

I'm trying to show my dictionary keys and values in template in for loop, but I don't want to show the first key.
What is the best way to do it?
I thought of using something like
dictionary.keys()[0]
but i can't do it in template.
or maybe add counter to the for loop (not sure how to do it in the loop below)
This is my loop to show the dictionary-
{% for year, durations in durationDict.items %}
<optgroup label={{ year }}>
{% for duration in durations %}
<option data-from-date="{{ duration.from_date }}" data-to-date="{{ duration.to_date }}" value={{ duration.duration_id }}>{{ duration.from_date }}
- {{ duration.to_date }}</option>
{% endfor %}
{% endfor %}
If you don't want to show the first iteration of a for loop in a Django template, you can omit it by checking for forloop.first
{% for year, durations in durationDict.items %}
{% if not forloop.first %}
<optgroup label={{ year }}>
{% for duration in durations %}
<option data-from-date="{{ duration.from_date }}" data-to-date="{{ duration.to_date }}" value={{ duration.duration_id }}>{{ duration.from_date }}
- {{ duration.to_date }}</option>
{% endfor %}
{% endif %}
{% endfor %}
What I finally done was as following:
creating an OrderedDict out of my Dictionary, making sure that it's sorted by dates Descending:
duration_dict = OrderedDict(sorted(duration_dict.items(), key=lambda t: t[0], reverse=True))
So now I can be sure that the first key is also the latest year.
and for presenting all the key,value pairs without the first year I used forloop.first, as suggested by #bozdoz:
{% for year, durations in duration_dict.items %}
{% if not forloop.first %}
<optgroup label={{ year }}>
{% endif %}
{% for duration in durations %}
<option data-from-date="{{ duration.from_date }}" data-to-date="{{ duration.to_date }}" value={{ duration.duration_id }}>{{ duration.from_date }}
- {{ duration.to_date }}</option>
{% endfor %}
{% endfor %}
You can use a filter tag. For example:
template_tags.py
from django import template
register = template.Library()
#register.filter()
def exclude_first(dict):
first_key = list(dict)[0]
del dict[first_key]
return dict
html
{% load template_tags %}
{% for year, durations in durationDict|exclude_first %}
...
{% endfor %}
This should allow you to loop through all of the items in durationDict except for the first item. Keep in mind that dicts are not ordered by default. If you have a custom key/value pair that you want deleted every time, you can simply replace del dict[first_key] with del dict['latest_year'] (or replace latest_year with the key name you want to delete).

Loop over dictionary of dictionaries in Django Template for 1 line of code

The dictionary I'm passing to the Django template contains 2 dictionaries, each with a list:
'nav_dict': {
'class_name': ['Chemical', 'Avian', 'Mammal'],
'tab_label': ['Chemical!', 'Avian!', 'Mammal!']
}
I want to loop over the lists in each dict to fill out this line of code:
<li class="{{ item_className }} tabSel">{{ item_tabLabel }}</li>
where item_className = each value in the class_name list and item_tabLabel = each value in the tab_label list. The result would 3 <li> tags with a class_name and tab_label.
I have tried something like this (This code only handles the class_name part), but I can't get the loops to append to the same line of code (each <li>):
{% for key, value in nav_dict.items %}
{% if key == 'class_name' %}
{% for item_className in value %}
{% if forloop.counter0 == 0 %}
<li class="{{ item_className }} tabSel">{{ item_tabLabel }}</li>
{% else %}
<li class="{{ item_className }} tabUnsel">{{ item_tabLabel }}</li>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
The problem you have is with the way you are presenting your data.
Why don't you create your dictionary like this, where each key is the class name and the associated value is the label. It would be more semantic and readable.
'nav_dict': {
'Chemical': 'Chemical!',
'Avian': 'Avian!',
'Mammal': 'Mammal!,
}
Then you can just loop over this and place the key as the class attribute, and the value as the label attribute.
{% for key, value in nav_dict.items %}
{% if forloop.counter0 == 0 %}
<li class="{{ key }} tabSel">{{ value }}</li>
{% else %}
<li class="{{ key }} tabUnsel">{{ value }}</li>
{% endif %}
{% endfor %}
If the order of the elements is important (as dictionaries are un-ordered), you could use an OrderedDict from the collections module to retain insertion order. You would do something like this inside your view...
>>> from collections import OrderedDict
>>> nav_dict = OrderedDict(zip(['Chemical', 'Avian', 'Mamma!'], ['Chemical!', 'Avian!', 'Mammal!']))
OrderedDict([('Chemical', 'Chemical!'), ('Avian', 'Avian!'), ('Mammal', 'Mammal!')])
Similarly you could use a list of tuples as your data
'nav_list': [('Chemical', 'Chemical!'), ('Avian', 'Avian!'), ('Mammal', 'Mammal!')]
And loop over this object like so
{% for class_name, label_name in nav_list %}
{% if forloop.counter0 == 0 %}
<li class="{{ class_name }} tabSel">{{ label_name }}</li>
{% else %}
<li class="{{ class_name }} tabUnsel">{{ label_name }}</li>
{% endif %}
{% endfor %}
If you only want to append an exclamation mark to your string to create the label, you could of course create a custom template filter instead - or just append the exclamation mark inside the template itself.
If you have to use that dictionary, you can use this template code:
{% for item_className in nav_dict.class_name %}
{% with forloop.counter0 as index_className %}
{% for item_tabLabel in nav_dict.tab_label %}
{% with forloop.counter0 as index_tabLabel %}
{% ifequal index_className index_tabLabel %}
{% if index_className == 0 %}
<li class="{{ item_className }} tabSel">{{ item_tabLabel }}</li>
{% else %}
<li class="{{ item_className }} tabUnsel">{{ item_tabLabel }}</li>
{% endif %}
{% endifequal %}
{% endwith %}
{% endfor %}
{% endwith %}
{% endfor %}

Categories