jinja2: including script, replacing {{variable}} and json encoding the result - python

I'm attempting to build a json document using Jinja2 and the document has a nested script. The script contains {{<variables>}} that I need to replace along with non-json characters. Therefore, the script needs to be json-escaped afterwards using json.dumps().
str = '{
"nestedScript" : {% include "scripts/sc.ps1" | json %}
}'
The escape function isn't enough, since the finished product contains characters that are valid HTML, but not JSON.
I'm thinking something along the lines of:
str = '{
"nestedScript" : {{ include("scripts/sc.ps1") | json}} %}
}'
using some a custom filter or something, but I can't seem to write the include function such that it also does variable replacement in the script. This is my script so far that I am including as a global:
Complete example
Folder structure:
.
└── templates
├── test.json
└── scripts
    └── script.ps1
template-file:
test.json = '{
"nestedScript" : {{ include("scripts/script.ps1") | json}}
}'
Script:
loader = jinja2.FileSystemLoader("templates")
env = jinja2.Environment(loader=self.loader)
template = env.get_template("test.json")
template.render({
'service_name': 'worker',
'context_name': 'googlesellerratings',
})
result:
{
"nestedScript" : "echo {{service_name}}"
}

Solved this in a way that feels a little hackish, so I'd love some feedback. When instantiating my class, I defined a global function called include_script:
loader = jinja2.FileSystemLoader(templates_folder)
env = inja2.Environment(loader=self.loader)
env.globals['include_script'] = render_script_func(env)
the render_script_func returns a function that retrieves the context from the environment and uses it to render the file I referenced:
def render_script_func(env):
def render_script(name):
ctx = env.globals['context']
tmpl = env.get_template(name)
raw = tmpl.render(ctx)
return json.dumps(raw)
return render_script
Now, before rendering, all one has to do, is add the context for the rendering to the global context object:
template = env.get_template(template_name)
template.environment.globals["context"] = ctx
renderedData = template.render(ctx)
And when a template uses {{ include_script("path/to/script") }} the script is both rendered and then json encoded.
Still feels a bit wrong, but so be it.

Given the (incomplete) example it seems you are searching the filter tag.
First the script in a form that's actually runnable and defines a json filter in terms of json.dumps():
import json
import jinja2
loader = jinja2.FileSystemLoader('templates')
env = jinja2.Environment(loader=loader)
env.filters['json'] = json.dumps
template = env.get_template('test.json')
print(
template.render({
'service_name': 'worker',
'context_name': 'googlesellerratings',
})
)
The missing PowerShell script with characters that cause trouble if used as is in JSON ("):
echo "{{service_name}}"
Now the solution for the test.json template:
{
"nestedScript" : {% filter json %}{% include "scripts/script.ps1" %}{% endfilter %}
}
And finally the printed result:
{
"nestedScript" : "echo \"worker\""
}

Related

How to handle line breaks in a templated file included in a Jinja2 JSON template

I am writing a Jinja2 template to output a JSON DAG where one value is a string containing a list of commands from an external shell script, (containing templated variables. This works if the commands in the script are semicolon-separated on a single line:
echo 'hello'; echo 'world';
But, it fails to render the template when formatted with each command on its own line:
echo 'hello';
echo 'world';
JSONDecodeError: Invalid control character at: line 2 column 29 (char 30)` where character 30 is the line break.
I understand this is because JSON does not support multi-line strings. I am using Jinja2's {% include path %} to load the file but am not sure how to escape the new lines before rendering them.
I was not able to pipe the include output through a Python replace successfully:
{% include path | replace("\n", " ") %}
I also tried putting the include in a macro with the replace calling inside or outside the macro.
Here is a full example of the template and rendering code.
multi_line.sh
echo 'hello';
echo '{{variable}}';
variables.json
{
"variable": "world!"
}
template.j2
{
{# Render included file into single line #}
"multi": "{% include './multi_line.sh' %}"
}
template_renderer.py
from jinja2 import Environment, FileSystemLoader
import json
with open("./variables.json") as variables_file:
variables_json = json.load(variables_file)
loader = FileSystemLoader("./")
env = Environment(loader=loader)
template = env.get_template("./template.j2")
rendered_template = template.render(variables_json)
json_output = json.loads(rendered_template)
print(json.dumps(json_output, indent=2))
The solution for me was to wrap the include in a block assignment and then pipe the results into replace to remove occurrences of \n.
{% set multi_line_script %}
{% include 'multi_line.sh' %}
{% endset %}
{
"multi": "{{ multi_line_script | replace("\n", " ") }}"
}

Flask how to pass list of list to javascript [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)

Passing in variable template inputs for render_template() (Flask) [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 webapp to set all query vars to a js object

I am building a simple web app with Python and web.py. My current challenge is to take all query vars (web.input()) and set a JavaScript object in the HTML template. However, I don't know how to ensure this gets rendered OK instead of a long string with encoding.
Thanks a million!
I have the following code in app.py, template HTML and content HTML:
app.py:
import web
urls = (
'/hello', 'hello',
'/bye/', 'bye'
)
app = web.application(urls, globals(), autoreload=True)
#test = "testwaarde"
test = web.input()
render = web.template.render('templates/', base='layout')
class hello:
def GET(self):
return render.index("Templates demo",test, "Hello", "A long time ago...")
class bye:
def GET(self):
return render.hell_form("Templates demo",test, "Bye", "14", "8", "25", "42", "19")
if __name__ == "__main__":
app.run()
Template:
$def with (page)
<html>
<head>
<title>$page.title</title>
<!-- Digital Data Layer -->
<script>
var test = "{{$page.path}}"
var digitalData = {
'page':{
'path': '$page.path'
}
};
</script>
</head>
<body>
<p>You are visiting page <b>$page.name</b>.</p>
$:page
</body>
</html>
index HTML:
$def with (title, **path,name, content)
$var title:$title
$var name:$name
$var path:$path
<p>$content</p>
You're close:
Move your call to test = web.input() to within your GET() methods, not at the top of the file. That's just a bug. (I know that's just your sample code, but it doesn't work.)
Within index.html, use
$var path = path
The using var path: $path sets the template variable to a string representation of path. You want to set it to the dict. That's one difference between the colon and the equal sign. Second, because it's '=', the right hand side is interpreted as python, so no leading '$'.
Within your template layout.html, change your javascript to something similar to:
var test = "$:page.path";
var digitalData = {
'page': {
'path': $:page.path
}
};
You'll want to escape the data using $: rather than $. That's why you're seeing the encoded values. Also, you don't want to surround the final $:page.path with quotes to set page.path to an object, rather than just a string.

Issue in storing Mysql Data From Flask to jinja (HTML) variable as json [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)

Categories