Wywołaj funkcję Pythona z jinja2

144

Używam jinja2 i chcę wywołać funkcję Pythona jako pomocnika, używając podobnej składni, jak gdybym wywoływał makro. jinja2 wydaje się mieć zamiar uniemożliwić mi wywołanie funkcji i nalega, abym się powtarzał, kopiując funkcję do szablonu jako makro.

Czy jest na to prosty sposób? I czy istnieje sposób na zaimportowanie całego zestawu funkcji Pythona i udostępnienie ich z jinja2 bez przechodzenia przez całą masę rygorolek (takich jak pisanie rozszerzenia)?

Zawietrzny
źródło

Odpowiedzi:

223

Dla tych, którzy używają Flaska, umieść to w swoim __init__.py :

def clever_function():
    return u'HELLO'

app.jinja_env.globals.update(clever_function=clever_function)

iw swoim szablonie nazwij to za pomocą {{ clever_function() }}

John32323
źródło
czy możesz przekazać wiele takich funkcji?
ffghfgh
6
W nowszych wersjach (używam Jinja2 2.9.6) wydaje się, że działa znacznie łatwiej. Używaj funkcji tak, jakbyś używał zmiennej (działa również w bardziej złożonych sytuacjach):from jinja2 import Template ##newline## def clever_function(): ##newline## return "Hello" ##newline## template = Template("{{ clever_function() }}") ##newline## print(template.render(clever_function=clever_function))
Semjon Mössinger
1
Nawet 8 lat później, jeśli używasz Flaska, wydaje się to czystszym rozwiązaniem niż którakolwiek z nowszych odpowiedzi. Odpowiadając na stare pytanie z @ffghfgh, tak - możesz przekazać wiele funkcji.
kevinmicke
133

Uwaga: to jest specyficzne dla Flask!

Wiem, że ten post jest dość stary, ale w nowszych wersjach Flaska są lepsze metody na zrobienie tego przy użyciu procesorów kontekstu.

Zmienne można łatwo tworzyć:

@app.context_processor
def example():
    return dict(myexample='This is an example')

Powyższe może być użyte w szablonie Jinja2 z Flask w następujący sposób:

{{ myexample }}

(Które wyjścia This is an example )

Oprócz pełnoprawnych funkcji:

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

Powyższe użyte w ten sposób:

{{ format_price(0.33) }}

(Który podaje cenę wejściową z symbolem waluty)

Alternatywnie możesz użyć filtrów jinja wypieczonych w Flasku. Np. Przy użyciu dekoratorów:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

Lub bez dekoratorów i ręcznie rejestrując funkcję:

def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

Filtry zastosowane dwiema powyższymi metodami mogą być używane w następujący sposób:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
źródło
4
gdzie powinny istnieć te funkcje, init, views czy gdziekolwiek?
knk
3
__init__.pyzakładając, że flask.Flask(__name__)tam zadeklarowałeś .
Liam Stanley
6
Down głosował jako pytanie o Jinja2, a odpowiedź jest specyficzna dla Flask.
AJP,
13
@AJP Wciąż teoretycznie odpowiada na pytanie. To JEDEN sposób rozwiązania problemu, zakładając, że używasz również Flaski. Trochę tak, jak wszystkie pytania dotyczące JavaScript, często odpowiadają, podając alternatywy z lub bez jQuery, lub pytania dotyczące Pythona często odpowiadają zarówno dla Pythona2, jak i 3. Pytanie nie wykluczało Flaska. (w przeciwieństwie do pytania o Py2 wykluczałoby odpowiedź Py3). Ta odpowiedź mi pomogła.
jeromej
2
Bardzo pomocny i właśnie to, czego szukałem. Jinja2 jest częścią frameworka internetowego i jako taki nie jest całkowicie niezależny od zaplecza. Pracuję zarówno w Django, jak i Flasku z Pythonem i ten post, a także inne tutaj, są dla mnie istotne. Próba zawyżenia pytania jest moim zdaniem równie szkodliwa, jak niepotrzebnie niejasna.
76

Myślę, że jinja celowo utrudnia uruchomienie „dowolnego” Pythona w szablonie. Próbuje narzucić opinię, że mniej logiki w szablonach to dobra rzecz.

Możesz manipulować globalną przestrzenią nazw w Environmentinstancji, aby dodawać odwołania do funkcji. Należy to zrobić przed załadowaniem jakichkolwiek szablonów. Na przykład:

from jinja2 import Environment, FileSystemLoader

def clever_function(a, b):
    return u''.join([b, a])

env = Environment(loader=FileSystemLoader('/path/to/templates'))
env.globals['clever_function'] = clever_function
Rob Cowie
źródło
5
Odkryłem to również - możesz dodać moduł używając czegoś takiego: import utils.helpers env.globals['helpers'] = utils.helpers
Lee
@Zawietrzny. Tak, możesz „wstrzykiwać” przestrzenie nazw (moduły), funkcje, instancje klas itp. Jest to przydatne, ale nie tak elastyczne, jak inne silniki szablonów, takie jak mako. Jednak jinja ma inne dobre strony. Byłbym wdzięczny, gdybyś zaakceptował odpowiedź, gdyby to pomogło :)
Rob Cowie
załatwił mi sprawę podczas robienia mojego projektu silnika aplikacji (webapp2 i jinja2). dzięki
Sojan V Jose
@RobCowie po dodaniu clever_function do słownika env.globals, jak można wywołać funkcję z szablonu.
Jorge Vidinha
1
Tak więc {{ clever_function('a', 'b') }}
Rob Cowie,
41
from jinja2 import Template

def custom_function(a):
    return a.replace('o', 'ay')

template = Template('Hey, my name is {{ custom_function(first_name) }} {{ func2(last_name) }}')
template.globals['custom_function'] = custom_function

Możesz również podać funkcję w polach zgodnie z odpowiedzią Matroskina

fields = {'first_name': 'Jo', 'last_name': 'Ko', 'func2': custom_function}
print template.render(**fields)

Wyświetli:

Hey, my name is Jay Kay

Działa z Jinja2 w wersji 2.7.3

A jeśli chcesz, aby dekorator ułatwił definiowanie funkcji, template.globalszapoznaj się z odpowiedzią Bruno Bronosky'ego

AJP
źródło
8
Prawdopodobnie dlatego, że zlekceważyłeś odpowiedzi wszystkich innych :(
Borko Kovacev,
13
@BorkoKovacev to nie jest dobry powód. Tylko w dół zagłosowałem 2 odpowiedzi; odpowiedzi, które dotyczyły raczej Flaska niż Jinja2. Jeśli chcą edytować swoje odpowiedzi na temat i na temat Jinja2, zagłosuję na nich.
AJP
Tx @BorkoKovacev :)
AJP
1
Zrobiłem wersję dekoratora funkcji tej odpowiedzi. Obecnie znajduje się na dole z 0 głosami
:,
2
@BrunoBronosky miło. Głosowałem wyżej :) ... daj mu kolejną dekadę, a może być wyższa niż moja: P ... jednak nigdy nie złapie kolb; P
AJP
25

Podoba mi się odpowiedź @ AJP . Używałem go dosłownie, aż skończyło się na wielu funkcjach. Następnie przełączyłem się na dekorator funkcji Pythona .

from jinja2 import Template

template = '''
Hi, my name is {{ custom_function1(first_name) }}
My name is {{ custom_function2(first_name) }}
My name is {{ custom_function3(first_name) }}
'''
jinga_html_template = Template(template)

def template_function(func):
    jinga_html_template.globals[func.__name__] = func
    return func

@template_function
def custom_function1(a):
    return a.replace('o', 'ay')

@template_function
def custom_function2(a):
    return a.replace('o', 'ill')

@template_function
def custom_function3(a):
    return 'Slim Shady'

fields = {'first_name': 'Jo'}
print(jinga_html_template.render(**fields))

Dobre funkcje mają __name__!

Bruno Bronosky
źródło
1
To jest niesamowicie fajne. Kiedy dodajesz adnotację do funkcji w Pythonie, czy automatycznie przekazuje ona nazwę funkcji do funkcji adnotacji?
mutant_city,
@mutant_city, tak. Przeczytaj ten link dekoratora funkcji Pythona. Świetne rzeczy!
Bruno Bronosky
1
@BrunoBronosky Doskonała demonstracja rozsądnego i czystego użycia również dla dekoratorów Pythona. Wspaniały post!
dreftymac
1
Co za wspaniała realizacja!
Philippe Oger
16

Nigdy nie widziałem tak prostego sposobu w oficjalnych dokumentach ani przy przepełnieniu stosu, ale byłem zdumiony, gdy znalazłem to:

# jinja2.__version__ == 2.8
from jinja2 import Template

def calcName(n, i):
    return ' '.join([n] * i)

template = Template("Hello {{ calcName('Gandalf', 2) }}")

template.render(calcName=calcName)
# or
template.render({'calcName': calcName})
Matroskin
źródło
Ta odpowiedź jest zdecydowanie najlepsza imho. Po prostu przekazujesz funkcję do szablonu dokładnie w ten sam sposób, w jaki przekazujesz wartość, w końcu wszystkie funkcje są obywatelami pierwszej klasy w Pythonie :)
Mark Kortink
8

Użyj lambdy, aby połączyć szablon z głównym kodem

return render_template("clever_template", clever_function=lambda x: clever_function x)

Następnie możesz bezproblemowo wywołać funkcję w szablonie

{{clever_function(value)}}
Robert Onslow
źródło
1
Sprytne wykorzystanie funkcji lambda.
23
@odiumediae: Nie, nie jest. To zupełnie niepotrzebne. Po prostu przekaż sam uchwyt funkcji: clever_function = clever_function
vezult
@vezult widzę. Jak mogłem tego przegapić? Dzięki za wyjaśnienie tego!
6

Aby wywołać funkcję Pythona z Jinja2, możesz użyć niestandardowych filtrów, które działają podobnie jak globalne: http://jinja.pocoo.org/docs/dev/api/#writing-filters

To całkiem proste i przydatne. W pliku myTemplate.txt napisałem:

{{ data|pythonFct }}

A w skrypcie Pythona:

import jinja2

def pythonFct(data):
    return "This is my data: {0}".format(data)

input="my custom filter works!"

loader = jinja2.FileSystemLoader(path or './')
env = jinja2.Environment(loader=loader)
env.filters['pythonFct'] = pythonFct
result = env.get_template("myTemplate.txt").render(data=input)
print(result)
Ben9000RPM
źródło
5

czy istnieje sposób na zaimportowanie całego zestawu funkcji Pythona i udostępnienie ich w jinja2?

Tak, oprócz innych odpowiedzi powyżej, to działa dla mnie.

Utwórz klasę i wypełnij ją powiązanymi metodami, np

class Test_jinja_object:

    def __init__(self):
        self.myvar = 'sample_var'

    def clever_function (self):
        return 'hello' 

Następnie utwórz instancję swojej klasy w funkcji widoku i przekaż wynikowy obiekt do szablonu jako parametr funkcji render_template

my_obj = Test_jinja_object()

Teraz w swoim szablonie możesz wywoływać metody klas w jinja w ten sposób

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
źródło
Równoważny i nieco prostszy sposób: umieść wszystkie funkcje szablonów w module, zaimportuj ten moduł i dodaj go jako szablon globalny. Moduł to obiekt, który zawiera funkcje :) (ale nie metody - nie ma potrzeby stosowania własnego parametru i nie jest wymagana klasa!)
Éric Araujo
@ ÉricAraujo A co, jeśli potrzebuję tylko zestawu funkcji w jednym lub dwóch szablonach, a nie we wszystkich. A co jeśli potrzebuję różnych zestawów funkcji Pythona w różnych szablonach Jinjas? Czy nadal uważałbyś za efektywne importowanie ich wszystkich jako szablonów globalnych, zamiast umieszczać je jako metody w klasie i przekazywać klasy tylko z metodami, których potrzebujesz.
Kudehinbu Oluwaponle
Aby używać tylko w określonych szablonach, dodałbym funkcje (lub moduł zawierający funkcje) tylko do dyktu kontekstowego szablonu, który jest powracany przez widoki korzystające z tych szablonów.
Éric Araujo
3

Aby zaimportować wszystkie wbudowane funkcje, których możesz użyć:

app.jinja_env.globals.update(__builtins__)

Dodaj .__dict__po__builtins__ jeśli to nie zadziała.

Na podstawie odpowiedzi John32323 .

Solomon Ucko
źródło
2

Jeśli robisz to z Django, możesz po prostu przekazać funkcję z kontekstem:

context = {
    'title':'My title',
    'str': str,
}
...
return render(request, 'index.html', context)

Teraz będziesz mógł korzystać z strfunkcji w szablonie jinja2

Jahid
źródło
1

Jest o wiele prostsza decyzja.

@app.route('/x')
def x():
    return render_template('test.html', foo=y)

def y(text):
    return text

Następnie w pliku test.html :

{{ y('hi') }}
Никита Шишкин
źródło
jinja2.exceptions.UndefinedError: 'y' is undefined
lww
tak, bo powinienem używać foo w test.html
luckyguy73