Jinja2/Python - Strings to Integers for comparison - python

I have strings like the following:
Team Li vs. Team Aguilar || SCORE (W-L-T): 5-4-0
Favors Flavors vs. Cupcakes For Breakfast || SCORE (W-L-T): 11-2-1
I would like the text to be green if the "W" value is greater than the "L" value, and red if the "L" value is greater than the "W" value. I have the following code in Jinja2 that works for the first case, but does not work for the second case. It incorrectly displays the string as red, even though the "W" column is greater than the L column.
{% for item in matchups %}
{% if (item[-5]|int) > (item[-3]|int) %}
<font color='green'>{{ item }}</font>
{% elif (item[-5]|int) == (item[-3]|int) %}
{{ item }}
{% else %}
<font color='red'>{{ item }}</font>
{% endif %}
<br>
{% endfor %}
I understand that my code fails because the second string has 2 digits. Is there a good way to fix this issue?
This is a Jinja2 problem, so answers in Jinja2 would be great. However, a Python solution might also work too.

You can extract the elements with two splits (using variables for clarity):
first to get the last column (split by whitespace) element:
{% set results = item.split()[-1] %}
then to get the first and second of the results (split by the dash):
{% set w = results.split('-')[0]|int %}
{% set l = results.split('-')[1]|int %}
The full code (also with a condition to process only lines containing SCORE to handle the one from your now-edited-out example with *************):
{% for item in matchups %}
{% if 'SCORE' in item %}
{% set results = item.split()[-1] %}
{% set w = results.split('-')[0]|int %}
{% set l = results.split('-')[1]|int %}
{% if w > l %}
<font color='green'>{{ item }}</font>
{% elif w == l %}
{{ item }}
{% else %}
<font color='red'>{{ item }}</font>
{% endif %}
<br>
{% 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

Render list in ordered structure without unicode characters

I have a list that looks like this:
[(u'Element1', u'Description1', u'Status1), (u'newElement2', u'newDescription2', u'newStatus2), (u'nextElement3', u'nextDescription3', u'nextStatus3), (u'anotherElement4', u'anotherDescription4', u'anotherStatus4)]
I have an ansible playbook that uses a jinja2 template to render the list to a text file. The template file looks like this:
{% for item in description | batch(3, ' ') %}
{% for el in item %}
{{ el }}
{% endfor %}
{% endfor %}
But this returns the text file to look like this:
[u'Element1', u'Description1', u'Status1]
[u'newElement2', u'newDescription2', u'newStatus2]
[u'nextElement3', u'nextDescription3', u'nextStatus3]
[u'anotherElement4', u'anotherDescription4', u'anotherStatus4]
What I want the report to look like is this:
Element1 Description1 Status1
newElement2 nextDescription2 newStatus2
nextElement3 nextDescription3 nextStatus3
anotherElement4 anotherDescription4 anotherDescription4
Is there a way to remove the unicode characters and render the lists this way?
For example:
{% for row in description %}
{% for cell in row %}
{{ "%-22s"|format(cell) }}
{%- endfor %}
{% endfor %}
Yields:
Element1 Description1 Status1
newElement2 newDescription2 newStatus2
nextElement3 nextDescription3 nextStatus3
anotherElement4 anotherDescription4 anotherStatus4
But to get a dynamic padding - depending on a max length of an element in a column - looks like a much more complex task: {{ "%-22s"|format(cell) }} can be replaced with {{ "{0}".format(cell).ljust(width) }} where width can be a variable, but likely it would require another loop first to collect the lengths.
You could try
{% for el in item %}
{% for e in el %}
{{ e }}
{% endfor %}
{% endfor %}
Or use html tables if you want to be able to change formatting

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

django: mysterious padding of strings in template

I'm using the following template code to try to produce a list in the format 'a, b, c and d':
{% for item in list %}
{% if not forloop.first %}
{% if not forloop.last %}
,
{% endif %}
{% endif %}
{% if forloop.last %}
and
{% endif %}
{{ item }}
{% endfor %}
The actual output I'm getting is 'a , b , c and d' (note the spaces before the commas).
What's going on and how do I fix it?
OK, after several attempts I think I've found a solution, it's a bit verbose but it does what you want - no extra spaces -
{% for item in list %}
{% if forloop.revcounter > 2 %}
{{ item }},
{% else %}
{% if forloop.revcounter == 2 %}
{{ item }} and
{% else %}
{{ item }}
{% endif %}
{% endif %}
{% endfor %}
forloop.revcounter is counting down from the end of the loop, so you add the 'and' to the second last item and leave the last one without anything.
Django inserts all the spaces that your template contains:
{% for item in list %}{% if not forloop.first %}{% if not forloop.last %}, {% endif %}{% endif %}{% if forloop.last %} and {% endif %}{{ item }}{% endfor %}
By the way, your template renders the wrong output if the list contains only one value. The corrected template looks like:
{% for item in list %}
{% if not forloop.first %}
{% if forloop.last %}
and
{% else %}
,
{% endif %}
{% endif %}
{{ item }}
{% endfor %}
And without the unwanted space it becomes:
{% for item in list %}{% if not forloop.first %}{% if forloop.last %} and {% else %}, {% endif %}{% endif %}{{ item }}{% endfor %}
I would make a simple template filter:
#register.filter
def format_list(li):
"""
Return the list as a string in human readable format.
>>> format_list([''])
''
>>> format_list(['a'])
'a'
>>> format_list(['a', 'b'])
'a and b'
>>> format_list(['a', 'b', 'c'])
'a, b and c'
>>> format_list(['a', 'b', 'c', 'd'])
'a, b, c and d'
"""
if not li:
return ''
elif len(li) == 1:
return li[0]
return '%s and %s' % (', '.join(li[:-1]), li[-1])
I'm far from an expert in python and there's probably a better way to do it. But still, this seems clean at the "django level", use it as such:
{{ your_list|format_list }}
What I like with this solution is that it is reusable, is more readable, has less code, and has tests.
Check out the documentation on writing template filters for details on how to install it.
Also, you might notice that this function comes with doctests, refer to django documentation to learn how to run the tests.
Here's a way:
>>> python -m doctest form.py -v
Trying:
format_list([''])
Expecting:
''
ok
Trying:
format_list(['a'])
Expecting:
'a'
ok
Trying:
format_list(['a', 'b'])
Expecting:
'a and b'
ok
Trying:
format_list(['a', 'b', 'c'])
Expecting:
'a, b and c'
ok
Trying:
format_list(['a', 'b', 'c', 'd'])
Expecting:
'a, b, c and d'
ok
1 items had no tests:
form
1 items passed all tests:
5 tests in form.format_list
5 tests in 2 items.
5 passed and 0 failed.
Test passed.

Categories