Jinja2 python

Теги: templating 

Документация

$ pip install Jinja2

Dependencies

MarkupSafe escapes untrusted input when rendering templates to avoid injection attacks.

Jinja uses a central object called the template Environment. Instances of this class are used to store the configuration and global objects, and are used to load templates from the file system or other locations. Even if you are creating templates from strings by using the constructor of Template class, an environment is created automatically for you, albeit a shared one.

from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
    loader=PackageLoader("yourapp"),
    autoescape=select_autoescape()
)

Это создает окружение шаблона с лоадером, который ищет шаблоны внутри вашего пакета #python. Другие лоадеры тоже доступны.

Чтобы загрузить шаблон из окружения, можно сделать так:

template = env.get_template("mytemplate.html")

Для отрисовки с переменными используется метод render()

print(template.render(the="variables", go="here"))

Все подробности об АПИ читать тут

Песочница позволяет работать с небезопасными данными

Native Python Types позволяет работать с нативным питоньим кодом в шаблонах. Бывает полезно при создании текстовых файлов.

Template Designer Documentation

Jinja не требует специальных форматов для шаблона (это может быть простой текстовый файл) и может генерировать любые текст-бейсед форматы - HTML, XML, CSV, LaTeX, etc. Шаблон содержит переменные и выражения, которые подменяются, когда шаблон рендерится. Кроме того, шаблон может содержать теги для контроля логики шаблона.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>My Webpage</title>
</head>
<body>
    <ul id="navigation">
    {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
    </ul>

    <h1>My Webpage</h1>
    {{ a_variable }}

    {# a comment #}
</body>
</html>

Разработчик может использовать любой удобный ему синтаксис: {% foo %}, <% foo %> и что-то похожее. Дефолтные разделители сконфигурированы так:

  • {% … %} for Statements
  • {{}} for Expressions to print to the template output
  • {# … #} for Comments not included in the template output

Переменные

{{ foo.bar }}
{{ foo['bar'] }}

Фильтры

Переменные могут быть изменеены фильтрами. Например это {{ listx join(‘, ‘) }} эквивалентно этому (str.join(', ', listx))

Тесты

Используется для проверки переменных с помощью простых выражений

{% if loop.index is divisibleby 3 %}
{% if loop.index is divisibleby(3) %}

Список буилт-ин тестов

Коментарии

{# note: commented-out template because we no longer use this
    {% for user in users %}
        ...
    {% endfor %}
#}

Контроль пробелов

При рендеринге выражения jinja заменяются на переносы строки. Также может быть сконфигурировано вырезать полностью код шаблона. Этим можно управлять вручную в коде шаблона с помощью знаков + и -

<div>
        {%+ if something %}yay{% endif %}
</div>

<div>
    {% if something +%}
        yay
    {% endif %}
</div>

{% for item in seq -%}
    {{ item }}
{%- endfor %}

В первом случае в ручную добавляем, во втором вырезаем.

>valid:
{%- if foo -%}...{% endif %}
invalid:
{% - if foo - %}...{% endif %}

Пропуск символов внутри кода шаблона

{{ '{{' }} - одинарные ковычки. Для больших секций тег raw

'{'% raw %'}'
    <ul>
    {% for item in seq %}
        <li>{{ item }}</li>
    {% endfor %}
    </ul>
'{'% endraw %'}'

Line Statements

Еще один вариант синтаксиса

<ul>
# for item in seq
    <li>{{ item }}</li>
# endfor
</ul>

<ul>
{% for item in seq %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

Наследование шаблонов

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
        &copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>.
        {% endblock %}
    </div>
</body>
</html>

Блок {% block %} определяет филлер, который будет использоваться в унаследовавших шаблонах. Пример наследника:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
    {{ super() }}
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock %}

{% extends “layout/default.html” %} определяет от кого наследуемся и его местоположение, например в данном случае из сабдиректории.

{% extends layout_template %} так тоже можно, если шаблон добавлен в окружение.

Тег {% block %} можно использовать множество раз с одинаковым именем в одном шаблоне. Если нужно использовать блок много раз, то можно вызвать его через self

<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}

Чтобы отрендерить контент филлера из блока родителя, используйте super()

{% block sidebar %}
    <h3>Table Of Contents</h3>
    ...
    {{ super() }}
{% endblock %}

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

# parent.tmpl
body: {% block body %}Hi from parent.{% endblock %}

# child.tmpl
{% extends "parent.tmpl" %}
{% block body %}Hi from child. {{ super() }}{% endblock %}

# grandchild1.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild1.{% endblock %}

# grandchild2.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild2. {{ super.super() }} {% endblock %}

Для удобства чтения шаблонов, jinja позволяет добавлять название тега в закрывающую часть

{% block sidebar %}
    {% block inner_sidebar %}
        ...
    {% endblock inner_sidebar %}
{% endblock sidebar %}

scoped позволяет унаследовать переменные из сабблока

{% for item in seq %}
    <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}

required блоки играют роль абстрактных классов. Они могут содержать только пробелы и коментарии, должны быть реализованы во всех дочерних шаблонах и могут быть переопределены. Он не будет рендерен напрямую - попытка отрендерить шаблон, содержащий required поднимет ошибку… отрендерится только дочерний

page.txt

{% block body required %}{% endblock %}

issue.txt

{% extends "page.txt" %}

bug_report.txt

{% extends "issue.txt" %}
{% block body %}Provide steps to demonstrate the bug.{% endblock %}

Пропуск HTML

Вручную {{ user.username e }} будут пропущены все переменные, содержащие >, <, &, or “. Автоматически описано тут

Циклы

FOR

По списку

<h1>Members</h1>
<ul>
{% for user in users %}
  <li>{{ user.username|e }}</li>
{% endfor %}
</ul>

По словарю

<dl>
{% for key, value in my_dict.items() %}
    <dt>{{ key|e }}</dt>
    <dd>{{ value|e }}</dd>
{% endfor %}
</dl>

По отсортированному словарю

<dl>
{% for key, value in my_dict | dictsort %}
    <dt>{{ key|e }}</dt>
    <dd>{{ value|e }}</dd>
{% endfor %}
</dl>

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

  • loop.index The current iteration of the loop. (1 indexed)
  • loop.index0 The current iteration of the loop. (0 indexed)
  • loop.revindex The number of iterations from the end of the loop (1 indexed)
  • loop.revindex0 The number of iterations from the end of the loop (0 indexed)
  • loop.first True if first iteration.
  • loop.last True if last iteration.
  • loop.length The number of items in the sequence.
  • loop.cycle A helper function to cycle between a list of sequences. See the explanation below.
  • loop.depth Indicates how deep in a recursive loop the rendering currently is. Starts at level 1
  • loop.depth0 Indicates how deep in a recursive loop the rendering currently is. Starts at level 0
  • loop.previtem The item from the previous iteration of the loop. Undefined during the first iteration.
  • loop.nextitem The item from the following iteration of the loop. Undefined during the last iteration.
  • loop.changed(*val) True if previously called with a different value (or not called at all).

Вот так:

{% for row in rows %}
    <li class="{{ loop.cycle('odd', 'even') }}">{{ row }}</li>
{% endfor %}

Выйти из цикла (в отличае от python, где есть break) нельзя! Можно только фильтровать по переменным цикла

{% for user in users if not user.hidden %}
    <li>{{ user.username|e }}</li>
{% endfor %}

Можно использовать дефолтный блок, если “пусто”

<ul>
{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no users found</em></li>
{% endfor %}
</ul>

Доступны рекурсии

<ul class="sitemap">
{%- for item in sitemap recursive %}
    <li><a href="{{ item.href|e }}">{{ item.title }}</a>
    {%- if item.children -%}
        <ul class="submenu">{{ loop(item.children) }}</ul>
    {%- endif %}</li>
{%- endfor %}
</ul>

IF

{% if kenny.sick %}
    Kenny is sick.
{% elif kenny.dead %}
    You killed Kenny!  You bastard!!!
{% else %}
    Kenny looks okay --- so far
{% endif %}

Macros

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

затем это может быть вызвано в шаблоне.

<p>{{ input('username') }}</p>
<p>{{ input('password', type='password') }}</p>

Filters

{% filter upper %}
    This text becomes uppercase
{% endfilter %}

Assignments

Внутри кода шаблона можно назначать значения переменным

{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

При этом назначенные переменные не будут видны за пределами циклов if и for!!!

Допустимо присваивать блоки (так-же поддерживаются и фильтры)

{% set navigation %}
    <li><a href="/">Index</a>
    <li><a href="/downloads">Downloads</a>
{% endset %}

included

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

{% include 'header.html' %}
    Body
{% include 'footer.html' %}
{% include "sidebar.html" ignore missing %}
{% include "sidebar.html" ignore missing with context %}
{% include "sidebar.html" ignore missing without context %}
{% include ['page_detailed.html', 'page.html'] %}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}

Import

Поддерживаются импорты внутри макросов и импорты from … import … as …

{% from 'forms.html' import input with context %}
{% include 'header.html' without context %}

Выражения

Литералы

“Hello World”

42 / 123_456

*The ‘_’ character can be used to separate groups for legibility.

42.23 / 42.1e2 / 123_456.789

[‘list’, ‘of’, ‘objects’]

<ul>
{% for href, caption in [('index.html', 'Index'), ('about.html', 'About'),
                         ('downloads.html', 'Downloads')] %}
    <li><a href="{{ href }}">{{ caption }}</a></li>
{% endfor %}
</ul>

(‘tuple’, ‘of’, ‘values’)

{‘dict’: ‘of’, ‘key’: ‘and’, ‘value’: ‘pairs’}

true / false / none *different with #python - lower letters

Математика

+ - / // % * **

{{ 11 % 7 }}

Сравнения

== != > >= <= <

Логика

and or nit (expr) *группа параметров выражения

Можно использовать для for, if

The is and in operators support negation using an infix notation, too: foo is not bar and foo not in bar instead of not foo is bar and not foo in bar. All other expressions require a prefix notation: not (foo and bar)

Другие опреаторы

in Perform a sequence / mapping containment test. Returns true if the left operand is contained in the right. {{ 1 in [1, 2, 3] }} would, for example, return true.

is Performs a test.

| (pipe, vertical bar) Applies a filter.

~ (tilde) Converts all operands into strings and concatenates them.

{{ "Hello " ~ name ~ "!" }} would return (assuming name is set to ‘John’) Hello John!.

() Call a callable: {{ post.render() }}. Inside of the parentheses you can use positional arguments and keyword arguments like in Python:

{{ post.render(user, full=true) }}.

. / [] Get an attribute of an object.

If

Можно так

{% extends layout_template if layout_template is defined else ‘default.html’ %}

<do something> if <something is true> else <do something else>

Методы python

{{ page.title.capitalize() }}
{{ f.bar(value) }}
{{ "Hello, %s!" % name }}
{{ "Hello, {}!".format(name) }}

Доступно

  • abs()
  • float()
  • lower()
  • round()
  • tojson()
  • attr()
  • forceescape()
  • map()
  • safe()
  • trim()
  • batch()
  • format()
  • max()
  • select()
  • truncate()
  • capitalize()
  • groupby()
  • min()\
  • selectattr()
  • unique()
  • center()
  • indent()
  • pprint()
  • slice()
  • upper()
  • default()
  • int()
  • random()
  • sort()
  • urlencode()
  • dictsort()
  • join()
  • reject()
  • string()
  • urlize()
  • escape()
  • last()
  • rejectattr()
  • striptags()
  • wordcount()
  • filesizeformat()
  • length()
  • replace()
  • sum()
  • wordwrap()
  • first()
  • list()
  • reverse()
  • title()
  • xmlattr()

Подробнее

Буилд-ин тестирующие ф-и

  • boolean()
  • even()
  • in()
  • mapping()
  • sequence()
  • callable()
  • false()
  • integer()
  • ne()
  • string()
  • defined()
  • filter()
  • iterable()
  • none()
  • test()
  • divisibleby()
  • float()
  • le()
  • number()
  • true()
  • eq()
  • ge()
  • lower()
  • odd()
  • undefined()
  • escaped()
  • gt()
  • lt()
  • sameas()
  • upper()

Подробнее

Глобальные функции

Доступны в глобальном контексте, не только в циклах.

Дополнения

{% for user in users %}
    {%- if loop.index is even %}{% continue %}{% endif %}
    ...
{% endfor %}

{% for user in users %}
    {%- if loop.index >= 10 %}{% break %}{% endif %}
{%- endfor %}

Дополнения можно написать самостоятельно. Пример

Немного трюков jinja

faq

[шаблонизаторы]