如何通过Jinja2将列表从Python传递到JavaScript


76

假设我有一个Python变量:

list_of_items = ['1','2','3','4','5']

然后通过呈现HTML将其传递给Jinja,并且我在JavaScript中还有一个名为的函数somefunction(variable)。我正在尝试通过的每个项目list_of_items。我尝试过这样的事情:

{% for item in list_of_items %}
<span onclick="somefunction({{item}})">{{item}}</span><br>
{% endfor %}

是否可以将列表从Python传递到JavaScript,还是应该逐个循环传递列表中的每个项目?我怎样才能做到这一点?

Answers:


116

要将一些上下文数据传递给javascript代码,您必须对其进行序列化,以使其能够被javascript(即JSON)“理解”。您还需要使用safeJinja过滤器将其标记为安全,以防止数据被html转义。

您可以通过执行以下操作来实现:

风景

import json

@app.route('/')
def my_view():
    data = [1, 'foo']
    return render_template('index.html', data=json.dumps(data))

模板

<script type="text/javascript">
    function test_func(data) {
        console.log(data);
    }
    test_func({{ data|safe }})
</script>

编辑-确切答案

因此,要完全实现所需的功能(遍历项目列表,并将其传递给javascript函数),您需要分别序列化列表中的每个项目。您的代码将如下所示:

风景

import json

@app.route('/')
def my_view():
    data = [1, "foo"]
    return render_template('index.html', data=map(json.dumps, data))

模板

{% for item in data %}
    <span onclick=someFunction({{ item|safe }});>{{ item }}</span>
{% endfor %}

编辑2

在我的示例中,我使用Flask,我不知道您使用的是什么框架,但是您知道了,您只需要使其适合您使用的框架即可。

编辑3(安全警告)

永远不要使用用户提供的数据来执行此操作,而只能使用受信任的数据来执行此操作!

否则,您将使您的应用程序暴露于XSS漏洞!


9
可能更好的方法是,不作为JSON字符串传递{{item|tojson|safe}}而是执行
Nick Garvey 2014年

2
-1; 在一般情况下,这是不安全的,并且只有少数情况下可以使用它。如果任何一项包含空格,则输出将被打断,如果其中一项" ><script>alert('pwned');</script> "是XSSed。是的,您有一个安全警告,但是即使这样,这也是一个错误的答案;有这样说的清洁方法破(如使用瓶的tojson过滤器),这些应改为使用。
Mark Amery

太棒了,它需要使用“ {{data | safe}}”从jinja变量中加载数据。
Ryan Chou

{{数据| 安全}}在出现的列表上为我工作,而无需先转换为json。
rbennell '19

28

使用Flask时,我遇到了类似的问题,但我不必诉诸JSON。我只是通过一个列表letters = ['a','b','c']render_template('show_entries.html', letters=letters)和集

var letters = {{ letters|safe }}

在我的JavaScript代码中。Jinja2替换{{ letters }}['a','b','c'],其中javascript将其解释为字符串数组。


5
-1; 这不适用于所有值,并且如果用户提供了任何内容,则很危险。例如,如果列表中的字符串之一是字符串,</script><script>alert('pwned')</script>则会中断您的页面。
Mark Amery

9

您可以使用Jinja的tojson过滤器执行此操作

将结构转储到JSON,以便可以安全<script>地在HTML中任何位置的标记[和]中使用双引号属性除外。

例如,在您的Python中,编写:

some_template.render(list_of_items=list_of_items)

...或者就Flask端点而言:

return render_template('your_template.html', list_of_items=list_of_items)

然后在您的模板中编写以下代码:

{% for item in list_of_items %}
<span onclick='somefunction({{item | tojson}})'>{{item}}</span><br>
{% endfor %}

(请注意,该onclick属性是引号。这是必需的,因为会|tojson转义'字符,但不会转义"在其输出中,这意味着可以安全地在单引号的HTML属性中使用它,而不能在双引号的HTML属性中使用它。)

或者,要用于list_of_items内联脚本而不是HTML属性,请编写以下代码:

<script>
const jsArrayOfItems = {{list_of_items | tojson}};
// ... do something with jsArrayOfItems in JavaScript ...
</script>

请勿使用json.dumpsPython代码对JSON变量进行编码,并将结果JSON文本传递给模板。对于某些字符串值,这将产生不正确的输出,并且如果您尝试对用户提供的值进行编码,则会使您接触XSS。这是因为Python的内置json.dumps功能不会转义诸如<>(需要转义以安全地将值模板化为内联<script>s的字符,如https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for所述) -contents-of-script-elements)或单引号(需要转义以安全地将值模板化为单引号HTML属性)。

如果您使用的是Flask,请注意Flask会注入自定义tojson过滤器,而不是使用Jinja的版本。但是,上面编写的所有内容仍然适用。这两个版本的行为几乎完全相同。Flask只是允许某些特定于应用程序的配置,而Jinja的版本中没有。


1
该答案似乎比所选答案更好。
郑刘

1

为了加总所选答案,我一直在测试一个新的选项,该选项也可以使用jinja2和flask一起工作:

@app.route('/')
def my_view():
    data = [1, 2, 3, 4, 5]
    return render_template('index.html', data=data)

模板:

<script>
    console.log( {{ data | tojson }} )
</script>

渲染模板的输出:

<script>
    console.log( [1, 2, 3, 4] )
</script>

safe可以添加,但也喜欢{{ data | tojson | safe }}以避免HTML逃跑,但它是工作,没有太多。


0

我可以建议您使用一种面向JavaScript的方法,该方法可以轻松地在项目中使用javascript文件。

在您的jinja模板文件中创建一个javascript部分,并将要在javascript文件中使用的所有变量放在一个window对象中:

Start.html

...
{% block scripts %}
<script type="text/javascript">
window.appConfig = {
    debug: {% if env == 'development' %}true{% else %}false{% endif %},
    facebook_app_id: {{ facebook_app_id }},
    accountkit_api_version: '{{ accountkit_api_version }}',
    csrf_token: '{{ csrf_token }}'
}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='app.js') }}"></script>
{% endblock %}

Jinja将替换值,并且可以从其他脚本文件访问我们的appConfig对象:

App.js

var AccountKit_OnInteractive = function(){
    AccountKit.init({
        appId: appConfig.facebook_app_id,
        debug: appConfig.debug,
        state: appConfig.csrf_token,
        version: appConfig.accountkit_api_version
    })
}

我已经用这种方式将html代码与html文档分开了,这种方式更易于管理和seo友好。


-1是因为,与此处的大多数其他答案一样,如果要模板化的字符串变量包含某些字符或子字符串(如单引号或</script><script>alert("XSSed ur site!!1")</script>),则此设置将中断。在您的最后一段中,关于SEO受网站使用内联JavaScript还是外部脚本的影响的说法在我看来也令人怀疑。
Mark Amery

@MarkAmery感谢您的澄清。我今天使用两种方法在浏览器中获取服务器变量。第一个与答案有点不同<input type="hidden" name="server_data" value="DATA">数据是json编码的,当然必须进行转义。而第二个是做一个API请求,如果数据依赖于访问者的最好使API请求得到它。
muratgozel

0

制作一些不可见的HTML标记(如<label>, <p>, <input>等),并命名其ID,而类名是一种模式,以便以后可以检索它。

让您拥有两个长度相同的清单maintenance_next []和maintenance_block_time [],并且您想使用烧瓶将这两个清单的数据传递给javascript。因此,您需要使用一些不可见的标签标记并将其标记名称设置为列表索引的模式,并将其类名称设置为index的值。

{% for i in range(maintenance_next|length): %}
<label id="maintenance_next_{{i}}" name="{{maintenance_next[i]}}" style="display: none;"></label>
<label id="maintenance_block_time_{{i}}" name="{{maintenance_block_time[i]}}" style="display: none;"></label>
{% endfor%}

现在,您可以使用如下所示的javascript操作来检索javascript中的数据-

<script>
var total_len = {{ total_len }};
 
for (var i = 0; i < total_len; i++) {
    var tm1 = document.getElementById("maintenance_next_" + i).getAttribute("name");
    var tm2 = document.getElementById("maintenance_block_time_" + i).getAttribute("name");
    
    //Do what you need to do with tm1 and tm2.
    
    console.log(tm1);
    console.log(tm2);
}
</script>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.