Chiama una funzione Python da jinja2


144

Sto usando jinja2 e voglio chiamare una funzione python come aiuto, usando una sintassi simile come se stessi chiamando una macro. jinja2 sembra intenzionato a impedirmi di effettuare una chiamata di funzione e insiste sul fatto che mi ripeto copiando la funzione in un modello come macro.

C'è un modo semplice per farlo? E c'è un modo per importare un intero set di funzioni Python e renderle accessibili da jinja2, senza passare attraverso un sacco di rigamarole (come scrivere un'estensione)?

Risposte:


223

Per quelli che usano Flask, inseriscilo nel tuo __init__.py:

def clever_function():
    return u'HELLO'

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

e nel tuo modello chiamalo con {{ clever_function() }}


puoi passare più funzioni come questa?
ffghfgh,

6
Nelle versioni più recenti (sto usando Jinja2 2.9.6) sembra funzionare molto più facilmente. Usa la funzione come faresti con una variabile (funziona anche in situazioni più complesse):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
Anche 8 anni dopo, se stai usando Flask, questa sembra una soluzione più pulita rispetto a una delle risposte più recenti. E per rispondere alla vecchia domanda di @ffghfgh, sì, puoi passare più funzioni.
kevinmicke,

133

Nota: questo è specifico del pallone!

So che questo post è piuttosto vecchio, ma ci sono metodi migliori per farlo nelle nuove versioni di Flask usando processori di contesto.

Le variabili possono essere facilmente create:

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

Quanto sopra può essere utilizzato in un modello Jinja2 con Flask in questo modo:

{{ myexample }}

(Quali uscite This is an example)

Oltre alle funzioni complete:

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

Quanto sopra usato in questo modo:

{{ format_price(0.33) }}

(Che genera il prezzo di input con il simbolo della valuta)

In alternativa, puoi usare i filtri jinja , cotti in Flask. Ad esempio usando i decoratori:

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

Oppure, senza decoratori e registrando manualmente la funzione:

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

I filtri applicati con i due metodi precedenti possono essere utilizzati in questo modo:

{% for x in mylist | reverse %}
{% endfor %}

4
dove dovrebbero esistere queste funzioni, init, viste o semplicemente ovunque?
Knk

3
__init__.pysupponendo che tu abbia dichiarato flask.Flask(__name__)lì.
Liam Stanley,

6
Down ha votato come domanda su Jinja2 e la risposta è specifica di Flask.
AJP,

13
@AJP Ancora teoricamente risponde alla domanda. Questo è UN modo per risolvere il problema, purché tu stia utilizzando anche Flask. Un po 'come tutte le domande JavaScript spesso rispondono dando alternative con o senza jQuery o domande su Python spesso rispondono sia per Python2 che per 3. La domanda non escludeva Flask. (a differenza di una domanda su Py2 escluderebbe una risposta Py3). Questa risposta mi ha aiutato.
Jeromej,

2
Molto utile, e proprio quello che stavo cercando. Jinja2 fa parte di un framework Web e come tale non è totalmente indipendente dal back-end. Lavoro sia in Django che in Flask con Python e questo post, così come gli altri qui sono rilevanti per me. Cercare di specificare una domanda è secondo me dannoso quanto inutilmente vago.

76

Penso che jinja renda deliberatamente difficile eseguire un pitone "arbitrario" all'interno di un modello. Cerca di rinforzare l'opinione secondo cui meno logica nei template è una buona cosa.

È possibile manipolare lo spazio dei nomi globale all'interno di Environmentun'istanza per aggiungere riferimenti alle funzioni. Deve essere fatto prima di caricare qualsiasi modello. Per esempio:

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

5
Ho scoperto anche questo: puoi aggiungere un modulo usando qualcosa del genere: import utils.helpers env.globals['helpers'] = utils.helpers
Lee,

@Lee. Sì, puoi 'iniettare' spazi dei nomi (moduli), funzioni, istanze di classe ecc. È utile, ma non flessibile come altri motori di template come Mako. Tuttavia, jinja ha altri punti positivi. Le sarei grato se accettassi la risposta se fosse d'aiuto :)
Rob Cowie,

ha fatto il trucco per me mentre facevo il mio progetto di motore di app (webapp2 e jinja2). grazie
Sojan V Jose

@RobCowie dopo aver aggiunto la funzione clever al dizionario env.globals, come si può chiamare la funzione dal modello.
Jorge Vidinha,

1
Quindi, {{ clever_function('a', 'b') }}
Rob Cowie il

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

Puoi anche dare la funzione nei campi secondo la risposta di Matroskin

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

Uscita:

Hey, my name is Jay Kay

Funziona con Jinja2 versione 2.7.3

E se vuoi che un decoratore faciliti la definizione delle funzioni, template.globalscontrolla la risposta di Bruno Bronosky


8
Probabilmente perché hai ridimensionato le risposte di tutti gli altri :(
Borko Kovacev,

13
@BorkoKovacev non è una buona ragione. Ho votato solo 2 risposte; risposte che riguardavano Flask piuttosto che Jinja2. Se vogliono modificare le loro risposte per essere in tema e su Jinja2, le voterò.
AJP,

Tx @BorkoKovacev :)
AJP,

1
Ho creato una versione per decoratore di funzioni di questa risposta. E 'attualmente in basso con 0 voti:, - ( stackoverflow.com/a/47291097/117471
Bruno Bronosky

2
@BrunoBronosky nice. Ho votato :) :) Dagli un altro decennio e potrebbe essere più alto del mio: P ... non prenderà mai le boccette; P
AJP

25

Mi piace la risposta di @ AJP . L'ho usato alla lettera fino a quando non ho finito con molte funzioni. Poi sono passato a un decoratore di funzioni Python .

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

Le cose buone hanno un __name__!


1
Questo è follemente bello. Quando annoti una funzione in Python, passa automaticamente il nome della funzione alla funzione dell'annotazione?
mutant_city,

@mutant_city, sì. Leggi quel link al decoratore della funzione Python. Roba fantastica!
Bruno Bronosky,

1
@BrunoBronosky Ottima dimostrazione di un uso sensato e pulito anche per i decoratori di pitone. Ottimo post!
dreftymac,

1
Che grande implementazione!
Philippe Oger,

16

Non ho mai visto un modo così semplice in documenti ufficiali o in overflow dello stack, ma sono rimasto sorpreso quando ho trovato questo:

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

Questa risposta è di gran lunga il migliore imho. Basta passare la funzione al modello esattamente nello stesso modo in cui si passa un valore, dopo che tutte le funzioni sono cittadini di prima classe in Python :)
Mark Kortink,

8

Usa un lambda per connettere il modello al tuo codice principale

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

Quindi puoi chiamare facilmente la funzione nel modello

{{clever_function(value)}}

1
Uso intelligente delle funzioni lambda.

23
@odiumediae: No non lo è. È completamente inutile. Basta passare la funzione stessa: clever_function = clever_function
vezult

@vezult vedo. Come potrei mancarlo? Grazie per averlo chiarito!

6

Per chiamare una funzione Python da Jinja2, puoi usare filtri personalizzati che funzionano in modo simile ai globali: http://jinja.pocoo.org/docs/dev/api/#writing-filters

È abbastanza semplice e utile. In un file myTemplate.txt, ho scritto:

{{ data|pythonFct }}

E in uno script Python:

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)

5

c'è un modo per importare un intero set di funzioni Python e renderle accessibili da jinja2?

Sì, oltre alle altre risposte precedenti, questo funziona per me.

Crea una classe e popolala con i metodi associati, ad es

class Test_jinja_object:

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

    def clever_function (self):
        return 'hello' 

Quindi crea un'istanza della tua classe nella tua funzione di visualizzazione e passa l'oggetto risultante al tuo modello come parametro per la funzione render_template

my_obj = Test_jinja_object()

Ora nel tuo modello, puoi chiamare i metodi di classe in jinja in questo modo

{{ my_obj.clever_function () }}

Modo equivalente e leggermente più semplice: inserisci tutte le funzioni per i modelli in un modulo, importa quel modulo e aggiungilo come modello globale. Un modulo è un oggetto che contiene funzioni :) (ma non metodi - nessuna necessità di auto-param e nessuna classe richiesta!)
Éric Araujo

@ ÉricAraujo E se avessi bisogno solo dell'insieme di funzioni in uno o due modelli e non in tutti. Inoltre, se avessi bisogno di diversi set di funzioni Python in diversi modelli di jinjas? Considereresti comunque efficace importarli tutti come template globali anziché metterli come metodi in una classe e passare i corsi solo con i metodi che ti servono.
Kudehinbu Oluwaponle,

Per utilizzare solo in modelli specifici, aggiungerei le funzioni (o un modulo contenente funzioni) solo al dict di contesto del modello ripreso dalle visualizzazioni che utilizzano tali modelli.
Éric Araujo,

3

Per importare tutte le funzioni integrate è possibile utilizzare:

app.jinja_env.globals.update(__builtins__)

Aggiungi .__dict__dopo __builtins__se questo non funziona.

Basato su risposta di John32323 .


2

Se lo stai facendo con Django, puoi semplicemente passare la funzione con il contesto:

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

Ora sarai in grado di utilizzare la strfunzione nel modello jinja2


1

C'è una decisione molto più semplice.

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

def y(text):
    return text

Quindi, in test.html :

{{ y('hi') }}

jinja2.exceptions.UndefinedError: 'y' non è definito
lww

Sì, perché dovrei usare foo in test.html
luckyguy73
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.