Jinja2 templates and Excel file - python

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>

Related

For loop inside Jinja2 template with CSV

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"].

Jinja 2 - displaying all my words instead of one

Please excuse my code, I am still a relative beginner trying to complete a school project! Basically, I am trying to create a language learning system where you input a word and it checks if it is correct. However, on the page learn.html all of the words in my database are coming up. I want to get it so that when you load the page there is the first word and when you click the button it checks that word, allowing the page to display the next word to translate. An example of this would be memrise, this screenshot shows the kind of system that I am trying to emulate.
I know that this is because of the for loop but I have tried lots of solutions to no success. It seems like a really easy problem but I am really stuck. I have 0 JavaScript expertise so if possible I would try to stay away from JS solutions. I will really appreciate any help possible :)
learn.html
{% block content %}
<form class = "form" id="form1" action="/learn/check" methods=["POST"]>
<td>
<h5> Please enter the spanish for : {% for word in course_content %}{{ word.english }} {% endfor %} </h5>
<input type="text" name="language_learning_input" id="desc" value="" size="100" maxlength="140"/>
<p> </p>
</td>
<input type="submit" name="button" class="btn btn-success" value="Update"/>
</form>
{% endblock content %}
snippet of main.py
#LEARN SYSTEM
#STORING DB VALUES IN LISTS
def spanish():
#setting variables
holding_spanish = []
#running db query
course_content = db.session.query(Course).all()
#iterating through db
for row in course_content:
holding_spanish.append(row.spanish)
return holding_spanish
def english():
#setting variables
holding_english = []
#running db query
course_content = db.session.query(Course).all()
#iterating through db
for row in course_content:
holding_english.append(row.english)
return holding_english
def score():
#setting variables
holding_score = []
#running db query
account_settings = db.session.query(AccountSettings).all()
#iterating through db
for row in account_settings:
holding_score.append(row.words_per_lesson)
return holding_score
#MAIN LEARN PAGE
#app.route("/learn")
def learn():
#getting values
english()
spanish()
score()
x=1
testingvalue = [score()]
acccount_settings = db.session.query(AccountSettings).all()
course_content = db.session.query(Course).all()
return render_template('learn.html', course_content=course_content, acccount_settings=acccount_settings,testingvalue=testingvalue,x=x,english=english)
#ROUTE USED TO CHECK
#app.route("/learn/check", methods =['GET'])
def learncheck():
course_content = db.session.query(Course).all()
language_learning_input = request.args.get('language_learning_input')
for row in course_content:
if language_learning_input == row.spanish:
"<h1> correcto! </h1>"
print("true")
else:
"<h1> not correcto! :</h1>"
print("false")
Basically you need to understand two things.
First, of the many words you have to return only one. *
So, how do you know which word the user has already seen?
You don't. HTTP is a stateless protocoll. That means, each request (access by a browser) is independent of another one.
But you can work around this limitation.
So, second, you need to save the state. And there are several concepts.
From the top of my head, you could use
sessions
cookies
or pass a parameter with e.h. the current word, so you can deduct the next word
That's it. Enjoy your homework and do not hesitate to create a new question when you encounter a specific problem
You could also return all words, and then do the cycling through the words via Javascript.

Flask correct variable value on terminal but not in HTML template

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.

Best way to send json to front end in python [duplicate]

This question already has answers here:
Return JSON response from Flask view
(15 answers)
Create a JSON file from a Flask view to be used with D3
(1 answer)
Closed 5 years ago.
I don't have much experience with python and I am looking for a clean way to send a json(which is the result of a query )to the front end and have structred nice.
The query looks like this:
def myquery(time1, time 2):
query_basictable = """
Select ...
my_data = pd.read_sql(sql=query_basictable, con=engine)
return my_data.to_json()
Now here is where I belive I am mistaking:
df_my_data=myquery(time1, time 2)
data = df_my_data
The json:
data_out = {}
data_out['datas']={}
data_out['datas']['stores']= data
And in the front end:
<td colspan="2" class="{{ data['datas']['stores']}}">
<h4><b>Stores</b></h1>
<h3>{{ data['datas']['stores'] }}</h3>
</td>
The result looks like this:
{"Store":{"0":"Store_013","1":"Store_03"}}
What am I doing wrong in order to have something nice like:
Store_013
Store_03
The jinja template can receive the dictionary and iterate over it using iteritems() and for loop
<ul>
{% for key, value in dataout.iteritems() %}
<li>{{value}}</li>
{% endfor %}
</ul>
Here dataout is send using render_template() in view function as follow:
#app.route('/yoururl')
def view_name():
# Other logics here
return render_template('some.html', dataout=dataout)
For this at least create an empty some.html file (under templates directory)

Pass Two Values in For Loop Python

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

Categories

Resources