# Использование Jinja шаблонов
## Введение
* **Проблема:** Жестко закодированный HTML или другие текстовые форматы в приложениях затрудняют динамическое формирование контента и разделение логики представления от бизнес-логики.
* **Решение:** Использование шаблонизаторов, таких как Jinja, для создания динамических текстовых документов (например, HTML, XML, CSV, конфигурационные файлы).
* **Jinja:** Гибкий и мощный шаблонизатор для Python.
## Что такое Jinja?
* **Определение:** Шаблонизатор, позволяющий внедрять динамический контент в статические шаблоны.
* **Основные принципы:**
* **Разделение ответственности:** Отделение логики представления (шаблонов) от бизнес-логики (Python-код).
* **Читаемость:** Синтаксис, разработанный для легкости чтения и понимания.
* **Мощность:** Поддержка переменных, циклов, условных операторов, фильтров, тестов и макросов.
* **Безопасность:** Встроенные механизмы для предотвращения XSS-атак.
## Основные компоненты Jinja шаблонов
1. **Переменные (`{{ ... }}`):**
* Используются для отображения данных, переданных из Python-кода в шаблон.
* Доступ к атрибутам объекта осуществляется через точку (`.`) или квадратные скобки (`[]`).
* **Пример:**
```html+jinja
Привет, {{ user.name }}!
Ваш email: {{ user['email'] }}
```
2. **Операторы (`{% ... %}`):**
* Используются для выполнения логических операций, таких как циклы и условные операторы.
* **Условные операторы (`if`, `elif`, `else`):**
```html+jinja
{% if user.is_logged_in %}
Добро пожаловать!
{% elif user.has_pending_notifications %}
У вас есть непрочитанные уведомления.
{% else %}
Пожалуйста, войдите.
{% endif %}
```
* **Циклы (`for`):**
```html+jinja
{% for item in items %}
- {{ item.name }} - {{ item.price }}
{% empty %}
- Список товаров пуст.
{% endfor %}
```
* `loop` контекстная переменная внутри цикла:
* `loop.index`: Текущий индекс (начиная с 1).
* `loop.index0`: Текущий индекс (начиная с 0).
* `loop.first`: True, если это первая итерация.
* `loop.last`: True, если это последняя итерация.
* `loop.length`: Общее количество элементов.
* `loop.revindex`: Количество оставшихся элементов (начиная с 1).
* `loop.revindex0`: Количество оставшихся элементов (начиная с 0).
* **Наследование шаблонов (`extends`, `block`):**
* **`extends`:** Указывает на родительский шаблон, который будет расширен.
* **`block`:** Определяет области в родительском шаблоне, которые могут быть переопределены в дочерних шаблонах.
* **Родительский шаблон (`base.html`):**
```html+jinja
{% block title %}Мой сайт{% endblock %}
{% block content %}{% endblock %}
```
* **Дочерний шаблон (`index.html`):**
```html+jinja
{% extends 'base.html' %}
{% block title %}Главная страница{% endblock %}
{% block content %}
Добро пожаловать на главную страницу!
Это содержимое главной страницы.
{% endblock %}
```
* **Включения (`include`):**
* Вставляет содержимое другого шаблона в текущий шаблон.
* Полезно для повторного использования фрагментов HTML.
* **Пример:**
```html+jinja
{% include 'navigation.html' %}
```
3. **Фильтры (`{{ variable | filter }}`):**
* Модифицируют значения переменных перед их отображением.
* Применяются с помощью символа `|`.
* Можно применять несколько фильтров последовательно (`{{ variable | filter1 | filter2 }}`).
* **Примеры встроенных фильтров:**
* `capitalize`: Преобразует первую букву в заглавную, остальные в строчные.
* `lower`: Преобразует строку в нижний регистр.
* `upper`: Преобразует строку в верхний регистр.
* `title`: Преобразует первую букву каждого слова в заглавную.
* `trim`: Удаляет пробелы с начала и конца строки.
* `striptags`: Удаляет HTML/XML теги.
* `length`: Возвращает длину последовательности или строки.
* `default(value)`: Возвращает указанное значение, если переменная неопределена.
* `join(separator)`: Объединяет элементы последовательности в строку с указанным разделителем.
* `sort`: Сортирует последовательность.
* `format(value, ...)`: Форматирует строку с использованием оператора `%`.
* `replace(old, new)`: Заменяет все вхождения подстроки `old` на `new`.
* **Пример использования фильтров:**
```html+jinja
{{ message | capitalize }}
Количество элементов: {{ items | length }}
Цена: {{ price | default('Бесплатно') }}
Имена: {{ names | join(', ') }}
```
4. **Тесты (`{% if variable is test %}`):**
* Используются для проверки типа или состояния переменной.
* Применяются с помощью ключевого слова `is`.
* **Примеры встроенных тестов:**
* `defined`: Проверяет, определена ли переменная.
* `undefined`: Проверяет, не определена ли переменная.
* `none`: Проверяет, является ли переменная `None`.
* `number`: Проверяет, является ли переменная числом.
* `string`: Проверяет, является ли переменная строкой.
* `boolean`: Проверяет, является ли переменная булевым значением.
* `iterable`: Проверяет, является ли переменная итерируемым объектом.
* `lower`: Проверяет, является ли строка в нижнем регистре.
* `upper`: Проверяет, является ли строка в верхнем регистре.
* **Пример использования тестов:**
```html+jinja
{% if user.age is number and user.age >= 18 %}
Совершеннолетний
{% else %}
Несовершеннолетний
{% endif %}
{% if items is iterable and items | length > 0 %}
{% for item in items %}
- {{ item.name }}
{% endfor %}
{% else %}
Нет элементов.
{% endif %}
```
5. **Комментарии (`{# ... #}`):**
* Используются для добавления комментариев в шаблон, которые не отображаются в выходном документе.
* **Пример:**
```html+jinja
{# Это комментарий, который не будет виден в HTML #}
Отображаемый текст.
```
6. **Макросы (`{% macro name(param1, param2='default') %}`):**
* Позволяют определять многократно используемые фрагменты шаблонов (похожи на функции).
* Могут принимать аргументы.
* Импортируются с помощью `import` или `from ... import ...`.
* **Определение макроса:**
```html+jinja
{% macro render_user(user) %}
{{ user.name }}
Email: {{ user.email }}
{% endmacro %}
```
* **Использование макроса:**
```html+jinja
{% from 'macros.html' import render_user %}
{{ render_user(current_user) }}
```
## Использование Jinja в Python
1. **Установка:**
```bash
pip install Jinja2
```
2. **Импорт:**
```python
from jinja2 import Environment, FileSystemLoader
```
3. **Создание окружения (`Environment`):**
* Загрузчик (`FileSystemLoader`) указывает, где искать файлы шаблонов.
```python
env = Environment(loader=FileSystemLoader('.')) # Искать шаблоны в текущей директории
# Или указать конкретную папку:
env = Environment(loader=FileSystemLoader('templates'))
```
4. **Загрузка шаблона (`get_template()`):**
```python
template = env.get_template('index.html')
```
5. **Рендеринг шаблона (`render()`):**
* Передача данных в шаблон в виде словаря.
```python
context = {
'user': {'name': 'Иван', 'email': 'ivan@example.com', 'is_logged_in': True},
'items': [{'name': 'Яблоко', 'price': 1.0}, {'name': 'Банан', 'price': 0.5}]
}
rendered_html = template.render(context)
print(rendered_html)
```
## Безопасность Jinja
* **Автоматическое экранирование:** Jinja автоматически экранирует HTML-специальные символы (`<`, `>`, `&`, `'`, `"`) для предотвращения XSS-атак.
* **Отключение экранирования (`| safe` фильтр):** Используйте с осторожностью для переменных, которые вы абсолютно уверены, что содержат безопасный HTML.
```html+jinja
{{ user.formatted_description | safe }}
```
## Лучшие практики
* **Разделение логики:** Передавайте в шаблоны только необходимые данные для отображения. Вся основная бизнес-логика должна оставаться в Python-коде.
* **Использование наследования шаблонов:** Для создания структуры сайта и повторного использования общих элементов.
* **Создание макросов для повторно используемых фрагментов:** Упрощает поддержку и улучшает читаемость шаблонов.
* **Применение фильтров для форматирования данных:** Делайте это непосредственно в шаблонах для логики представления.
* **Осторожное использование `| safe`:** Только когда это действительно необходимо и вы уверены в безопасности HTML-содержимого.
* **Организация файлов шаблонов:** Создавайте логическую структуру директорий для удобства поддержки.
## Заключение
Jinja является мощным инструментом для динамического формирования текстового контента в Python-приложениях. Понимание его синтаксиса и основных возможностей позволяет создавать гибкие, читаемые и безопасные шаблоны, эффективно разделяя логику представления от бизнес-логики.