Appeler une fonction python depuis jinja2

144

J'utilise jinja2, et je veux appeler une fonction python comme aide, en utilisant une syntaxe similaire comme si j'appelais une macro. jinja2 semble vouloir m'empêcher de faire un appel de fonction, et insiste pour que je me répète en copiant la fonction dans un modèle sous forme de macro.

Existe-t-il un moyen simple de le faire? Et, y a-t-il un moyen d'importer tout un ensemble de fonctions python et de les rendre accessibles à partir de jinja2, sans passer par beaucoup de rigamarole (comme l'écriture d'une extension)?

Lee
la source

Réponses:

223

Pour ceux qui utilisent Flask, mettez ceci dans votre __init__.py:

def clever_function():
    return u'HELLO'

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

et dans votre modèle, appelez-le avec {{ clever_function() }}

John32323
la source
pouvez-vous passer plusieurs fonctions comme celle-ci?
ffghfgh
6
Dans les versions plus récentes (j'utilise Jinja2 2.9.6), cela semble fonctionner beaucoup plus facilement. Utilisez la fonction comme vous utiliseriez une variable (fonctionne également dans des situations plus complexes):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
Même 8 ans plus tard, si vous utilisez Flask, cela semble être une solution plus propre que toutes les réponses les plus récentes. Et pour répondre à la vieille question de @ffghfgh, oui, vous pouvez passer plusieurs fonctions.
kevinmicke
133

Remarque: ceci est spécifique à Flask!

Je sais que cet article est assez ancien, mais il existe de meilleures méthodes pour le faire dans les nouvelles versions de Flask utilisant des processeurs de contexte.

Les variables peuvent être facilement créées:

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

Ce qui précède peut être utilisé dans un modèle Jinja2 avec Flask comme ceci:

{{ myexample }}

(Quelles sorties This is an example)

Ainsi que des fonctions à part entière:

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

Ce qui précède lorsqu'il est utilisé comme ceci:

{{ format_price(0.33) }}

(Qui sort le prix d'entrée avec le symbole monétaire)

Vous pouvez également utiliser des filtres jinja , cuits dans Flask. Par exemple en utilisant des décorateurs:

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

Ou, sans décorateurs, et en enregistrant manuellement la fonction:

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

Les filtres appliqués avec les deux méthodes ci-dessus peuvent être utilisés comme ceci:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
la source
4
où devraient exister ces fonctions, init, vues ou n'importe où?
knk
3
__init__.pyen supposant que vous flask.Flask(__name__)y ayez déclaré .
Liam Stanley
6
Down a voté comme question posée à propos de Jinja2 et la réponse est spécifique à Flask.
AJP
13
@AJP répond encore théoriquement à la question. C'est UN moyen de résoudre le problème, à condition que vous utilisiez également Flask. Un peu comme toutes les questions JavaScript répondent souvent à des alternatives avec ou sans jQuery ou des questions sur Python répondent souvent à la fois pour Python2 et 3. La question n'exclut pas Flask. (contrairement à une question sur Py2, elle exclurait une réponse Py3). Cette réponse m'a aidé.
jeromej
2
Très utile et exactement ce que je recherchais. Jinja2 fait partie d'un framework web, et en tant que tel n'est pas totalement indépendant du back-end. Je travaille à la fois dans Django et Flask avec Python et ce post, ainsi que les autres ici me concernent. Essayer de trop spécifier une question est à mon avis aussi préjudiciable qu'être inutilement vague.
76

Je pense que jinja rend délibérément difficile l'exécution de python «arbitraire» dans un modèle. Il essaie de renforcer l'opinion selon laquelle moins de logique dans les modèles est une bonne chose.

Vous pouvez manipuler l'espace de noms global dans une Environmentinstance pour ajouter des références à vos fonctions. Cela doit être fait avant de charger des modèles. Par exemple:

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
la source
5
J'ai découvert cela aussi - vous pouvez ajouter un module en utilisant quelque chose comme ceci: import utils.helpers env.globals['helpers'] = utils.helpers
Lee
@Lee. Oui, vous pouvez «injecter» des espaces de noms (modules), des fonctions, des instances de classe, etc. C'est utile, mais pas aussi flexible que d'autres moteurs de modèles comme mako. Pourtant, jinja a d'autres bons points. Je vous serais reconnaissant d'accepter la réponse si cela a aidé :)
Rob Cowie
a fait l'affaire pour moi en réalisant mon projet de moteur d'application (webapp2 et jinja2). merci
Sojan V Jose
@RobCowie après avoir ajouté la fonction clever_function au dictionnaire env.globals, comment appeler la fonction à partir du modèle.
Jorge Vidinha
1
Ainsi, {{ 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

Vous pouvez également donner la fonction dans les champs selon la réponse de Matroskin

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

Sortira:

Hey, my name is Jay Kay

Fonctionne avec Jinja2 version 2.7.3

Et si vous voulez qu'un décorateur facilite la définition des fonctions, template.globalsconsultez la réponse de Bruno Bronosky

AJP
la source
8
Probablement parce que vous avez décliné les réponses de tout le monde :(
Borko Kovacev
13
@BorkoKovacev ce n'est pas une bonne raison. Je n'ai voté que pour 2 réponses; réponses qui concernaient Flask plutôt que Jinja2. S'ils veulent modifier leurs réponses pour être sur le sujet et sur Jinja2, je les voterai.
AJP
Tx @BorkoKovacev :)
AJP
1
J'ai fait une version décoratrice de fonction de cette réponse. Il est actuellement en bas avec 0 votes
:,
2
@BrunoBronosky sympa. Ont voté :) ... donnez-lui une autre décennie et cela pourrait être plus élevé que le mien: P ... ne rattrapera jamais les flacons cependant; P
AJP
25

J'aime la réponse de @ AJP . Je l'ai utilisé textuellement jusqu'à ce que je me retrouve avec beaucoup de fonctions. Ensuite, je suis passé à un décorateur de fonctions 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))

Les bonnes fonctions ont un __name__!

Bruno Bronosky
la source
1
C'est incroyablement cool. Lorsque vous annotez une fonction en python, passe-t-il automatiquement le nom de la fonction à la fonction de l'annotation?
mutant_city
@mutant_city, oui. Lisez ce lien de décorateur de fonction Python. Super truc!
Bruno Bronosky
1
@BrunoBronosky Excellente démonstration d'une utilisation sensée et propre pour les décorateurs python également. Très bonne publication!
dreftymac
1
Quelle belle mise en œuvre!
Philippe Oger
16

Jamais vu un moyen aussi simple dans les documents officiels ou au débordement de pile, mais j'ai été étonné quand j'ai trouvé ceci:

# 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
la source
Cette réponse est de loin la meilleure à mon humble avis. Vous passez simplement la fonction au modèle exactement de la même manière que vous transmettez une valeur, après que toutes les fonctions soient des citoyens de première classe en python :)
Mark Kortink
8

Utilisez un lambda pour connecter le modèle à votre code principal

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

Ensuite, vous pouvez appeler la fonction de manière transparente dans le modèle

{{clever_function(value)}}
Robert Onslow
la source
1
Utilisation intelligente des fonctions lambda.
23
@odiumediae: Non, ce n'est pas le cas. C'est complètement inutile. Passez simplement la poignée de fonction elle-même: clever_function = clever_function
vezult
@vezult je vois. Comment pourrais-je manquer ça? Merci d'avoir clarifié cela!
6

Pour appeler une fonction python depuis Jinja2, vous pouvez utiliser des filtres personnalisés qui fonctionnent de la même manière que les globals: http://jinja.pocoo.org/docs/dev/api/#writing-filters

C'est assez simple et utile. Dans un fichier myTemplate.txt, j'ai écrit:

{{ data|pythonFct }}

Et dans un 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)
Ben9000RPM
la source
5

existe-t-il un moyen d'importer tout un ensemble de fonctions python et de les rendre accessibles à partir de jinja2?

Oui, en plus des autres réponses ci-dessus, cela fonctionne pour moi.

Créez une classe et remplissez-la avec les méthodes associées, par exemple

class Test_jinja_object:

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

    def clever_function (self):
        return 'hello' 

Ensuite, créez une instance de votre classe dans votre fonction de vue et transmettez l'objet résultant à votre modèle en tant que paramètre de la fonction render_template

my_obj = Test_jinja_object()

Maintenant, dans votre modèle, vous pouvez appeler les méthodes de classe dans jinja comme ceci

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
la source
Méthode équivalente et un peu plus simple: mettre toutes les fonctions des modèles dans un module, importer ce module et l'ajouter en tant que modèle global. Un module est un objet qui contient des fonctions :) (mais pas des méthodes - pas besoin de self param et pas de classe requise!)
Éric Araujo
@ ÉricAraujo Et si je n'ai besoin que de l'ensemble des fonctions dans un ou deux modèles et pas dans tous. Aussi, que faire si j'ai besoin de différents ensembles de fonctions python dans différents modèles jinjas? Considérez-vous toujours qu'il est efficace de les importer tous en tant que modèles globaux plutôt que de les placer comme méthodes dans une classe et de ne transmettre les classes qu'avec les méthodes dont vous avez besoin.
Kudehinbu Oluwaponle
Pour ne les utiliser que dans des modèles spécifiques, j'ajouterais les fonctions (ou un module contenant des fonctions) uniquement au dictionnaire contextuel du modèle qui est revu par les vues qui utilisent ces modèles.
Éric Araujo
3

Pour importer toutes les fonctions intégrées, vous pouvez utiliser:

app.jinja_env.globals.update(__builtins__)

Ajoutez .__dict__après __builtins__si cela ne fonctionne pas.

Basé sur la réponse de John32323 .

Solomon Ucko
la source
2

Si vous le faites avec Django, vous pouvez simplement passer la fonction avec le contexte:

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

Vous pourrez maintenant utiliser la strfonction dans le modèle jinja2

Jahid
la source
1

Il y a une décision beaucoup plus simple.

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

def y(text):
    return text

Ensuite, dans test.html :

{{ y('hi') }}
Никита Шишкин
la source
jinja2.exceptions.UndefinedError: 'y' n'est pas défini
lww
ouais, parce que censé utiliser foo dans test.html
luckyguy73