Zmienne szablonu Django i JavaScript

219

Kiedy renderuję stronę przy użyciu renderera szablonów Django, mogę przekazać zmienną słownikową zawierającą różne wartości, aby manipulować nimi na stronie przy użyciu {{ myVar }}.

Czy istnieje sposób na dostęp do tej samej zmiennej w Javascript (być może przy użyciu DOM, nie wiem, jak Django udostępnia zmienne)? Chcę mieć możliwość wyszukiwania szczegółów przy użyciu wyszukiwania AJAX na podstawie wartości zawartych w przekazywanych zmiennych.

AlMcLean
źródło

Odpowiedzi:

291

{{variable}}Podstawiony bezpośrednio do HTML. Zrób źródło widoku; nie jest to „zmienna” ani nic podobnego. To tylko renderowany tekst.

Powiedziawszy to, możesz umieścić tego rodzaju podstawienie w swoim JavaScript.

<script type="text/javascript"> 
   var a = "{{someDjangoVariable}}";
</script>

To daje ci „dynamiczny” javascript.

S.Lott
źródło
29
Należy jednak pamiętać, że zgodnie z tym rozwiązaniem jest to podatne na ataki iniekcyjne
Casebash
13
@Casebash: Na takie okazje escapejsistnieje filtr:escapejs('<>') -> u'\\u003C\\u003E'
Tomasz Zieliński
24
Aby dodać do tego odniesienie: jeśli „someDjangoVariable” tak się składa, że ​​jest JSON, pamiętaj, aby użyć {{someDjangoVariable | safe}}, aby usunąć & quot;
Mark
4
Ta odpowiedź działa tylko dla prostej zmiennej, nie działa dla złożonej struktury danych. W tym przypadku najprostszym rozwiązaniem jest dodanie kodu po stronie klienta, aby przejść przez strukturę danych i zbudować podobny w JavaScript. Jeśli złożona struktura danych ma format JSON, innym rozwiązaniem jest jej serializacja, przekazanie serializowanego JSON do szablonu Django w kodzie po stronie serwera i deserializacja JSON w obiekcie javascript w kodzie po stronie klienta. Jedna odpowiedź poniżej wymienia tę alternatywę.
Alan Evangelista
14
co jeśli skrypt javascript jest zapisany w innym pliku?
the_unknown_spirit
64

UWAGA Sprawdź bilet nr 17419 w celu omówienia dodawania podobnego znacznika do rdzenia Django i możliwych luk w zabezpieczeniach XSS wprowadzonych przy użyciu tego znacznika szablonu z danymi generowanymi przez użytkownika. Komentarz od amacneil omawia większość obaw zgłoszonych na bilecie.


Myślę, że najbardziej elastycznym i poręcznym sposobem na to jest zdefiniowanie filtru szablonu dla zmiennych, których chcesz używać w kodzie JS. Dzięki temu masz pewność, że twoje dane są poprawnie ucieczkowe i możesz ich używać ze złożonymi strukturami danych, takimi jak dicti list. Dlatego piszę tę odpowiedź, mimo że istnieje akceptowana odpowiedź z dużą liczbą głosów pozytywnych.

Oto przykład filtru szablonu:

// myapp/templatetags/js.py

from django.utils.safestring import mark_safe
from django.template import Library

import json


register = Library()


@register.filter(is_safe=True)
def js(obj):
    return mark_safe(json.dumps(obj))

Ten szablon filtrów konwertuje zmienną na ciąg JSON. Możesz go używać w następujący sposób:

// myapp/templates/example.html

{% load js %}

<script type="text/javascript">
    var someVar = {{ some_var | js }};
</script>
Yaroslav Admin
źródło
3
Jest to miłe, ponieważ pozwala kopiować tylko niektóre zmienne wejściowe szablonu Django do Javascript, a kod po stronie serwera nie musi wiedzieć, które struktury danych muszą być używane przez Javascript, a zatem konwertowane na JSON przed renderowaniem szablonu Django. Użyj tego albo zawsze skopiuj wszystkie zmienne Django do Javascript.
Alan Evangelista
Ale uwaga: stackoverflow.com/questions/23752156/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@JorgeOrpinel Nie, to nie jest to samo. safeoznacza tylko wartość jako bezpieczną, bez odpowiedniej konwersji i ucieczki.
Jarosław Admin
2
Jak wyświetlić zmienną w szablonie django?
kbdev
1
@Sandi z powrotem, kiedy go opublikowałem, często widżet znajdował się w osobnym pliku JS i inicjalizowany był w kodzie źródłowym strony. Powiedzmy, że deklarujesz function myWidget(config) { /* implementation */ }w pliku JS i używasz go na niektórych stronach za pomocąmyWidget({{ pythonConfig | js }}) . Ale nie można go używać w plikach JS (jak zauważyłeś), więc ma swoje ograniczenia.
Jarosław Admin
51

Rozwiązaniem, które mi pomogło, jest użycie ukrytego pola wejściowego w szablonie

<input type="hidden" id="myVar" name="variable" value="{{ variable }}">

Następnie otrzymuję wartość w javascript w ten sposób,

var myVar = document.getElementById("myVar").value;
bosco-
źródło
3
bądź ostrożny. w zależności od tego, jak korzystasz ze zmiennej / formularza, użytkownik może wstawić co tylko zechce.
AndyL
3
możesz także ustawić pole wejściowe na readonly (zobacz ten link w3schools.com/tags/att_input_readonly.asp )
everest,
Jeśli coś nie zmieni bazy danych lub nie zostanie wysłane do zapytania do bazy danych, byłoby dobrze. @AndyL
James111
3
Chłopaki ... użytkownicy i tak mogą robić, co chcą. Przeglądarki sprawiają, że jest to takie proste dzięki w pełni funkcjonalnemu inspektorowi DOM i narzędziom do debugowania. Morał tej historii: wykonaj WSZYSTKIE sprawdzanie poprawności danych na serwerze .
user2867288
1
Czy ktoś może mi powiedzieć, jak uzyskasz dostęp do tej zmiennej w zewnętrznym pliku JavaScript?
Ahtisham
27

Jak Django 2.1, nowy zbudowany w szablon został wprowadzony specjalnie dla tego przypadku użycia: json_script.

https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#json-script

Nowy tag bezpiecznie serializuje wartości szablonu i chroni przed XSS.

Fragment dokumentu Django:

Bezpiecznie wyświetla obiekt Python jako JSON, zawinięty w tag, gotowy do użycia z JavaScript.

Jon Sakas
źródło
10

Oto, co robię bardzo łatwo: zmodyfikowałem plik base.html dla mojego szablonu i umieściłem go na dole:

{% if DJdata %}
    <script type="text/javascript">
        (function () {window.DJdata = {{DJdata|safe}};})();
    </script>
{% endif %}

następnie, gdy chcę użyć zmiennej w plikach javascript, tworzę słownik DJdata i dodaje go do kontekstu za pomocą json: context['DJdata'] = json.dumps(DJdata)

Mam nadzieję, że to pomoże!

Insomniak
źródło
Nie używaj tego, jest podatny na wstrzykiwanie skryptów (XSS).
pcworld,
8

W przypadku słownika najlepiej jest najpierw zakodować JSON. Możesz użyć simplejson.dumps () lub jeśli chcesz przekonwertować z modelu danych w App Engine, możesz użyć encode () z biblioteki GQLEncoder.

jamtoday
źródło
1
Zauważ, że „simplejson” stał się „json” od django 1.7, jak sądzę.
fiveclubs
4

Miałem do czynienia z podobnym problemem i odpowiedź zaproponowana przez S.Lott działała dla mnie.

<script type="text/javascript"> 
   var a = "{{someDjangoVariable}}"
</script>

Chciałbym jednak tutaj zwrócić uwagę na poważne ograniczenie wdrażania . Jeśli planujesz umieścić kod JavaScript w innym pliku i dołączyć ten plik do szablonu. To nie zadziała.

Działa to tylko wtedy, gdy główny szablon i kod javascript znajdują się w tym samym pliku. Zespół django prawdopodobnie może rozwiązać to ograniczenie.

Konkwistador
źródło
1
Jak możemy to przezwyciężyć?
Csaba Toth,
2
Aby temu zaradzić, możesz umieścić globalne zmienne JavaScript, tak jak w przykładzie pokazanym tuż przed dołączeniem statycznego pliku JavaScript. Twój statyczny plik JavaScript będzie miał dostęp do wszystkich globalnych zmiennych w ten sposób.
Bobort
Nie używaj tego, jest podatny na wstrzykiwanie skryptów (XSS).
pcworld,
Mogą zatwierdzać dane po stronie serwera.
Muhammad Faizan Fareed
4

Walczyłem też z tym. Na pierwszy rzut oka wydaje się, że powyższe rozwiązania powinny działać. Jednak architektura django wymaga, aby każdy plik html miał własne renderowane zmienne (to znaczy {{contact}}jest renderowane contact.html, podczas gdy {{posts}}przechodzi do np. index.htmlItd.). Z drugiej strony <script>tagi pojawiają się po {%endblock%}in, base.htmlz którego pochodzą contact.htmli index.htmldziedziczą. Zasadniczo oznacza to, że każde rozwiązanie, w tym

<script type="text/javascript">
    var myVar = "{{ myVar }}"
</script>

musi się nie powieść, ponieważ zmienna i skrypt nie mogą współistnieć w tym samym pliku.

Najprostszym rozwiązaniem, które w końcu wymyśliłem i działało dla mnie, było po prostu owinięcie zmiennej znacznikiem o id, a następnie odwołanie się do niej w pliku js, w ten sposób:

// index.html
<div id="myvar">{{ myVar }}</div>

i wtedy:

// somecode.js
var someVar = document.getElementById("myvar").innerHTML;

i tylko to <script src="static/js/somecode.js"></script>się base.htmljak zwykle. Oczywiście chodzi tu tylko o uzyskanie treści. Jeśli chodzi o bezpieczeństwo, wystarczy postępować zgodnie z innymi odpowiedziami.

Shoval Sadde
źródło
Prawdopodobnie chcesz użyć .textContentzamiast .innerHTML, ponieważ w przeciwnym razie jednostki, które zostaną zakodowane w HTML, również będą częścią zmiennej JS. Ale nawet wtedy może nie zostać odtworzone 1: 1 (nie jestem pewien).
pcworld,
Wyjaśnienie . Używam podobnego sposobu do przechwytywania wartości pól w zmiennych (przy użyciu identyfikatorów tworzonych dynamicznie w formularzu) i działa (ale tylko w JEDNYM wierszu zestawu formularzy ). Nie jestem w stanie się obejść, to przechwytywanie wartości ze wszystkich wierszy pól zestawu formularzy , które są wypełniane ręcznie (tj. W tabeli HTML za pomocą pętli for ). Podczas wizualizacji zmienne tylko ostatniego wiersza zestawu formularzy są przekazywane do zmiennych, a wcześniejsze wartości są zastępowane ostatnimi wartościami, gdy pętla for przechodzi przez wiersze zestawu formularzy. Czy jest na to jakiś sposób?
user12379095
3

W przypadku obiektu JavaScript przechowywanego w polu Django jako tekst, który musi ponownie stać się obiektem JavaScript dynamicznie wstawianym do skryptu na stronie, musisz użyć zarówno escapejsi JSON.parse():

var CropOpts = JSON.parse("{{ profile.last_crop_coords|escapejs }}");

Django escapejspoprawnie obsługuje cytowanie i JSON.parse()konwertuje ciąg znaków z powrotem na obiekt JS.

shacker
źródło
2

Zauważ, że jeśli chcesz przekazać zmienną do zewnętrznego skryptu .js, musisz poprzedzić tag skryptu innym tagiem skryptu, który deklaruje zmienną globalną.

<script type="text/javascript">
    var myVar = "{{ myVar }}"
</script>

<script type="text/javascript" src="{% static "scripts/my_script.js" %}"></script>

data jest zdefiniowany w widoku, jak zwykle w get_context_data

def get_context_data(self, *args, **kwargs):
    context['myVar'] = True
    return context
Daniel Kislyuk
źródło
Część do zadeklarowania zmiennej globalnie była naprawdę pomocna.
Rahul
jeśli renderujesz dane na zmienne js z szablonów takich jak powyżej, musisz renderować na tę samą stronę (uczynić je globalnymi), a inny kod przechodzi do osobnego pliku js. Po stronie serwera muszą być poprawne dane, ponieważ użytkownik może zmienić z przeglądarki.
Muhammad Faizan Fareed
1

Używam tego w Django 2.1 i pracuję dla siebie, a ten sposób jest bezpieczny (odniesienie) :

Strona Django:

def age(request):
    mydata = {'age':12}
    return render(request, 'test.html', context={"mydata_json": json.dumps(mydata)})

Strona HTML:

<script type='text/javascript'>
     const  mydata = {{ mydata_json|safe }};
console.log(mydata)
 </script>
Henry
źródło
0

możesz złożyć cały skrypt, w którym zmienna tablicowa jest zadeklarowana w ciągu, w następujący sposób:

views.py

    aaa = [41, 56, 25, 48, 72, 34, 12]
    prueba = "<script>var data2 =["
    for a in aaa:
        aa = str(a)
        prueba = prueba + "'" + aa + "',"
    prueba = prueba + "];</script>"

który wygeneruje ciąg w następujący sposób

prueba = "<script>var data2 =['41','56','25','48','72','34','12'];</script>"

po otrzymaniu tego ciągu musisz wysłać go do szablonu

views.py

return render(request, 'example.html', {"prueba": prueba})

w szablonie otrzymujesz go i interpretujesz w literacki sposób jako kod HTML, tuż przed kodem JavaScript, gdzie go potrzebujesz, na przykład

szablon

{{ prueba|safe  }}

a poniżej znajduje się reszta kodu, pamiętaj, że zmienną, która ma być użyta w tym przykładzie, jest data2

<script>
 console.log(data2);
</script>

w ten sposób zachowasz rodzaj danych, co w tym przypadku jest ustaleniem

Daniel Muñoz
źródło
Użyj tego tylko wtedy, gdy masz pewność, że aaabędą zawierać tylko liczby, w przeciwnym razie możliwe jest XSS (wstrzyknięcie skryptu).
pcworld,
0

Istnieją dwie rzeczy, które działały dla mnie wewnątrz Javascript:

'{{context_variable|escapejs }}'

i inne: w views.py

from json import dumps as jdumps

def func(request):
    context={'message': jdumps('hello there')}
    return render(request,'index.html',context)

i w html:

{{ message|safe }}
MbeforeL
źródło