Creating a table using jinja2 - python

I am trying to create a (latex) table using jinja2. I defined a macro to help me to create the table:
{% macro table(header, rows, columns) %}
\begin{tabular}{(formatting, later)}
{{ row(header) }}
{% for row in rows %}
{% for column in columns %}
{{ caller(row, column) }} & % *here*
{% endfor %} \\
{% endfor %}
\end{tabular}
{% endmacro %}
I can use the template like this:
{% call(row, column) table.table(header, rows, columns) %}
Content at row = {{row}}, column = {{column}}
{% endcall %}
I quite like how these macros work. The problem is however that I would like to join the results of the macro by a & sign rather than
playing a sign after each row. Essentially now I have this:
Content at row = 0, column = 0 &
Content at row = 0, column = 1 & \\
and I would like to get this instead:
Content at row = 0, column = 0 &
Content at row = 0, column = 1 \\
Is it possible to use filters on macro results? Is there some other way to generate a table where each cell is another jinja2 template depending on parameters?

I'm not familiar with a join functionality in Jinja itself. However it does have built in indexes on your loop. Using loop.last you can determine where to change the output symbol
{% macro table(header, rows, columns, ) %}
\begin{tabular}{(formatting, later)}
{{ row(header) }}
{% for row in rows %}
{% for column in columns %}
{{ caller(row, column) }} {% if loop.last %}\\{% else %}&{%endif%}
{% endfor %}
{% endfor %}
\end{tabular}
{% endmacro %}
Here's documentation on the other loop variables that are available: http://jinja.pocoo.org/docs/2.9/templates/#for

Related

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

Reducing size of columns in Flask-Admin

Is there a way to limit the size (length/width) of a ModelView column? I am using a WYSIWYG editor and this creates really long text, therefor making the column for the ModelView very long.
Here is picture of what it looks like. Look on the right hand side the last column. It is even longer than the screenshot could handle.
Don't show the column (by exclusion):
class MyView(ModelView):
column_exclude_list = ('description')
Don't show the column (by inclusion):
class MyView(ModelView):
column_list = ('rating', 'category_id', 'year', 'stock', 'image')
Reformat the column:
class MyView(ModelView):
def _description_formatter(view, context, model, name):
# Format your string here e.g show first 20 characters
# can return any valid HTML e.g. a link to another view to show the detail or a popup window
return model.description[:20]
column_formatters = {
'description': _description_formatter,
}
A way to do this could be to override the css style of the relevant column. In the Flask-admin list.html template you find the following code for creating the columns:
{% for c, name in list_columns %}
<td class="col-{{c}}">
{% if admin_view.is_editable(c) %}
{% set form = list_forms[get_pk_value(row)] %}
{% if form.csrf_token %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c), csrf=form.csrf_token._value()) }}
{% else %}
{{ form[c](pk=get_pk_value(row), display_value=get_value(row, c)) }}
{% endif %}
{% else %}
{{ get_value(row, c) }}
{% endif %}
</td>
{% endfor %}
So e.g. for column 2 you could add a max-width property to the css class col-2 to limit its width.

Jinja2/Python - Strings to Integers for comparison

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

python jinja2: using variable in template with conditional

I have this:
{% for row in data: %}
<td
{{'value={{row['value']}}' if 'showit' in map and header in map['showit'] and 'value' in row}}
> ... </td>
{% endfor %}
But obviously that's not working. Trying to add an HTML attribute to the cell using a secondary column in the row dict. Any thoughts?
Assuming your trouble is with the syntax getting the row['value'], concatenate the strings rather than trying to nest the {{ }}s:
{{ 'value=' + row['value'] if True }}
Replace the True with your condition of course. If you need to quote the param:
{{ 'value="' + row['value'] + '"' if True }}
As #Reza-S4 suggested you can also put the conditional outside the print statement to be a little clearer:
{% if 'showit' in map and header in map['showit'] and 'value' in row: %}
value="{{ row['value'] }}"
{% endif %}

Categories