jinja2 include file with variable - python

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.

Related

Issue with displaying non-English characters in flask application in bootstrap table [duplicate]

I am trying to pass data as JSON from a Flask route to a Jinja template rendering JavaScript. I want to iterate over the data using JavaScript. The browser shows SyntaxError: Unexpected token '&'. Expected a property name. when JSON.parse is called on the rendered data. How do I use rendered JSON data in JavaScript?
var obj = JSON.parse({{ data }})
for (i in obj){
document.write(obj[i].text + "<br />");
}
def get_nodes(node):
d = {}
if node == "Root":
d["text"] = node
else:
d["text"] = node.name
getchildren = get_children(node)
if getchildren:
d["nodes"] = [get_nodes(child) for child in getchildren]
return d
tree = get_nodes("Root")
return render_template("folder.html", data=tree)
If I just put {{ data }} in the HTML part, what I see looks correct.
{'text': 'Root', 'nodes': [{'text': u'Prosjekt3'}, {'text': u'Prosjekt4', 'nodes': [{'text': u'mappe8'}]}]}
Flask's Jinja environment automatically escapes data rendered in HTML templates. This is to avoid security issues if the dev tries to render untrusted user input.
Since you are passing a Python object to be treated as JSON, Flask provides the tojson filter which automatically dumps the data to JSON and marks it safe.
return render_template('tree.html', tree=tree)
var tree = {{ tree|tojson }};
When you just look at the data rendered in HTML, it looks correct because the browser displays the escaped symbols as the real symbols (although in this case you're seeing the string representation of a Python dict, not JSON, so there's still some issues like u markers).
Previous versions of Flask didn't mark the dumped data safe, so you might come across examples like {{ tree|tojson|safe }}, which isn't required anymore.
If you're not rendering JSON (or you already dumped the JSON to a string), you can tell Jinja that data is safe to render without escaping by using the safe filter.
# already dumped to json, so tojson would double-encode it
return render_template('tree.html', tree=json.dumps(tree))
var tree = {{ tree|safe }};
You can also wrap the string in Markup before rendering it, it's equivalent to the safe filter.
# already dumped and marked safe
return render_template('tree.html', tree=Markup(json.dumps(tree)))
var tree = {{ tree }};
If you're not passing this data to JavaScript, but using it in Jinja instead, you don't need JSON. Pass the actual Python data, don't call tojson on it, and use it as you would any other data in the template.
return render_template('tree.html', tree=tree)
{% for item in tree %}
<li>{{ item }}</li>
{% endfor %}
I could archive it using the following code sample.
<script>
console.log(JSON.parse({{json|safe}}))
</script>
The problem is that your server returns not JSON, but rendered HTML, which escapes some of the symbols with & notation.
Instead of using
return render_template("folder.html", data=tree)
try
return flask.jsonify(**tree)

Python flask - Encoding links based on JSON varibale

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

How do I use Jinja2 filter on a block with argumets

I am trying to use jinja2 templates. I have custom filter called highlight, that takes string and language name and passes them to pyhments for code highlightning. I am trying to use it like this:
{% filter highlight("python") %}
import sys
def main():
pass
{% endfilter %}
But I get this error:
AttributeError: 'str' object has no attribute 'get_tokens'
Then I tried this:
{% filter highlight "python" %}
It does not work either.
There might be a trick via set block filtering and then pasting it back via {{ ... }}, but this technique is not merged in master source code yet, and seems too hacky for me.
So, is that even possible currently, or I am just doing it wrong?
EDIT: Here is filter:
#jinja2.contextfilter
def highlight(context, code, lang):
print("HIGHLIGHT")
print(code)
return jinja2.Markup(pygments.highlight(code, lexer=lang, formatter='html'))
I am an idiot, that was pygments error. By some mistake, I didn't see that last entry in stacktrace was from there.
You should use:
pygments.highlight(
code,
lexer=pygments.lexers.get_lexer_by_name(lang),
formatter=pygments.formatters.get_formatter_by_name('html')
)
instead of:
pygments.highlight(code, lexer=lang, formatter='html')

Can I make a file optional based on a variable's value in cookiecutter.json

I would like to have a file that is optionally added in my python cookiecutter project.
An example would be in cookiecutter.json have the variable
{"settings_file": true}
which would create a file settings.py at the root of my directory (with maybe some contents).
Does cookiecutter offer an option to do this? Or should I be using the post processing hook to write a script which creates the files (which I feel like is not the most elegant solution).
I'm going to answer my own question, in case someone runs into it: it is not yet implemented as a feature of the project, see ticket for enhancement here: https://github.com/audreyr/cookiecutter/issues/127
The "least ugly" solution I have come up with is to create the files every time, and clean them up during the post hook (you could also create them during the post hook, but would lose cookiecutter templating advantages)
According to the official docs, you can use a post-generate hook script (hooks/post_gen_project.py)
(Copying the snippet here for those skimming through)
import os
import sys
REMOVE_PATHS = [
'{% if cookiecutter.packaging != "pip" %} requirements.txt {% endif %}',
'{% if cookiecutter.packaging != "poetry" %} poetry.lock {% endif %}',
]
for path in REMOVE_PATHS:
path = path.strip()
if path and os.path.exists(path):
if os.path.isdir(path):
os.rmdir(path)
else:
os.unlink(path)
It is possible to use Jinja templating and conditionals in file names.
As such you can have a file named {% if cookiecutter.settings_file == "true" -%} __main__.py {%- endif %} which will only be created if settings_file is true.
Note that boolean variables are not yet supported in Cookiecutter, although there is a merged PR to add this functionality. One way to solve it now is to use e.g., ["true", "false"] as values.

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