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

99

假设我有一个Python变量:

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

我通过渲染HTML将它传递给Jinja,并且我还有一个名为somefunction(variable)的JavaScript函数。我正在尝试传递list_of_items中的每个项目。我尝试了如下代码:

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

我能否将 Python 中的列表传递到 JavaScript,或者我需要在循环中逐一传递列表中的每个元素?如何实现?

7个回答

139

为了将一些上下文数据传递给JavaScript代码,您需要以JavaScript(即JSON)“理解”的方式对其进行序列化。您还需要使用safe Jinja过滤器将其标记为安全,以防止数据被HTML转义。<\p>

您可以通过以下方式实现此目的:

视图

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漏洞的风险!


11
更好的方法是不要将其作为JSON字符串传递,并改用 {{item|tojson|safe}} - Nick Garvey
3
这段内容存在普遍的问题和安全隐患,只有在特定情况下使用才可以。如果其中任何一项包含空格,输出将会出错,如果其中一项是“><script>alert('pwned');</script>”,那么你就会受到跨站脚本攻击。虽然有安全警告,但这仍然是一个不好的答案;有干净的方法可以解决这个问题(例如使用 Flask 的 tojson 过滤器),应该使用它们来代替。 - Mark Amery
很棒,需要使用“{{ data | safe }}”从jinja变量中加载数据。 - Ryan Chou
1
{{ data | safe }} 对于我来说很有效,可以直接使用列表,无需先转换为json。 - rbennell

40

我使用 Flask 时遇到了类似的问题,但我不必使用 JSON。我只需要传递一个列表 letters = ['a','b','c'],并使用 render_template('show_entries.html', letters=letters),然后设置

var letters = {{ letters|safe }}
在我的 JavaScript 代码中,Jinja2 用['a', 'b', 'c']替换了{{ letters }},这被 JavaScript 解释为一个字符串数组。

6
-1; 这种方法并不适用于所有的数值,并且如果列表中有任何用户提供的内容,这种方法会带来安全风险。例如,如果你的列表中有一个字符串是 </script><script>alert('pwned')</script>,那么它将会破坏你的页面。 - Mark Amery

25
你可以使用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 属性而不是双引号属性。)
或者,如果想在内联脚本中使用 list_of_items 而不是 HTML 属性,请写成这样:
<script>
const jsArrayOfItems = {{list_of_items | tojson}};
// ... do something with jsArrayOfItems in JavaScript ...
</script>

不要使用 json.dumps 对Python代码中的变量进行JSON编码,并将生成的JSON文本传递给模板。这会导致某些字符串值的输出不正确,并且如果您尝试对用户提供的值进行编码,还会使您面临XSS攻击的风险。这是因为Python内置的json.dumps不会转义字符,例如<>(需要转义才能安全地将值模板化到内联的<script>中,如https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements中所示),或单引号(需要转义以将值安全地模板化到单引号的HTML属性中)。
如果您正在使用Flask,请注意Flask注入了一个自定义的 tojson 过滤器,而不是使用Jinja的版本。但是,仍然适用上述所有内容。这两个版本的行为几乎相同;Flask只是 允许一些特定于应用程序的配置,这在Jinja的版本中不可用。

4
这个答案似乎比被选中的答案更好。 - Zheng Liu
1
这是这里最好的答案。 - Anonymous12332313

7

补充选定答案,我一直在测试一种新的选项,它也可以使用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转义,但不添加也能正常工作。


2

我可以为您提供一种基于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
    })
}

我使用这种方法将JavaScript代码与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

你能做到

<tbody>
    {% for proxy in proxys %}
            <tr>
                <td id={{proxy.ip}}>{{proxy.ip}}</td>
                <td id={{proxy.port}}>{{proxy.port}}</td>
                <td>{{proxy.protocol}}</td>
                <td>{{proxy.speed}}</td>
                <td>{{proxy.type}}</td>
                <td>{{proxy.city}}</td>
                <td>{{proxy.verify_time}}</td>
                <td>
                    <button type="button" class="btn btn-default" aria-label="Left Align">
                        <span class="glyphicon glyphicon-paste" aria-hidden="true" onclick="copyProxy('{{proxy.ip}}', '{{proxy.port}}')"></span>
                    </button>
                </td>
            </tr>
        {% endfor %}
    </tbody>
</table>

-1
创建一些不可见的 HTML 标签,例如 <label>, <p>, <input> 等,并为其命名 id,class 名称采用一个模式以便稍后检索。
假设你有两个长度相同的列表 maintenance_next[] 和 maintenance_block_time[],你想使用 Flask 将这两个列表的数据传递给 JavaScript。因此,你可以创建一些不可见的标签,将其标签名称设置为列表索引的模式,并将其 class 名称设置为索引处的值。

{% 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 操作来检索数据,例如以下操作 -

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接