So übergeben Sie eine Liste von Python über Jinja2 an JavaScript

74

Angenommen, ich habe eine Python-Variable:

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

und ich übergebe es Jinja durch Rendern von HTML, und ich habe auch eine Funktion in JavaScript namens somefunction(variable). Ich versuche, jeden Gegenstand von zu übergeben list_of_items. Ich habe so etwas versucht:

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

Ist es möglich, eine Liste von Python an JavaScript zu übergeben, oder sollte ich jedes Element einzeln in einer Schleife aus der Liste übergeben? Wie kann ich das machen?

user1843766
quelle

Antworten:

116

Um einige Kontextdaten an Javascript-Code zu übergeben, müssen Sie sie so serialisieren, dass sie von Javascript (nämlich JSON) "verstanden" werden. Sie müssen es auch mit dem safeJinja-Filter als sicher markieren , um zu verhindern, dass Ihre Daten htmlescaped werden.

Sie können dies erreichen, indem Sie so etwas tun:

Die Aussicht

import json

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

Die Vorlage

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

Bearbeiten - genaue Antwort

Um genau das zu erreichen, was Sie möchten (eine Liste von Elementen durchlaufen und an eine Javascript-Funktion übergeben), müssen Sie jedes Element in Ihrer Liste separat serialisieren. Ihr Code würde dann so aussehen:

Die Aussicht

import json

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

Die Vorlage

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

Bearbeiten 2

In meinem Beispiel, das ich verwende Flask, weiß ich nicht, welches Framework Sie verwenden, aber Sie haben die Idee, Sie müssen es nur an das von Ihnen verwendete Framework anpassen.

Bearbeiten 3 (Sicherheitswarnung)

Tun Sie dies niemals mit vom Benutzer bereitgestellten Daten, sondern nur mit vertrauenswürdigen Daten!

Andernfalls würden Sie Ihre Anwendung XSS-Schwachstellen aussetzen!

mdeous
quelle
9
Was besser sein könnte, ist nicht als JSON-String zu übergeben und {{item|tojson|safe}}stattdessen zu tun
Nick Garvey
2
-1; Dies ist im allgemeinen Fall kaputt und unsicher, und es gibt nur eine begrenzte Anzahl von Umständen, unter denen die Verwendung in Ordnung ist. Wenn eines der Elemente ein Leerzeichen enthält, wird die Ausgabe unterbrochen, und wenn eines davon vorhanden ist, werden " ><script>alert('pwned');</script> "Sie XSSed. Ja, Sie haben eine Sicherheitswarnung, aber dennoch ist dies eine schlechte Antwort. Es gibt saubere Wege, die nicht kaputt sind (wie die Verwendung des Flask- tojsonFilters), und diese sollten stattdessen verwendet werden.
Mark Amery
Genial, es war erforderlich, "{{data | safe}}" zu verwenden, um Daten aus Jinja-Variablen zu laden.
Ryan Chou
{{Daten | safe}} arbeitete für mich auf der Liste, wie es schien, ohne dass zuerst in json konvertiert werden musste.
Rbennell
27

Ich hatte ein ähnliches Problem mit Flask, musste aber nicht auf JSON zurückgreifen. Ich habe gerade eine Liste letters = ['a','b','c']mit übergeben render_template('show_entries.html', letters=letters)und festgelegt

var letters = {{ letters|safe }}

in meinem Javascript-Code. Jinja2 ersetzt {{ letters }}durch ['a','b','c'], das Javascript als Array von Strings interpretiert.

Gottschmied
quelle
5
-1; Dies funktioniert nicht für alle Werte und ist gefährlich, wenn der Inhalt vom Benutzer bereitgestellt wird. Wenn beispielsweise eine der Zeichenfolgen in Ihrer Liste vorhanden ist </script><script>alert('pwned')</script>, wird Ihre Seite beschädigt.
Mark Amery
8

Sie können dies mit Jinjas tojsonFilter tun , der

Gibt eine Struktur an JSON aus, sodass die Verwendung in <script>Tags [und] an jeder Stelle in HTML sicher ist, mit Ausnahme von Attributen in doppelten Anführungszeichen.

Schreiben Sie beispielsweise in Ihrem Python:

some_template.render(list_of_items=list_of_items)

... oder im Kontext eines Flask-Endpunkts:

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

Schreiben Sie dann in Ihre Vorlage Folgendes:

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

(Beachten Sie, dass das onclickAttribut ist Single -quoted. Dies da notwendig ist , |tojsonentkommt 'Zeichen aber nicht" Zeichen in seinem Ausgang, was bedeutet , dass es sicher in einfachen Anführungszeichen verwendet werden können HTML - Attribute , aber nicht in doppelten Anführungszeichen diejenigen.)

Oder list_of_itemsschreiben Sie Folgendes, um es in einem Inline-Skript anstelle eines HTML-Attributs zu verwenden:

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

Verwenden Sie diese Option NICHT json.dumps, um Variablen in Ihrem Python-Code mit JSON zu codieren und den resultierenden JSON-Text an Ihre Vorlage zu übergeben. Dies führt bei einigen Zeichenfolgenwerten zu einer falschen Ausgabe und setzt Sie XSS aus, wenn Sie versuchen, vom Benutzer bereitgestellte Werte zu codieren. Dies liegt daran, dass in Pythons integriertem json.dumpsZeichen keine Zeichen wie <und >(die maskiert werden müssen, um Werte sicher in Inlines zu speichern) <script>entfallen, wie unter https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for angegeben -Inhalte-von-Skript-Elementen ) oder einfache Anführungszeichen (die müssen, um Werte sicher in einfache HTML-Attribute mit einfachen Anführungszeichen zu verschieben).

Wenn Sie Flask verwenden, beachten Sie, dass Flask tojsonanstelle der Jinja-Version einen benutzerdefinierten Filter injiziert . Es gilt jedoch weiterhin alles oben Geschriebene. Die beiden Versionen verhalten sich fast identisch; Flask erlaubt nur einige app-spezifische Konfigurationen , die in Jinjas Version nicht verfügbar sind.

Mark Amery
quelle
1
Diese Antwort scheint besser zu sein als die ausgewählte Antwort.
Zheng Liu
1

Um die ausgewählte Antwort zusammenzufassen, habe ich eine neue Option getestet, die auch mit jinja2 und flask funktioniert:

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

Die Vorlage:

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

die Ausgabe der gerenderten Vorlage:

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

Das safekönnte hinzugefügt werden, aber auch gerne {{ data | tojson | safe }}HTML-Escape vermeiden, aber es funktioniert auch ohne.

Sylhare
quelle
0

Ich kann Ihnen einen Javascript-orientierten Ansatz vorschlagen, der es einfach macht, mit Javascript-Dateien in Ihrem Projekt zu arbeiten.

Erstellen Sie einen Javascript-Abschnitt in Ihrer Jinja-Vorlagendatei und platzieren Sie alle Variablen, die Sie in Ihren Javascript-Dateien verwenden möchten, in einem Fensterobjekt:

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 ersetzt Werte und unser appConfig-Objekt ist über unsere anderen Skriptdateien erreichbar:

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

Ich habe Javascript-Code auf diese Weise von HTML-Dokumenten getrennt, was einfacher zu verwalten und seofreundlich ist.

Muratgozel
quelle
-1, da dies, wie die meisten anderen Antworten hier, unterbrochen wird, wenn die Zeichenfolgenvariablen, in denen Sie Vorlagen erstellen, bestimmte Zeichen oder Teilzeichenfolgen enthalten (z. B. ein einfaches Anführungszeichen oder </script><script>alert("XSSed ur site!!1")</script>). Die Behauptung in Ihrem letzten Absatz, dass SEO davon beeinflusst wird, ob eine Website Inline-JavaScript oder externe Skripte verwendet, erscheint mir ebenfalls zweifelhaft.
Mark Amery
@ MarkAmery Danke für die Klarstellung. Es gibt zwei Methoden, die ich heute verwende, um Servervariablen im Browser abzurufen. Das erste unterscheidet sich ein wenig von der Antwort : <input type="hidden" name="server_data" value="DATA">Daten sind json-codiert und müssen natürlich maskiert werden. Und die zweite ist eine API-Anfrage. Wenn Ihre Daten vom Besucher abhängen, ist es am besten, sie durch eine API-Anfrage zu erhalten.
Muratgozel
0

Erstellen Sie einige unsichtbare HTML-Tags wie <label>, <p>, <input>usw. und benennen Sie die ID. Der Klassenname ist ein Muster, damit Sie ihn später abrufen können.

Sie haben zwei Listen "tenance_next [] "und" Maintenance_block_time [] "gleicher Länge, und Sie möchten die Daten dieser beiden Listen mithilfe des Kolbens an Javascript übergeben. Sie nehmen also ein unsichtbares Label-Tag und legen fest, dass der Tag-Name ein Muster des Listenindex ist, und legen den Klassennamen als Wert am Index fest.

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

Jetzt können Sie die Daten in Javascript mit einer Javascript-Operation wie unten abrufen -

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

Tanmoy Datta
quelle