Фильтры и макросы: macro, call

Продолжаем изучение модуля Jinja и подробнее рассмотрим фильтры, которые удобно применять для получения более сложных представлений. И первый часто применяемый фильтр – это sum для вычисления суммы определенной коллекции. Например, у нас имеется список автомобилей:

cars = [
    {'model': 'Ауди', 'price': 23000},
    {'model': 'Шкода', 'price': 17300},
    {'model': 'Вольво', 'price': 44300},
    {'model': 'Фольксваген', 'price': 21300}
]

Требуется вывести суммарную цену всех автомобилей. Для этого можно прописать такой шаблон:

tpl = "Суммарная цена автомобилей {{ cs | sum(attribute='price') }}"
tm = Template(tpl)
msg = tm.render(cs = cars)
 
print(msg)

Смотрите, мы здесь сначала указываем коллекцию cs, из которой следует брать атрибут price и суммировать его значения по всем автомобилям. В результате применения такого фильтра в фигурных скобках будет подставлено одно число, равное сумме всех полей price:

Суммарная цена автомобилей 105900

А вот если убрать этот фильтр:

tpl = "Суммарная цена автомобилей {{ cs  }}"

то увидим коллекцию автомобилей:

Суммарная цена автомобилей [{'model': 'Ауди', 'price': 23000}, {'model': 'Шкода', 'price': 17300}, {'model': 'Вольво', 'price': 44300}, {'model': 'Фольксваген', 'price': 21300}]

Вот так фильтр может кардинально менять поведение шаблона. В общем случае синтаксис фильтра sum, следующий:

sum(iterable, attribute=None, start=0)

Здесь последний параметр start – это прибавочное значение к вычисленной сумме.

Полный список доступных фильтров, следующий:

Они все вполне очевидны и подробное описание можно почитать на странице официальной документации:

https://jinja.palletsprojects.com/en/2.11.x/templates/

Я здесь приведу лишь еще несколько примеров:

tpl = "Автомобиль: {{ cs | max(attribute='price')  }}"

Выводит словарь для автомобиля с максимальной ценой:

Автомобиль: {'model': 'Вольво', 'price': 44300}

Для выбора отдельного поля следует использовать такую запись:

tpl = "Автомобиль: {{ (cs | max(attribute='price')).model  }}"

то есть, мы сначала получаем словарь, а затем, выбираем из него поле model.

То же самое будет и для фильтра min:

tpl = "Автомобиль: {{ (cs | min(attribute='price')).model  }}"

Выбор случайного значения из последовательности:

tpl = "Автомобиль: {{ cs | random  }}"

Замена малой буквы ‘о’ на заглавную:

tpl = 'Автомобиль: {{ cs | replace("о", "О") }}'

И так далее. Каждый из этих фильтров можно применять в соответствии с его описанием и на выходе будем получать соответствующие данные.

Блок filter

Большую гибкость в применении фильтров дает специальный блок:

{{% filter <название фильтра> %}
<фрагмент для применения фильтра>
{% endfilter %}

Например, сделать так:

persons = [
    {"name": "Алексей", "old": 18, "weight": 78.5},
    {"name": "Николай", "old": 28, "weight": 82.3},
    {"name": "Иван", "old": 33, "weight": 94.0}
]
 
tpl = '''
{%- for u in users -%}
{% filter upper %}{{u.name}}{% endfilter %}
{% endfor -%}
'''
 
tm = Template(tpl)
msg = tm.render(users = persons)
 
print(msg)

На выходе получим список имен, записанных заглавными буквами.

Макроопределения

Модуль Jinja поддерживает макроопределения для шаблонов, которые весьма полезны, чтобы избежать повторяемых определений в соответствии с принципом

DRY – Don’t Repeat Yourself (не повторяйся)

Например, нам необходимо создать несколько полей ввода input в шаблоне HTML-документа. Его можно задать так:

html = '''
{% macro input(name, value='', type='text', size=20) -%}
    <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
{%- endmacro %}
 
{{ input('username') }}
{{ input('email') }}
{{ input('password') }}
'''

Здесь с помощью ключевого слова macro задано макроопределение с именем input и набором параметров. Это очень похоже на определение функций в Python. Учитывая, что в качестве параметров можно указывать специальные:

  • varargs – список переданных значений (параметров);
  • kwargs – список переданных именованных параметров.

А далее, мы используем этот макрос для создания трех полей input. Как видите, это может быть очень удобно. По аналогии можно создавать другие макросы и, затем, многократно их использовать в шаблонах.

Вложенные макросы – call

Модуль Jinja имеет специальное определение:

{% call[(параметры)] <вызов макроса> %}
<вложенный шаблон>
{% endcall %}

которое позволяет создавать своего рода вложенные макросы. Проще всего понять работу этого блока на конкретном примере. Предположим, мы хотим сформировать вот такой список:

На уровне HTML-документа это выглядит так:

<ul>
<li>Алексей 
    <ul>
    <li>age: 
    <li>weight: 78.5
    </ul>
<li>Николай 
    <ul>
    <li>age: 
    <li>weight: 82.3
    </ul>
<li>Иван 
    <ul>
    <li>age: 
    <li>weight: 94.0
    </ul>
</ul>

Создадим шаблон, который позволяет генерировать такой список на основе коллекции:

persons = [
    {"name": "Алексей", "old": 18, "weight": 78.5},
    {"name": "Николай", "old": 28, "weight": 82.3},
    {"name": "Иван", "old": 33, "weight": 94.0}
]

Далее, определим макрос, который генерирует начальный список из имен. Его можно представить так:

html = '''
{% macro list_users(list_of_user) -%}
<ul>
{% for u in users -%}
    <li>{{u.name}} 
{%- endfor %}
</ul>
{%- endmacro %}
 
{{list_users(users)}}
'''
 
tm = Template(html)
msg = tm.render(users = persons)
 
print(msg)

После выполнения этой программы, увидим следующее:

<ul>
<li>Алексей<li>Николай<li>Иван
</ul>

Отлично, это сделали. А теперь для каждого человека добавим вложенный список с его возрастом и весом. И сделаем это через блок call. Определим его так:

html = '''
{% macro list_users(list_of_user) -%}
<ul>
{% for u in list_of_user -%}
    <li>{{u.name}} {{caller(u)}}
{%- endfor %}
</ul>
{%- endmacro %}
 
{% call(user) list_users(users) %}
    <ul>
    <li>age: {{user.old}}
    <li>weight: {{user.weight}}
    </ul>
{% endcall -%}
'''

Смотрите, мы здесь прописали call с передаваемым ему параметром user – это будет текущий словарь, взятый из списка persons. Далее, указываем макрос, который следует вызвать для этого блока call. А все что записано внутри этого блока будет подставлено на место вызова метода caller внутри макроса list_users. В результате будет сформирован искомый список.