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

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


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 %}

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

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


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__!


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

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

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)

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

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 .


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


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

jinja2.exceptions.UndefinedError: 'y' é indefinido
— LWW

sim, porque deveria usar foo em
— test.html
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.