I am trying to build an output based on a jinja2 template using a CSV as input. How could I use the for loop inside the template instead of inside the python code to render the output?
This is the code I have been working on. The code works fine without the for loop in the template. When the for loop is added to the template the output contains the same vlan information duplicated.
Is there any way to include the for-loop in the template, so that the iteration is performed in at the template level?
import csv
from jinja2 import Template
source_file = "VLAN.csv"
vlan_template_file = "vlan.j2"
vlan_configs = ""
with open(vlan_template_file) as tf:
vlan_template = Template(tf.read(), keep_trailing_newline=True)
with open(source_file) as sf:
reader = csv.DictReader(sf)
for row in reader:
vlan_config = vlan_template.render(row)
vlan_configs += vlan_config +"!\n"
print(vlan_configs)
`
The vlan_configs output provides the vlan details twice.
CSV file format:
vlan_id,vlan_name
10,VLAN_10
11,VLAN_11
12,VLAN_12
jinja2 template:
{% for vlan_id in row %}
vlan {{vlan_id}}
name {{vlan_name}}
{% endfor %}
Change your Python code to:
with open(source_file) as sf:
reader = csv.DictReader(sf)
vlan_config = vlan_template.render(csv=reader)
vlan_configs += vlan_config + "!\n"
print(vlan_configs)
And your jinja template to:
{% for row in csv %}
vlan {{row["vlan_id"]}}
name {{row["vlan_name"]}}
{% endfor %}
Explanation:
From your Python code you just send the DictReader to the jinja template. So you only do one loop in the jinja template and not in the Python code.
DictReader is a list of dictionaries.
The jinja template walks each row in the DictReader. Each row is a dict. You access the values of each row with row["vlan_id"] and row["vlan_name"].
Related
I am new to python and trying to use a jinja2 template to write some configurations file. I have already created the template with curly braces. I am using an excel file to store all the values. the problem i am looking at is that it is not scalable as I need to enter the data.value for each cell against every variable, I have more than 40 variables. Is there a way to read the values without manually telling typing each cell and also to deal with the render command the same way
I am very new to Python and only tried what I have pasted below.
import openpyxl
import jinja2
from jinja2 import Environment, FileSystemLoader
from jinja2 import Template
output = []
env = Environment(loader=FileSystemLoader('C:\\Python\\Templates'))
template1 = env.get_template('config.cfg')
xfile = openpyxl.load_workbook("templates.xlsx")
xsheets = xfile.sheetnames
data = xfile.get_sheet_by_name('variables')
DST_ROUTE1 = data['e24'].value
DST_ROUTE2 = data['e25'].value
DST_ROUTE3 = data['e26'].value
DST_ROUTE4 = data['e27'].value
DST_PREFIX1 = data['e28'].value
DST_PREFIX2 = data['e29'].value
DST_PREFIX3 = data['e30'].value
DST_PREFIX4 = data['e31'].value
output.append(template1.render(DST_ROUTE1=DST_ROUTE1, DST_ROUTE2=DST_ROUTE2, DST_PREFIX1=DST_PREFIX1))
with open("C:\\Python\\script.txt", mode='w+') as f:
f.writelines(output)
You would assign the whole dictionary to the template and loop through it there.
import openpyxl
import jinja2
from jinja2 import Environment, FileSystemLoader
from jinja2 import Template
output = []
env = Environment(loader=FileSystemLoader('C:\\Python\\Templates'))
template1 = env.get_template('config.cfg')
xfile = openpyxl.load_workbook("templates.xlsx")
xsheets = xfile.sheetnames
data = xfile.get_sheet_by_name('variables')
output.append(template1.render(sheets_by_name=data))
with open("C:\\Python\\script.txt", mode='w+') as f:
f.writelines(output)
the template would look something like this:
<table>
{% for key, sheet in sheets_by_name.items() %}
<tr>
<td>{{ key }}</td><td>{{ sheet.value }}</td>
</tr>
{% endfor %}
</table>
assuming python3 here, otherwise it's .iteritems() I believe
You can also use a subtemplate and keep the logic inside python
template1 = env.get_template('table_frame.tmpl')
subtemplate1 = env.get_template('table_row.tmpl')
sheets = ""
for key, sheet in data.items():
sheets += subtemplate1.render(key=key, sheet=sheet)
output.append(template1.render(sheets=sheets))
table_frame.tmpl
<table>
{{ sheets }}
</table>
table_row.tmpl
<tr>
<td>{{ key }}</td><td>{{ sheet }}</td>
</tr>
Similar question which does not solve my problem.
I have a Flask app which reads from a database and renders an HTML template using the DB data. I'm trying to manipulate a value I get from the DB before sending it to the HTML template and this doesn't work.
Python code:
#app.route('/pilot', methods=['GET'])
def form_view():
result = {}
# query DB and get cursor
numQuestions = 0
for row in cursor:
row.pop('_id', None) # delete the key and add modified key back
row['_id'] = row['stage'][-1] # get only last char- eg, "1" from "stage1", "2" from "stage2" and so on
print(row['_id'])
result[numQuestions] = row
numQuestions += 1
return render_template("form.html", count=numQuestions, result=result, debug=app.debug)
Output when run on terminal is as expected:
1
1
1
2
2
2
Jinja2 fragment of form.html:
{% for row in result[row_num]['scrutiny_stage'] %}
{{ row['_id'] }}
{% endfor %}
Output on browser:
stage1 stage1 stage1 stage2 stage2 stage2 stage2
Can anyone help me understand what I'm doing wrong here and how I can get the correct value of the variable I'm setting in the Python code to show up in the HTML template being rendered by Flask?
Thanks.
Thanks for the tips folks, even though I couldn't share all the context (since I have to protect proprietary info), they were helpful. I managed to solve this with multiple steps:
I redid my DB schema so that every document has an '_id' key with a plain numeric value. I dropped the 'stage': 'stage1' kinds of fields. The 'stage' is now computed dynamically in the code base on the DB query results.
Then I changed the Flask view function to add a 'stage' key to the result dict being passed to the HTML template (no mucking around with the '_id' field):
numQuestions = 0
for row in cursor:
for line in row['scrutiny_stage']:
row['stage'] = line['_id']
result[numQuestions] = row
numQuestions += 1
Finally, in my Jinja2 block, I realized that I need to operate over the result as a dict, so I changed it to use the dict.values() method:
{% for row in result.values() %}
{{ row['stage'] }}
{% endfor %}
Now, I get the same values printed in the terminal as well as in my browser.
I'm trying to refacto some pretty heavy template with jinja2 and I'm stucked on an include.
This is the behaviour i'm expecting :
<h1>{{ key }} </h1>
{% set file = key | include_text %}
{% include file %}
The custom filter returns a string like this one ::
texts/my_include.html
But instead I got this error:
jinja2.exceptions.TemplatesNotFound: Tried to select from an empty list of templates
Some hack I've already tried :
Place the templates in the same folder and remove the 'texts/' from the returned string
Add the path in the Env loader
But it keeps sending this error
I'm now wondering if jinja2 allows this implementation or if I'll have to keep this template the way it was (even if it takes a very long time to be generated).
Does someone know about some trick here ?
Well, for those who eventually met this problem in the futur, I've solved it by removing the unecessary single quotes and by sending some empty file from my custom filter when the condition is not verified... (my mistake)
Here is my custom filter :
#environmentfilter
def include_text(ctx, key):
res_dict = {
'key_value_1' : 'file_name_1',
'key_value_2' : 'file_name_2'
}
try:
return "texts/" + res_dict[key] + ".html"
except KeyError:
return "texts/empty.html"
Now, the first solution I was trying works fine.
I'm creating an application in Python flask and I'm struggling to encode my links. In my HTML template I'm calling data from JSON and based on a variable from JSON, I want to create a link to another page but the variables that have "space" in them, only take the first word and the link doesn't work as it should.
This is my JSON:
[
{
"team":"AFC Bournemouth"
},
{
"team":"Arsenal"
}
]
And this is my python:
#app.route('/<team>/')
def artist(team):
json_data=open('static/data.json').read()
data= json.loads(json_data)
urllib.quote_plus(data.team)
return render_template("team.html", team=team)
I'm trying to use "urllib.quote_plus" but I get an error
AttributeError: 'list' object has no attribute 'team'
I don't know how to fix it.
And this is my loop in html:
{% for data in results %}
<div class="team">
<p><a href=/{{ data.team }}>{{ data.team }}</a></p>
</div>
{% endfor %}
Before I used "urllib.quote_plus" the link for "Arsenal" worked perfect, but for "AFC Bournemouth" it only took the word "AFC".
That is strange that it is working correctly for "Arsenal". Actually you should iterate over the "data" because it is a list
Example:
#app.route('/<team>/')
def artist(team):
json_data=open('static/data.json').read()
data= json.loads(json_data)
data = [{'team': urllib.quote_plus(team['team'])} for team in data]
return render_template("team.html", results=data)
Another thing is that in render_template you are sending variable team and not results (changed in my example). This way it should be fine and work with your Jinja template.
Edit: changed list comprehension
I have two Python files and one HTML file. One of the Python files is using Flask to connect with the HTML file.
In file1.py(the non Flask one) I set a for loop to print the variable volume
for volume in current_volumes:
print volume
which prints out two strings in Terminal
Volume:vol-XXXXXXX
Volume:vol-YYYYYYY
Now I put from file1 import * on the top of file2.py.
Additionally, file2.py contains
def template(name=volume):
return render_template('index.html', name=name)
Index.html contains
<p>{{ name }}</p>
but only reads Volume:vol-YYYYYYY when launched.
How do I get it to print out both values of volume?
I think you want to use a for loop to create a new string:
volume_string = ""
for volume in current_volumes:
volume_string += volume
def template(name=volume_string):
...
You can insert a "\n" (newline) at the end of every volume appended to get it to 2 print lines.
I haven't played around with Flask, but you may also want to just try
def template(name=current_volumes):
Perhaps it's smart enough to make that work.
You are using an escaping for variable volume rather than the list of volumes (current_volumes). (Should you switch to Python 3 this will raise a ReferenceError instead of working). Change:
def template(name=volume):
return render_template('index.html', name=name)
to:
def template(name=current_volumes):
return render_template('index.html', name=name)
You will also want to change your {{ name }} to a loop - let's go ahead and change the name:
def template(volumes=current_volumes):
return render_template('index.html', volumes=volumes)
and then add a loop in our Jinja template:
{% for volume in volumes %}
<p>Volume Data: {{ volume }}</p>
{% endfor %}