Вызов функции Python из jinja2

151

Я использую jinja2 и хочу вызвать функцию python в качестве помощника, используя такой же синтаксис, как если бы я вызывал макрос. jinja2, похоже, намерен помешать мне выполнить вызов функции и настаивает, чтобы я повторял себя, копируя функцию в шаблон в виде макроса.

Есть ли простой способ сделать это? И есть ли способ импортировать весь набор функций Python и сделать их доступными из jinja2, не проходя через целую кучу ритуалов (например, написание расширения)?

Ли
источник

Ответы:

225

Для тех, кто использует Flask, поместите это в свой __init__.py:

def clever_function():
    return u'HELLO'

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

и в вашем шаблоне назовите его {{ clever_function() }}

Джон32323
источник
вы можете передать несколько таких функций?
ffghfgh
6
В более новых версиях (я использую Jinja2 2.9.6) это работает намного проще. Используйте функцию так же, как и переменную (работает также в более сложных ситуациях):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
Даже через 8 лет, если вы используете Flask, это кажется более чистым решением, чем любой из более поздних ответов. И, отвечая на старый вопрос @ffghfgh, да - вы можете передавать несколько функций.
kevinmicke
137

Примечание: это специфично для Flask!

Я знаю, что этот пост довольно старый, но в новых версиях Flask есть более эффективные способы сделать это с использованием контекстных процессоров.

Переменные можно легко создавать:

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

Вышеупомянутое можно использовать в шаблоне Jinja2 с Flask следующим образом:

{{ myexample }}

(Какие выходы This is an example)

А также полноценные функции:

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

Приведенное выше при таком использовании:

{{ format_price(0.33) }}

(Что выводит входную цену с символом валюты)

В качестве альтернативы вы можете использовать фильтры jinja , встроенные в Flask. Например, с помощью декораторов:

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

Или без декораторов и вручную зарегистрировав функцию:

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

Фильтры, примененные двумя вышеуказанными методами, можно использовать следующим образом:

{% for x in mylist | reverse %}
{% endfor %}
Лиам Стэнли
источник
4
где должны существовать эти функции, инициализация, представления или где угодно?
Knk
3
__init__.pyпредполагая, что вы flask.Flask(__name__)там заявили .
Liam Stanley
7
Даун проголосовал за вопрос, заданный о Jinja2, и ответ относится к Flask.
AJP
14
@AJP Еще теоретически отвечает на вопрос. Это ОДИН способ решить проблему, если вы также используете Flask. Как и все вопросы по JavaScript, часто отвечают с указанием альтернатив с jQuery или без него, или вопросы о Python часто отвечают как для Python2, так и для Python 3. Вопрос не исключает Flask. (в отличие от вопроса о Py2 исключит ответ Py3). Этот ответ мне помог.
jeromej
3
Очень полезно и именно то, что я искал. Jinja2 является частью веб-фреймворка и как таковой не является полностью независимым от серверной части. Я работаю как в Django, так и во Flask с Python, и этот пост, как и другие здесь, имеют отношение ко мне. На мой взгляд, попытка переоценить вопрос так же вредна, как и излишне расплывчатость.
80

Я думаю, что jinja намеренно затрудняет запуск «произвольного» питона в шаблоне. Он пытается укрепить мнение, что меньше логики в шаблонах - это хорошо.

Вы можете управлять глобальным пространством имен в Environmentэкземпляре, чтобы добавлять ссылки на свои функции. Это необходимо сделать перед загрузкой любых шаблонов. Например:

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
Я тоже это обнаружил - вы можете добавить модуль, используя что-то вроде этого: import utils.helpers env.globals['helpers'] = utils.helpers
Ли
@ Ли. Да, вы можете «вставлять» пространства имен (модули), функции, экземпляры классов и т. Д. Это полезно, но не так гибко, как другие механизмы шаблонов, такие как mako. Тем не менее, у джиндзя есть и другие достоинства. Буду признателен, если вы примете ответ, если он поможет :)
Роб Коуи
помогли мне сделать мой проект движка приложений (webapp2 и jinja2). спасибо
Sojan V Jose
@RobCowie после добавления функции clever_function в словарь env.globals, как можно вызвать функцию из шаблона.
Хорхе Видинья,
3
Итак, {{ clever_function('a', 'b') }}
Роб Коуи
43
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

Вы также можете указать функцию в полях в соответствии с ответом Матроскина

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

Выведет:

Hey, my name is Jay Kay

Работает с Jinja2 версии 2.7.3

И если вы хотите, чтобы декоратор облегчил определение функций template.globals, ознакомьтесь с ответом Бруно Броноски.

AJP
источник
8
Вероятно, потому что вы проголосовали против всех остальных :(
Борко Ковачев
15
@BorkoKovacev, это плохая причина. Я проголосовал только за 2 ответа; ответы, которые касались Flask, а не Jinja2. Если они захотят отредактировать свои ответы, чтобы быть по теме и о Jinja2, я проголосую за них.
AJP
Tx @BorkoKovacev :)
AJP
1
Я сделал версию этого ответа с помощью декоратора функций. В настоящее время он внизу с 0 голосами:, - ( stackoverflow.com/a/47291097/117471
Бруно Броноски
2
@BrunoBronosky приятно. Проголосовали :) ... дайте ему еще десять лет, и он может быть выше, чем у меня: P ... никогда не поймаю фляги; P
AJP
25

Мне нравится ответ @AJP . Я использовал его дословно, пока не получил множество функций. Затем я переключился на декоратор функций 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))

Хорошие функции есть __name__!

Бруно Броноски
источник
1
Это безумно круто. Когда вы аннотируете функцию в Python, она автоматически передает имя функции в функцию аннотации?
mutant_city
@mutant_city, ага. Прочтите ссылку на декоратор функций Python. Отличный материал!
Бруно Броноски
1
@BrunoBronosky Отличная демонстрация разумного и чистого использования декораторов Python. Отличный пост!
dreftymac
1
Какая отличная реализация!
Philippe Oger
17

Никогда не видел такого простого способа в официальных документах или при переполнении стека, но я был поражен, когда обнаружил это:

# 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})
Матроскин
источник
Этот ответ, безусловно, лучший, имхо. Вы просто передаете функцию в шаблон точно так же, как вы передаете значение, в конце концов, все функции являются первоклассными гражданами в python :)
Марк Кортинк,
8

Используйте лямбда, чтобы подключить шаблон к вашему основному коду

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

Затем вы можете легко вызвать функцию в шаблоне

{{clever_function(value)}}
Роберт Онслоу
источник
1
Умное использование лямбда-функций.
23
@odiumediae: Нет, это не так. Это совершенно не нужно. Просто передайте дескриптор функции: clever_function = clever_function
vezult
@vezult Понятно. Как я мог это пропустить? Спасибо, что прояснили это!
6

Чтобы вызвать функцию Python из Jinja2, вы можете использовать настраиваемые фильтры, которые работают так же, как глобальные: http://jinja.pocoo.org/docs/dev/api/#writing-filters

Это довольно просто и полезно. В файле myTemplate.txt я написал:

{{ data|pythonFct }}

И в скрипте 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)
Ben9000об / мин
источник
5

есть ли способ импортировать весь набор функций python и сделать их доступными из jinja2?

Да, есть, в дополнение к другим ответам выше, это работает для меня.

Создайте класс и заполните его соответствующими методами, например

class Test_jinja_object:

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

    def clever_function (self):
        return 'hello' 

Затем создайте экземпляр своего класса в своей функции просмотра и передайте полученный объект в свой шаблон в качестве параметра для функции render_template.

my_obj = Test_jinja_object()

Теперь в вашем шаблоне вы можете вызывать методы класса в jinja следующим образом

{{ my_obj.clever_function () }}
Кудехинбу Олувапонле
источник
Эквивалентный и немного более простой способ: поместить все функции для шаблонов в модуль, импортировать этот модуль и добавить его как глобальный шаблон. Модуль - это объект, который содержит функции :) (но не методы - нет необходимости в параметрах self и не требуются классы!)
Эрик Араужо
@ ÉricAraujo Что делать, если мне нужен только набор функций в одном или двух шаблонах, а не во всех. Кроме того, что, если мне нужны разные наборы функций python в разных шаблонах jinjas? Считаете ли вы эффективным импортировать их все как глобальные объекты шаблона, а не помещать их как методы в класс и передавать только классы с теми методами, которые вам нужны?
Кудехинбу Олувапонле
Чтобы использовать только в определенных шаблонах, я бы добавил функции (или модуль, содержащий функции) только в dict контекста шаблона, который возвращается представлениями, использующими эти шаблоны.
Эрик Араужо
3

Чтобы импортировать все встроенные функции, вы можете использовать:

app.jinja_env.globals.update(__builtins__)

Добавьте .__dict__после, __builtins__если это не сработает.

На основе ответа John32323 .

Соломон Учко
источник
2

Если вы делаете это с помощью Django, вы можете просто передать функцию с контекстом:

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

Теперь вы сможете использовать strфункцию в шаблоне jinja2

Джахид
источник
1

Есть гораздо более простое решение.

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

def y(text):
    return text

Затем в test.html :

{{ y('hi') }}
Никита Шишкин
источник
jinja2.exceptions.UndefinedError: 'y' не определено
lww
да, потому что предполагается использовать foo в test.html
luckyguy73
0

Ответ @ John32323 - очень чистое решение.

Вот такой же, но сохраните в отдельный файл, может быть, более чистый.

Создать вспомогательный файл

приложение \ helper.py

from app import app

def clever_function_1():
    return u'HELLO'

def clever_function_2(a, b):
    return a + b



app.jinja_env.globals.update(
    clever_function_1=clever_function_1,
    clever_function_2=clever_function_2,
)

Импортировать из приложения

app.py

from app import routes
from app import helper   # add this one

Используйте как это

приложение \ шаблоны \ some.html


{{ clever_function_1() }}
{{ clever_function_2(a, b) }}

хлопушка
источник