Llamar a una función de python desde jinja2

144

Estoy usando jinja2, y quiero llamar a una función de python como ayudante, usando una sintaxis similar a la de una macro. Parece que jinja2 intenta evitar que haga una llamada a una función, e insiste en que me repita copiando la función en una plantilla como una macro.

¿Hay alguna forma directa de hacer esto? Y, ¿hay alguna manera de importar un conjunto completo de funciones de Python y tenerlas accesibles desde jinja2, sin pasar por un montón de rigamarole (como escribir una extensión)?

Sotavento
fuente

Respuestas:

223

Para aquellos que usan Flask, pon esto en tu __init__.py:

def clever_function():
    return u'HELLO'

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

y en tu plantilla llámalo con {{ clever_function() }}

John32323
fuente
¿Puedes pasar múltiples funciones como esta?
ffghfgh
66
En versiones más recientes (estoy usando Jinja2 2.9.6) parece funcionar mucho más fácilmente. Use la función como usaría una variable (funciona también en situaciones más complejas):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 el
1
Incluso 8 años después, si está usando Flask, parece una solución más limpia que cualquiera de las respuestas más recientes. Y para responder la vieja pregunta de @ffghfgh, sí, puede pasar múltiples funciones.
kevinmicke
133

Nota: ¡Esto es específico de Flask!

Sé que esta publicación es bastante antigua, pero hay mejores métodos para hacerlo en las versiones más recientes de Flask usando procesadores de contexto.

Las variables se pueden crear fácilmente:

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

Lo anterior se puede usar en una plantilla Jinja2 con Flask así:

{{ myexample }}

(Que salidas This is an example)

Además de las funciones completas:

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

Lo anterior cuando se usa así:

{{ format_price(0.33) }}

(Que genera el precio de entrada con el símbolo de moneda)

Alternativamente, puede usar filtros jinja , horneados en Flask. Por ejemplo, usar decoradores:

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

O, sin decoradores, y registrando manualmente la función:

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

Los filtros aplicados con los dos métodos anteriores se pueden usar así:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
fuente
44
¿Dónde deberían existir estas funciones, init, vistas o simplemente en cualquier lugar?
knk
3
__init__.pyasumiendo que declaraste flask.Flask(__name__)allí.
Liam Stanley
66
Down votó como pregunta sobre Jinja2 y la respuesta es específica de Flask.
AJP
13
@AJP Still teóricamente responde la pregunta. Esta es UNA forma de resolver el problema, siempre que también esté usando Flask. Un poco como todas las preguntas de JavaScript a menudo responden dando alternativas con o sin jQuery o las preguntas sobre Python a menudo responden tanto para Python2 como para 3. La pregunta no excluía el Frasco. (a diferencia de una pregunta sobre Py2 excluiría una respuesta de Py3). Esta respuesta me ayudó.
jeromej
2
Muy útil, y justo lo que estaba buscando. Jinja2 es parte de un marco web y, como tal, no es totalmente independiente del back-end. Trabajo tanto en Django como en Flask con Python y esta publicación, así como las demás aquí son relevantes para mí. Intentar especificar más de una pregunta es tan perjudicial en mi opinión como ser innecesariamente vago.
76

Creo que jinja deliberadamente hace que sea difícil ejecutar python 'arbitrario' dentro de una plantilla. Intenta imponer la opinión de que menos lógica en las plantillas es algo bueno.

Puede manipular el espacio de nombres global dentro de una Environmentinstancia para agregar referencias a sus funciones. Debe hacerse antes de cargar cualquier plantilla. Por ejemplo:

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
fuente
55
Descubrí esto también - se puede añadir un módulo usando algo como esto: import utils.helpers env.globals['helpers'] = utils.helpers
Lee
@Sotavento. Sí, puede 'inyectar' espacios de nombres (módulos), funciones, instancias de clase, etc. Es útil, pero no tan flexible como otros motores de plantillas como mako. Aún así, jinja tiene otros buenos puntos. Le agradecería que aceptara la respuesta si ayudara :)
Rob Cowie
hizo el truco para mí mientras hacía mi proyecto de motor de aplicaciones (webapp2 y jinja2). gracias
Sojan V Jose
@RobCowie después de agregar la función inteligente al diccionario env.globals, ¿cómo se puede llamar a la función desde la plantilla?
Jorge Vidinha
1
Por lo tanto, {{ 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

También puede asignar la función en los campos según la respuesta de Matroskin

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

Saldrá:

Hey, my name is Jay Kay

Funciona con Jinja2 versión 2.7.3

Y si desea un decorador para facilitar la definición de funciones al template.globalsconsultar la respuesta de Bruno Bronosky

AJP
fuente
8
Probablemente porque rechazaste las respuestas de todos los demás :(
Borko Kovacev
13
@BorkoKovacev esa no es una buena razón. Solo rechacé 2 respuestas; respuestas que fueron sobre Flask en lugar de Jinja2. Si quieren editar sus respuestas para estar en el tema y sobre Jinja2, las votaré.
AJP
Tx @BorkoKovacev :)
AJP
1
Hice una versión decoradora de funciones de esta respuesta. Actualmente en la parte inferior con 0 votos:, - ( stackoverflow.com/a/47291097/117471
Bruno Bronosky
2
@BrunoBronosky agradable. Han votado :) ... denle otra década y podría ser más alto que el mío: P ... nunca atrapará los frascos; P
AJP
25

Me gusta la respuesta de @ AJP . Lo usé textualmente hasta que terminé con muchas funciones. Luego me cambié a un decorador de funciones de 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))

Lo bueno es que las funciones tienen un __name__!

Bruno Bronosky
fuente
1
Esto es increíblemente genial. Cuando anota una función en python, ¿pasa automáticamente el nombre de la función a la función de la anotación?
mutant_city
@mutant_city, sí. Lea el enlace del decorador de funciones de Python. ¡Buena cosa!
Bruno Bronosky
1
@BrunoBronosky Excelente demostración de un uso sensato y limpio para decoradores de python también. ¡Buena publicación!
dreftymac
1
¡Qué gran implementación!
Philippe Oger
16

Nunca vi una manera tan simple en los documentos oficiales o en el desbordamiento de la pila, pero me sorprendió cuando encontré esto:

# 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
fuente
Esta respuesta es, con mucho, la mejor en mi humilde opinión. Simplemente pasa la función a la plantilla exactamente de la misma manera que pasa un valor, después de que todas las funciones son ciudadanos de primera clase en python :)
Mark Kortink
8

Use una lambda para conectar la plantilla a su código principal

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

Luego puede llamar sin problemas a la función en la plantilla

{{clever_function(value)}}
Robert Onslow
fuente
1
Uso inteligente de las funciones lambda.
23
@odiumediae: No, no lo es. Es completamente innecesario. Simplemente pase el identificador de la función en sí: clever_function = clever_function
vezult
@vezult ya veo. ¿Cómo podría extrañar eso? ¡Gracias por aclarar eso!
6

Para llamar a una función de Python desde Jinja2, puede usar filtros personalizados que funcionan de manera similar a los globales: http://jinja.pocoo.org/docs/dev/api/#writing-filters

Es bastante simple y útil. En un archivo myTemplate.txt, escribí:

{{ data|pythonFct }}

Y en un script de 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
fuente
5

¿Hay alguna manera de importar un conjunto completo de funciones de Python y tenerlas accesibles desde jinja2?

Sí, hay, además de las otras respuestas anteriores, esto funciona para mí.

Cree una clase y complétela con los métodos asociados, p. Ej.

class Test_jinja_object:

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

    def clever_function (self):
        return 'hello' 

Luego cree una instancia de su clase en su función de vista y pase el objeto resultante a su plantilla como parámetro para la función render_template

my_obj = Test_jinja_object()

Ahora en su plantilla, puede llamar a los métodos de clase en jinja así

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
fuente
Forma equivalente y ligeramente más simple: poner todas las funciones para plantillas en un módulo, importar ese módulo y agregarlo como plantilla global. Un módulo es un objeto que contiene funciones :) (pero no métodos, ¡no es necesario un auto parámetro y no se requiere clase!)
Éric Araujo
@ ÉricAraujo ¿Qué sucede si solo necesito el conjunto de funciones en una o dos plantillas y no en todas? Además, ¿qué pasa si necesito diferentes conjuntos de funciones de python en diferentes plantillas de jinjas? ¿Consideraría que es efectivo importarlos todos como plantillas globales en lugar de ponerlos como métodos en una clase y solo pasar las clases con los métodos que necesita?
Kudehinbu Oluwaponle
Para usar solo en plantillas específicas, agregaría las funciones (o un módulo que contenga funciones) solo al diagrama de contexto de la plantilla que devuelven las vistas que usan esas plantillas.
Éric Araujo
3

Para importar todas las funciones integradas que puede usar:

app.jinja_env.globals.update(__builtins__)

Agregue .__dict__después __builtins__si esto no funciona.

Basado en la respuesta de John32323 .

Solomon Ucko
fuente
2

Si lo está haciendo con Django, puede pasar la función con el contexto:

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

Ahora podrá usar la strfunción en la plantilla jinja2

Jahid
fuente
1

Hay una decisión mucho más simple.

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

def y(text):
    return text

Luego, en test.html :

{{ y('hi') }}
Никита Шишкин
fuente
jinja2.exceptions.UndefinedError: 'y' no está definido
lww
sí, porque se supone que debe usar foo en test.html
luckyguy73