Chamar uma função python do jinja2

144

Estou usando jinja2 e quero chamar uma função python como auxiliar, usando uma sintaxe semelhante como se estivesse chamando uma macro. O jinja2 parece ter a intenção de impedir que eu faça uma chamada de função e insiste em que eu repita copiando a função em um modelo como uma macro.

Existe alguma maneira simples de fazer isso? E, existe alguma maneira de importar todo um conjunto de funções python e torná-las acessíveis a partir do jinja2, sem passar por um monte de rigamarole (como escrever uma extensão)?

Lee
fonte

Respostas:

223

Para aqueles que usam o Flask, coloque isso em __init__.py:

def clever_function():
    return u'HELLO'

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

e no seu modelo chame-o com {{ clever_function() }}

John32323
fonte
você pode passar várias funções como essa?
precisa saber é
6
Nas versões mais recentes (estou usando o Jinja2 2.9.6), parece funcionar muito mais fácil. Use a função como você usaria uma variável (funciona também em situações mais complexas):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
Mesmo 8 anos depois, se você estiver usando o Flask, isso parece uma solução mais limpa do que qualquer uma das respostas mais recentes. E para responder à pergunta antiga de @ffghfgh, sim - você pode passar várias funções.
22619 kevinmicke
133

Nota: Isso é específico para o frasco!

Eu sei que este post é bastante antigo, mas existem métodos melhores para fazer isso nas versões mais recentes do Flask usando processadores de contexto.

Variáveis ​​podem ser facilmente criadas:

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

O acima pode ser usado em um modelo Jinja2 com o Flask da seguinte forma:

{{ myexample }}

(Quais saídas This is an example)

Bem como funções de pleno direito:

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

A descrição acima, quando usada da seguinte maneira:

{{ format_price(0.33) }}

(Que gera o preço de entrada com o símbolo da moeda)

Como alternativa, você pode usar filtros jinja , inseridos no Flask. Por exemplo, usando decoradores:

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

Ou, sem decoradores, e registrando manualmente a função:

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

Os filtros aplicados com os dois métodos acima podem ser usados ​​assim:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
fonte
4
onde essas funções devem existir, init, visualizações ou qualquer outro lugar?
KNK
3
__init__.pyassumindo que você declarou flask.Flask(__name__)lá.
Liam Stanley
6
Down votou como uma pergunta sobre Jinja2 e a resposta é específica do Flask.
AJP
13
@AJP Ainda, teoricamente, responde à pergunta. Esta é uma maneira de resolver o problema, desde que você também esteja usando o Flask. Como todas as perguntas sobre JavaScript, muitas vezes respondem dando alternativas com ou sem jQuery, ou perguntas sobre Python geralmente respondem para Python2 e 3. A pergunta não estava excluindo o Flask. (ao contrário de uma pergunta sobre o Py2, excluiria uma resposta do Py3). Essa resposta me ajudou.
Jeromej
2
Muito útil e exatamente o que eu estava procurando. O Jinja2 faz parte de uma estrutura da Web e, como tal, não é totalmente independente do back-end. Eu trabalho no Django e Flask com Python e este post, assim como os outros aqui, são relevantes para mim. Tentar especificar demais uma pergunta é tão prejudicial na minha opinião quanto ser desnecessariamente vago.
76

Eu acho que o jinja deliberadamente dificulta a execução de python 'arbitrário' dentro de um modelo. Ele tenta reforçar a opinião de que menos lógica nos modelos é uma coisa boa.

Você pode manipular o espaço para nome global em uma Environmentinstância para adicionar referências às suas funções. Isso deve ser feito antes de você carregar qualquer modelo. Por exemplo:

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
fonte
5
Eu descobri isso também - você pode adicionar um módulo usando algo parecido com isto: import utils.helpers env.globals['helpers'] = utils.helpers
Lee
@Lee. Sim, você pode 'injetar' namespaces (módulos), funções, instâncias de classe etc. É útil, mas não tão flexível quanto outros mecanismos de modelo como o mako. Ainda assim, jinja tem outros bons pontos. Eu ficaria muito grato se você aceitar a resposta se ajudou :)
Rob Cowie
fez o truque para mim enquanto fazia meu projeto de mecanismo de aplicativo (webapp2 e jinja2). graças
Sojan V Jose
@RobCowie, depois de adicionar a função clever ao dicionário env.globals, como chamar a função a partir do modelo.
Jorge Vidinha
1
Assim, {{ 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

Você também pode fornecer a função nos campos conforme a resposta de Matroskin

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

Saída:

Hey, my name is Jay Kay

Funciona com o Jinja2 versão 2.7.3

E se você quer um decorador para facilitar a definição de funções, template.globalsconfira a resposta de Bruno Bronosky

AJP
fonte
8
Provavelmente porque você votou negativamente nas respostas de todos os outros :(
Borko Kovacev
13
@BorkoKovacev, esse não é um bom motivo. Eu apenas votei 2 respostas; respostas que eram sobre o Flask e não sobre o Jinja2. Se eles querem editar suas respostas para serem sobre o tópico e sobre o Jinja2, votarei nelas.
AJP
Tx @BorkoKovacev :)
AJP
1
Eu fiz uma versão do decorador de funções desta resposta. Atualmente, na parte inferior, com 0 votos
:,
2
@BrunoBronosky nice. Já votei :) ... dê mais uma década e pode ser maior que a minha: P ... nunca pegará os frascos; P
AJP
25

Eu gosto da resposta da @ AJP . Eu o usei literalmente até terminar com muitas funções. Então mudei para um decorador de funções 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))

Coisa boa funções têm um __name__!

Bruno Bronosky
fonte
1
Isso é incrivelmente legal. Quando você anota uma função em python, ela passa automaticamente o nome da função para a função da anotação?
22819 mutant_city #
@mutant_city, sim. Leia o link do decorador da função Python. Coisas boas!
Bruno Bronosky
1
@BrunoBronosky Excelente demonstração de um uso sensato e limpo para os decoradores de python também. Ótimo post!
Dreftymac
1
Que ótima implementação!
Philippe Oger
16

Nunca vi uma maneira tão simples em documentos oficiais ou no estouro de pilha, mas fiquei surpreso ao encontrar isso:

# 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
fonte
Esta resposta é de longe o melhor imho. Você apenas passa a função para o modelo exatamente da mesma maneira que passa um valor, depois de todas as funções serem cidadãos de primeira classe em python :)
Mark Kortink
8

Use um lambda para conectar o modelo ao seu código principal

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

Em seguida, você pode chamar perfeitamente a função no modelo

{{clever_function(value)}}
Robert Onslow
fonte
1
Uso inteligente de funções lambda.
23
@odiumediae: Não, não é. É completamente desnecessário. Basta passar o próprio identificador de função: clever_function = clever_function
vezult
@vezult eu vejo. Como eu poderia sentir falta disso? Obrigado por esclarecer isso!
6

Para chamar uma função python do Jinja2, você pode usar filtros personalizados que funcionam da mesma forma que os globais: http://jinja.pocoo.org/docs/dev/api/#writing-filters

É bastante simples e útil. Em um arquivo myTemplate.txt, escrevi:

{{ data|pythonFct }}

E em um 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
fonte
5

existe alguma maneira de importar todo um conjunto de funções python e tê-las acessíveis a partir do jinja2?

Sim, existe. Além das outras respostas acima, isso funciona para mim.

Crie uma classe e preencha-a com os métodos associados, por exemplo

class Test_jinja_object:

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

    def clever_function (self):
        return 'hello' 

Em seguida, crie uma instância da sua classe na sua função view e passe o objeto resultante para o seu template como um parâmetro para a função render_template

my_obj = Test_jinja_object()

Agora, no seu modelo, você pode chamar os métodos de classe no jinja dessa maneira

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
fonte
Maneira equivalente e um pouco mais simples: coloque todas as funções dos modelos em um módulo, importe esse módulo e adicione-o como modelo global. Um módulo é um objeto que contém funções :) (mas não métodos - sem necessidade de auto param e nenhuma classe reqiured)
Éric Araujo
@ ÉricAraujo E se eu precisar apenas do conjunto de funções em um ou dois modelos e não em todos eles. Além disso, e se eu precisar de conjuntos diferentes de funções python em diferentes modelos jinjas? Você ainda consideraria eficaz importar todos eles como modelo global em vez de colocá-los como métodos em uma classe e apenas passar as classes com os métodos necessários?
Kudehinbu Oluwaponle
Para usar apenas em modelos específicos, eu adicionaria as funções (ou um módulo contendo funções) apenas ao contexto de modelo que é repetido pelas visualizações que usam esses modelos.
Éric Araujo
3

Para importar todas as funções internas, você pode usar:

app.jinja_env.globals.update(__builtins__)

Adicione .__dict__depois __builtins__se isso não funcionar.

Com base na resposta de John32323 .

Solomon Ucko
fonte
2

Se você estiver fazendo isso com o Django, basta passar a função com o contexto:

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

Agora você poderá usar a strfunção no modelo jinja2

Jahid
fonte
1

Há uma decisão muito mais simples.

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

def y(text):
    return text

Então, em test.html :

{{ y('hi') }}
Никита Шишкин
fonte
jinja2.exceptions.UndefinedError: 'y' é indefinido
LWW
sim, porque deveria usar foo em
test.html