🐍⛩️🥤 Руководство для начинающих по шаблонам Jinja в Flask

Подробный гайд по шаблонизатору Jinja: от создания первого шаблона, использования условных операторов и for-циклов до применения фильтров и макросов.

Данная статья является переводом. Ссылка на оригинал.

С помощью Jinja вы можете создавать шаблоны, имеющие широкие возможности и обеспечивающие работу интерфейсов веб-приложений Python.

В этом уроке вы узнаете, как:

  • Установить обработчик шаблонов Jinja.
  • Создать свой первый шаблон Jinja.
  • Провести рендеринг шаблона Jinja в Flask.
  • Использовать for-циклы и условные операторы с Jinja.
  • Создать вложенную структуру шаблонов Jinja.
  • Модифицировать переменные в Jinja с помощью фильтров.
  • Использовать макросы для добавления функциональных возможностей интерфейсу.
Исходный код: кликните здесь, чтобы загрузить исходный код, который вы будете использовать для изучения возможностей Jinja.

Начинаем работу с Jinja

Jinja — это не только город в Восточном регионе Уганды и японский храм, но и обработчик шаблонов. Обычно используют обработчики шаблонов для веб-шаблонов, которые получают динамический контент из бэкенда и отображают его как статическую страницу во фронтенде.

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

Устанавливаем Jinja

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

Выберите свою операционную систему ниже и используйте команды для нужной ОС, чтобы настроить виртуальную среду:

Windows:

PS> python -m venv venv
PS> .\venv\Scripts\activate
(venv) PS>

Linux + macOS:

$ python3 -m venv venv
$ source venv/bin/activate
(venv) $

С помощью приведенных выше команд вы создадите и активируете виртуальную среду, названную venv, с помощью встроенного venv-модуля Python. Круглые скобки (()), в которых находится venv, перед приглашением ввода означают, что вы успешно активировали виртуальную среду.

После того как вы создали и активировали виртуальную среду, пришло время установить Jinja с помощью pip:

(venv) $ python -m pip install Jinja2

Не забудьте 2 в конце имени пакета. В противном случае вы установите старую версию, несовместимую с Python 3.

После установки Jinja с заглавной буквой J вы должны импортировать ее со строчной буквой j в Python. Попробуйте это, открыв интерактивный интерпретатор Python и выполнив следующие команды:

>>> import Jinja2
Traceback (most recent call last):
  ...
ModuleNotFoundError: No module named 'Jinja2'

>>> import jinja2
>>> # No error

Когда вы пытаетесь импортировать Jinja2 с заглавной буквой, чтобы установить Jinja, то вы получаете ModuleNotFoundError. То есть чтобы импортировать пакет Jinja в Python, вы должны ввести jinja2 с маленькой буквы j.

🐍🎓 Библиотека собеса по Python
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека собеса по Python»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Рендеринг вашего первого шаблона Jinja

С импортированным Jinja вы можете загрузить и отобразить свой первый шаблон:

>>> import jinja2
>>> environment = jinja2.Environment()
>>> template = environment.from_string("Hello, {{ name }}!")
>>> template.render(name="World")
'Hello, World!'

Основным компонентом Jinja является класс Environment(). В этом примере вы создаете среду Jinja без каких-либо аргументов. Позже вы измените параметры Environment, чтобы настроить свою среду. Здесь вы создаете простую среду, в которую вы загружаете строку "Hello, {{ name }}!" как шаблон.

То, что вы только что сделали, может показаться не более впечатляющим, чем использование форматированной строки в простом Python. Однако в этом примере показаны два важных шага, которые вы обычно выполняете при использовании Jinja:

  1. Загрузка шаблона: загружаем источник, содержащий переменные-заполнители. По умолчанию они заключаются в пару фигурных скобок ({{ }}).
  2. Визуализация шаблона: помещаем содержимое в плейсхолдер. Вы можете передать словарь или аргументы ключевого слова как содержимое. Заполнив плейсхолдер, на выходе вы получаете Hello, World!.

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

Использование внешнего файла в качестве шаблона

Если вы хотите следовать примерам из этого руководства, вы можете создать новую папку для работы. Внутри рабочего каталога создайте папку с именем templates/.

Вы будете хранить все предстоящие шаблоны в templates/folder. Теперь создайте текстовый файл с именем message.txt:

{# templates/message.txt #}

Hello {{ name }}!

I'm happy to inform you that you did very well on today's {{ test_name }}.
You reached {{ score }} out of {{ max_score }} points.

See you tomorrow!
Anke

Представьте, что вы учитель, который хочет отправить результаты хорошо успевающим ученикам. Шаблон message.txt содержит черновик сообщения, который можно скопировать и вставить для отправки позже. Как и в примере с Hello, World!, вы найдете фигурные скобки ({{ }}) в тексте вашего шаблона.

Затем создайте файл Python с именем write_messages.py:

# write_messages.py

from jinja2 import Environment, FileSystemLoader

max_score = 100
test_name = "Python Challenge"
students = [
    {"name": "Sandrine",  "score": 100},
    {"name": "Gergeley", "score": 87},
    {"name": "Frieda", "score": 92},
]

environment = Environment(loader=FileSystemLoader("templates/"))
template = environment.get_template("message.txt")

for student in students:
    filename = f"message_{student['name'].lower()}.txt"
    content = template.render(
        student,
        max_score=max_score,
        test_name=test_name
    )
    with open(filename, mode="w", encoding="utf-8") as message:
        message.write(content)
        print(f"... wrote {filename}")

Когда вы создаете среду Jinja с помощью FileSystemLoader, вы можете передать путь к папке с вашими шаблонами. Вместо того, чтобы передавать строку, теперь вы загружаете message.txt как свой шаблон. Как только ваш шаблон загружен, вы можете использовать его снова и снова, чтобы заполнить его содержимым. В write_messages.py вы передадите name и score для каждого из ваших лучших учеников в текстовый файл.

Обратите внимание, что ключи словаря students, наряду с max_score и test_name, соответствуют переменным шаблона в message.txt. Если вы не передаете контекст для переменных в шаблон, они не выдают ошибку, а отображают пустую строку, что обычно нежелательно.

При вызове template.render() вы возвращаете отображаемый шаблон в виде строки. Как и в случае с любой другой строкой, вы можете использовать .write() для записи в файл. Чтобы увидеть write_messages.py в действии, запустите скрипт:

(venv) $ python write_messages.py
... wrote message_sandrine.txt
... wrote message_gergeley.txt
... wrote message_frieda.txt

Вы только что создали файл для каждого из ваших учеников. Посмотрите message_gergeley.txt, например:

Hello Gergeley!

I'm happy to inform you that you did very well on today's Python Challenge.
You reached 87 out of 100 points.

See you tomorrow!
Anke

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

Управление потоком в Jinja

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

Использование оператора if

В примере из предыдущего раздела вы программно создали настраиваемые сообщения для лучших учеников. Теперь пришло время обратить внимание на всех ваших учеников. Добавьте двух учеников с более низкими баллами в students в write_messages.py:

# write_messages.py

# ...

students = [
    {"name": "Sandrine",  "score": 100},
    {"name": "Gergeley", "score": 87},
    {"name": "Frieda", "score": 92},
    {"name": "Fritz", "score": 40},
    {"name": "Sirius", "score": 75},
]

# ...

Вы добавляете баллы Фрица и Сириуса в список students. В отличие от других студентов, оба набрали меньше восьмидесяти баллов. Используйте данное значение для создания условного оператора в message.txt:

{# templates/message.txt #}

Hello {{ name }}!

{% if score > 80 %}
I'm happy to inform you that you did very well on today's {{ test_name }}.
{% else %}
I'm sorry to inform you that you did not do so well on today's {{ test_name }}.
{% endif %}
You reached {{ score }} out of {{ max_score }} points.

See you tomorrow!
Anke

В дополнение к переменным, которые вы использовали раньше, теперь вы также используете условный оператор с блоком Jinja. Вместо пары двойных фигурных скобок, вы создаете блоки Jinja с одной фигурной скобкой и знаком процента {% %} на каждом конце.

В то время как вы можете думать об обычных переменных как о подстроках, блоки Jinja включают в себя большую часть вашего шаблона. Вот почему вам также нужно сообщить Jinja, где заканчивается ваш блок. Чтобы закрыть блок, вы снова используете те же самые ключевые слова с префиксом end.

В приведенном выше примере вы начинаете блок {% if %} в строке 5, который закрывается в строке 9 с помощью {% endif %}. Сам if-оператор работает как условный оператор в Python. В строке 5 вы проверяете score, выше ли он 80. Если это так, то вы передаете сообщение об успехе. В противном случае вы отображаете «извиняющееся» сообщение в строке 8.

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

Циклы

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

Создайте новый файл с именем results.html в вашем templates/directory:

{# templates/results.html #}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Results</title>
</head>

<body>
  <h1>{{ test_name }} Results</h1>
  <ul>
  {% for student in students %}
    <li>
      <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
    </li>
  {% endfor %}
  </ul>
</body>
</html>

Здесь вы создаете HTML-страницу, которая просматривает ваш словарь students и отображает их баллы. Как и в случае с {% if %} блоками, вы должны убедиться, что вы закрываете свой {% for %} блок с помощью {% endfor %}.

Вы можете комбинировать if-операторы и for-циклы, чтобы еще больше контролировать поток ваших шаблонов:

{# templates/results.html #}

{# ... #}

{% for student in students %}
  <li>
    {% if student.score > 80 %}🙂{% else %}🙁{% endif %}
    <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
  </li>
{% endfor %}

{# ... #}

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

Продолжайте и обновляйте write_messages.py:

# write_messages.py

# ...

results_filename = "students_results.html"
results_template = environment.get_template("results.html")
context = {
    "students": students,
    "test_name": test_name,
    "max_score": max_score,
}
with open(results_filename, mode="w", encoding="utf-8") as results:
    results.write(results_template.render(context))
    print(f"... wrote {results_filename}")

В дополнение к for-циклу, в котором вы сохраняли сообщение для каждого учащегося, теперь вы также пишете один HTML-файл, содержащий все результаты для этого учащегося. На этот раз вы создаете словарь context, содержащий все переменные, которые вы передаете в шаблон.

Примечание
Используйте context в качестве имени коллекции, в которой хранятся согласованные переменные шаблона. Тем не менее, вы можете назвать словарь по-другому, если хотите.

Со словарем, который содержит весь контекст вашего шаблона, вы можете использовать .render() с context в качестве единственного аргумента. При запуске write_messages.py вы также создаете файл HTML:

(venv) $ python write_messages.py
... wrote message_sandrine.txt
... wrote message_gergeley.txt
... wrote message_frieda.txt
... wrote message_fritz.txt
... wrote message_sirius.txt
... wrote students_results.html

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

Как и в сценариях Python, вы можете управлять потоком шаблонов Jinja с помощью if-операторов и for-циклов. В Jinja вы используете блоки для переноса контента. Когда вы используете for-блок, содержимое этого блока отображается на каждом этапе цикла.

С помощью шаблонов вы можете создавать стандартные блоки для больших веб-сайтов, не дублируя код фронтенда. Вот почему такие веб-фреймворки, как Flask, используют возможности шаблонов Jinja. В следующем разделе вы узнаете, как использовать Flask для рендеринга данных из серверной части на веб-страницы клиентской части.

Используйте Jinja с Flask

Скорее всего, вы впервые услышали о Jinja, когда использовали веб-фреймворк вроде Flask. И Jinja, и Flask поддерживаются Pallets Project, организацией, которая заботится о библиотеках Python, лежащих в основе веб-фреймворка Flask.

В этом разделе вы продолжите работу над приведенными выше примерами, создав простое веб-приложение для своих учащихся.

Установка Flask

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

(venv) $ python -m pip install flask

Установив Flask, создайте свой первый маршрут обработки запросов, чтобы убедиться, что Flask работает должным образом. Создайте файл с именем app.py в корневом каталоге вашего проекта:

# app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, World!"

if __name__ == "__main__":
    app.run(debug=True)

Когда вы помещаете @app.route() декоратор поверх функции представления Flask , вы регистрируете его с заданным правилом URL . Здесь вы устанавливаете маршрут через /, который возвращает Hello, World!.

Чтобы увидеть свою домашнюю страницу в браузере, запустите веб-сервер разработки Flask:

(venv) $ python app.py
...
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with watchdog (fsevents)
 * Debugger is active!

Теперь ваше приложение Flask работает в debug-режиме: вы будете получать более содержательные сообщения об ошибках, если что-то пойдет не так. Кроме того, ваш сервер будет автоматически перезагружаться всякий раз, когда вы что-то меняете в кодовой базе.

Чтобы увидеть свою домашнюю страницу, посетите http://127.0.0.1:5000:

Отлично, теперь у вас есть работающее приложение Flask! В следующем разделе вы внедрите шаблоны Jinja в свое приложение Flask.

Добавляем базовый шаблон

Пока ваше приложение Flask возвращает только строку. Вы можете улучшить свою строку, добавив HTML-код, и Flask отобразит ее для вас. Но, как вы узнали из предыдущего раздела, использование шаблонов значительно упрощает отображение содержимого.

Создайте новый шаблон с именем base.html в вашем templates/directory:

{# templates/base.html #}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ title }}</title>
</head>

<body>
  <h1>Welcome to {{ title }}!</h1>
</body>
</html>

В base.html, у вас есть два случая, когда вы используете переменные шаблона: один раз в строке 7, а затем снова в строке 11. Чтобы отображать и обслуживать base.html, загрузите ее как домашнюю страницу в app.py:

# app.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def home():
    return render_template("base.html", title="Jinja and Flask")

# ...

По умолчанию Flask ожидает, что ваши шаблоны находятся в templates/directory. Поэтому вам не нужно явно указывать каталог шаблона. Когда вы прописываете base.html в render_template(), Flask знает, где искать ваш шаблон.

Примечание
Обычно вы улучшаете внешний вид своего веб-сайта с помощью CSS и добавляете некоторые функции с помощью JavaScript. Поскольку в этом туториале основной упор делается на структуру контента, ваше веб-приложение останется в основном без стилей.

Перезапустите сервер разработки Flask, если он еще не обновился автоматически. Затем перейдите по адресу http://127.0.0.1:5000 и убедитесь, что Flask выполняет ваш базовый шаблон и отображает его:

Flask отобразил переменную title в заголовке вашего сайта и в приветственном сообщении. Далее вы создадите страницу, чтобы показать результаты ваших учеников.

Добавление дополнительной страницы

В одном из предыдущих разделов вы использовали results.html в качестве шаблона для создания файла с именем students_results.html. Теперь, когда у вас есть веб-приложение, вы можете использовать results.html для динамического рендеринга шаблона, на этот раз не сохраняя его в новый файл.

Убедитесь, что results.html размещен в templates/ и выглядит так:

{# templates/results.html #}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{ title }}</title>
</head>

<body>
  <h1>{{ test_name }} {{ title }}</h1>
  <ul>
  {% for student in students %}
    <li>
      {% if student.score > 80 %}🙂{% else %}🙁{% endif %}
      <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
    </li>
  {% endfor %}
  </ul>
</body>
</html>

Единственные изменения в более ранней версии выделены выше:

  1. Строка 7 добавляет динамический заголовок страницы.
  2. Строка 11 увеличивает первый заголовок.

Чтобы получить доступ к странице результатов в веб-приложении, необходимо создать путь. Добавьте следующий код в app.py:

# app.py

# ...

max_score = 100
test_name = "Python Challenge"
students = [
    {"name": "Sandrine",  "score": 100},
    {"name": "Gergeley", "score": 87},
    {"name": "Frieda", "score": 92},
    {"name": "Fritz", "score": 40},
    {"name": "Sirius", "score": 75},
]

@app.route("/results")
def results():
    context = {
        "title": "Results",
        "students": students,
        "test_name": test_name,
        "max_score": max_score,
    }
    return render_template("results.html", **context)

# ...

В полноценном веб-приложении вы, вероятно, будете хранить данные во внешней базе данных . На данный момент вы держите max_score, test_name и students рядом с results().

Flask render_template() принимает только один позиционный аргумент, которым является имя шаблона. Любые другие аргументы должны быть аргументами ключевого слова. Таким образом, вы должны распаковать словарь с двумя звездочками (**) перед context. С операторами звездочки вы передаете элементы context в качестве аргументов ключевого слова в render_template().

Когда вы посещаете адрес http://127.0.0.1:5000/results через свой браузер, Flask открывает results.html и показывает его содержимое (контекст). Откройте в браузере, и вы увидите:

Теперь у вас есть домашняя страница и страница, на которой показаны результаты ваших учеников. Отличное начало!

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

Вложение шаблонов

По мере роста приложения и добавления новых шаблонов вы должны синхронизировать общий код. На данный момент два шаблона base.html и results.html выглядят очень похожими. Когда несколько шаблонов содержат один и тот же код, при изменении какой-либо части общего кода вам все равно необходимо настраивать каждый шаблон.

В этом разделе вы реализуете структуру родительского и дочернего шаблонов, что значительно упростит сопровождение вашего кода.

Настраиваем базовый шаблон

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

Ваш base.html почти готов служить базовым шаблоном. Чтобы сделать ваш базовый шаблон расширяемым, добавьте {% block %} в структуру:

{# templates/base.html #}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{% block title %}{{ title }}{% endblock title %}</title>
</head>

<body>
  {% block content %}
    <h1>Welcome to {{ title }}!</h1>
  {% endblock content %}
</body>
</html>

Вы используете {% block %}, чтобы определить, какие части вашего базового шаблона могут быть переопределены дочерними шаблонами. Как и в случае с {% if %} и {% for %}, вы должны закрывать свои блоки с помощью {% endblock %}.

Обратите внимание, что вы также называете свои блоки. С помощью аргумента title вы позволяете дочернему шаблону заменять код между {% block title %} и {% endblock title %} своим собственным блоком title. Вы можете заменить код {% block content %} блоком content.

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

Содержимое, которое содержится в {% block %} в base.html, является заполнителем. Резервное содержимое отображается всякий раз, когда дочерний шаблон не содержит соответствующих тегов {% block %}.

Вы также можете решить не добавлять резервный контент между {% block %}. Как и в случае с переменными в шаблонах, Jinja не будет жаловаться, если вы не предоставите для них содержимое. Вместо этого, Jinja отобразит пустую строку.

В base.html вы предоставляете резервный контент для блоков шаблонов. Поэтому вам не нужно ничего менять в представлении home(). Он будет работать так же, как и раньше.

В следующем разделе вы подготовите дочерний шаблон для работы с файлами base.html.

Расширение дочерних шаблонов

В настоящее время у вас есть шаблон results.html, который работает независимо, без родительского шаблона. Это означает, что теперь вы можете настроить код в results.html, чтобы связать его с base.html:

{# templates/results.html #}

{% extends "base.html" %}

{% block content %}
<h1>{{ test_name }} {{ title }}</h1>
<ul>
{% for student in students %}
  <li>
    {% if student.score > 80 %}🙂{% else %}🙁{% endif %}
    <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
  </li>
{% endfor %}
</ul>
{% endblock content %}

Чтобы связать дочерний шаблон с родительским, необходимо добавить {% extends %} вверху файла.

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

Обратите внимание, что results.html не содержит блока title. При этом ваша страница по-прежнему будет отображать правильный заголовок, потому что она использует резервный контент base.html, а отображение визуала обеспечивается переменной title.

Вам не нужно настраивать визуализацию в results(). При посещении http://127.0.0.1:5000/results вы не заметите никаких изменений. Визуализированная страница содержит основной код base.html и заполнители из блока results.html.

Имейте в виду, что любой контент за пределами блоков дочернего шаблона не будет отображаться на вашей странице. Например, если вы хотите добавить навигацию по results.html и ссылку на домашнюю страницу, вам нужно либо определить новый блок-заполнитель в base.html, либо добавить меню навигации в структуру base.html.

Подключаем меню навигации

Навигация веб-сайта обычно отображается на каждой странице. С вашей базовой и дочерней структурой шаблона лучше всего добавить код меню навигации в файл base.html.

Также вместо добавления кода меню навигации непосредственно в base.html, вы можете использовать тег {% include %}. Ссылаясь на другой шаблон с помощью {% include %}, вы загружаете весь шаблон в эту позицию.

Включенные шаблоны — это частичные версии, которые содержат часть полного HTML-кода. Чтобы указать, что шаблон должен быть включен, вы можете поставить перед его именем знак подчеркивания (_).

Создайте новый шаблон с именем _navigation.html в своей папке в templates/folder:

{# templates/_navigation.html #}

<nav>
{% for menu_item in ["home", "results"] %}
  <a href="{{ url_for(menu_item) }}">{{ menu_item }}</a>
{% endfor %}
</nav>

Обратите внимание, что _navigation.html не содержит ни {% extends %}, ни каких -либо {% block %}. Вы можете сосредоточиться исключительно на способе отображения меню навигации.

Когда вы используете url_for(), Flask создает для вас полный URL-адрес данного представления. Так что, даже когда вы решите изменить маршрут к одной из ваших страниц, меню навигации все равно будет работать.

Добавьте _navigation.html в base.html, чтобы отобразить меню навигации на всех ваших страницах:

{# templates/base.html #}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{% block title %}{{ title }}{% endblock title %}</title>
</head>

<body>
  <header>
    {% include "_navigation.html" %}
  </header>
  {% block content %}
    <h1>Welcome to {{ title }}!</h1>
  {% endblock content %}
</body>
</html>

Вместо того чтобы добавлять код меню навигации непосредственно в base.html, вы добавляете _navigation.html в заголовок своего веб-сайта. Так как results.html расширяет base.html, вы можете перейти по адресу http://127.0.0.1:5000/results, чтобы проверить новое меню навигации:

Ваша страница результатов наследует код вашего базового шаблона. Когда вы щелкаете на ссылку в меню навигации, URL-адрес в адресной строке меняется в зависимости от вашей текущей страницы.

Применение фильтров

Найдите минутку и подумайте о ситуации, когда другой человек заботится о бэкенде, а вы отвечаете за фронтенд веб-сайта. Чтобы не мешать друг другу, вам не разрешено настраивать какие-либо представления app.py или изменять данные, поступающие в ваши шаблоны.

После разговора со своими учениками вы соглашаетесь с тем, что ваше веб-приложение можно улучшить. Вы хотите реализовать две функции со следующим функционалом:

  1. Отображать элементы меню навигации в верхнем регистре.
  2. Отсортировать имена учеников в results.html в алфавитном порядке.

Вы будете использовать функциональность фильтра Jinja для реализации обеих функций, не касаясь серверной части.

Настраиваем пункты меню

Jinja предоставляет множество встроенных фильтров. Если вы посмотрите на них, то заметите, что они похожи на встроенные функции и строковые методы Python.

Прежде чем продолжить, просмотрите свой _navigation.html:

{# templates/_navigation.html #}

<nav>
{% for menu_item in ["home", "results"] %}
  <a href="{{ url_for(menu_item) }}">{{ menu_item }}</a>
{% endfor %}
</nav>

В настоящее время элементы вашего меню отображаются строчными буквами, чтобы соответствовать имени представления. Если бы menu_item не соответствовал бы этому условию, то ссылка не работала бы. Это означает, что menu_item в атрибуте href должен оставаться таким, какой он есть.

Однако вы можете настроить отображение menu_item внутри <a> тегов:

{# templates/_navigation.html #}

<nav>
{% for menu_item in ["home", "results"] %}
  <a href="{{ url_for(menu_item) }}">{{ menu_item|upper }}</a>
{% endfor %}
</nav>

Вы указываете переменную, а затем символ вертикальной черты (|), за которым следует фильтр. В некоторых случаях вы можете указать аргументы в круглых скобках.

В меню навигации вы передаете переменную menu_item фильтру upper. Подобно .upper(), строковому методу Python, фильтр Jinja upper возвращает переменную в верхнем регистре.

Зайдите на http://127.0.0.1:5000, чтобы увидеть свои изменения вживую:

Отлично, пункты меню теперь в верхнем регистре! Вы реализовали первую функцию из списка. Время заняться следующей функцией.

Отсортируйте список результатов

На данный момент результаты ваших учеников отображаются в том же порядке, в котором вы определили их в словаре в app.py. Вы будете использовать фильтр Jinja sort для сортировки списка результатов по ученикам в алфавитном порядке.

Откройте results.html и добавьте фильтр sort в свой for-цикл:

{# templates/results.html #}

{# ... #}

{% for student in students|sort(attribute="name") %}
  <li>
    {% if student.score > 80 %}🙂{% else %}🙁{% endif %}
    <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
  </li>
{% endfor %}

{# ... #}

Фильтр sort использует Python sorted() с точки зрения внутренней структуры.

students в results.html содержит словарь для каждого элемента. Добавляя attribute=name, вы указываете Jinja сортировать students на основе значений name. Вы можете использовать фильтр sort без каких-либо аргументов, когда хотите отсортировать списки строк.

Перейдите на http://127.0.0.1:5000/results и ознакомьтесь с результатами новой сортировки:

Вы использовали фильтр Jinja sort, чтобы отсортировать результаты ваших студентов по их именам. Если у вас есть ученики с одинаковым именем, вы можете связать фильтры:

{% for student in students|sort(attribute="name")
    |sort(attribute="score", reverse=true) %}

  {# ... #}

{% endfor %}

Сначала вы сортируете своих студентов по именам. Если есть два студента с одинаковым именем, вы сортируете их по баллам. Конечно, учащимся с одинаковыми именами на самом деле потребуется некоторая дифференциация, помимо оценок, но для примера такой сортировки достаточно.

Теперь вы будете использовать score, чтобы отсортировать студентов от самого высокого до самого низкого балла. Когда строки становятся слишком длинными, Jinja позволяет вам распределять ваши выражения по нескольким строкам.

Примечание
В отличие от использования логических значений в Python, логические значения в Jinja следует писать строчными буквами.

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

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

Если вы хотите добавить еще больше логики в свои шаблоны, вы можете использовать макросы. В следующем разделе вы изучите макросы, реализовав еще три функции в своем приложении Flask.

Подключение макросов

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

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

В этом разделе вы добавите в свой проект Flask еще три функции:

  1. Реализация темного режима.
  2. Выделение студента с лучшим результатом.
  3. Помещение текущей страницы в меню навигации.

Как и раньше, вы не будете трогать внутренний код для улучшения своего веб-приложения.

Реализация темного режима

Некоторым учащимся цветовая схема «светлое на темном» кажется более привлекательной. Чтобы удовлетворить потребности всех учащихся, вы добавите возможность переключения в темный режим со светлым текстом на темном фоне.

Добавьте файл macros.html в templates/directory:

{# templates/macros.html #}

{% macro light_or_dark_mode(element) %}
  {% if request.args.get('mode') == "dark" %}
    <a href="{{ request.path }}">Switch to Light Mode</a>
    <style>
      {{ element }} {
        background-color: #212F3C;
        color: #FFFFF0;
      }
      {{ element }} a {
        color: #00BFFF !important;
      }
    </style>
  {% else %}
    <a href="{{ request.path }}?mode=dark">Switch to Dark Mode</a>
  {% endif %}
{% endmacro %}

Вы определяете макрос с блоком {% macro %}, напоминающим определение функции в Python. У вашего макроса должно быть имя, он также может принимать аргументы.

Для макроса light_or_dark_mode() необходимо указать имя элемента HTML. Это будет элемент, который CSS в строках с 6 по 14 меняет со светлого на темный.

Чтобы избежать использования веб-сайта с темной тематикой по умолчанию, вы хотите дать своим ученикам возможность переключать темы. Когда они добавляют ?mode=dark к любому маршруту обработки запросов приложения, вы активируете темный режим.

В строке 4 вы используете объект Flask request для чтения параметров запроса. Объект request присутствует в контексте вашего шаблона по умолчанию.

Если параметр темного режима существует в GET-запросе, то вы показываете ссылку с возможностью переключения в светлый режим в строке 5 и добавляете тег style в шаблон. Без каких-либо параметров темного режима вы показываете ссылку, которая переключает на темную тему.

Чтобы использовать ваши макросы, вы должны импортировать их в свой базовый шаблон. Как и в случае с выражением Python import, рекомендуется размещать блок {% import %} в верхней части шаблона:

{# templates/base.html #}

{% import "macros.html" as macros %}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{% block title %}{{ title }}{% endblock title %}</title>
</head>

<body>
  {# ... #}
  <footer>
    {{ macros.light_or_dark_mode("body") }}
  </footer>
</body>
</html>

В зависимости от параметров GET-запроса вы отображаете темный режим и показываете ссылку для переключения на другую цветовую тему.

Указав body в качестве аргумента macros.light_or_dark_mode(), ваши ученики могут переключать цветовую схему всей страницы. Посетите адрес http://127.0.0.1:5000 и поэкспериментируйте с переключателем цветовой схемы:

Вы можете попробовать использовать "h1" вместо "body" в macros.light_or_dark_mode(), а затем перезагрузить страницу. Поскольку макросы принимают аргументы, они дают вам возможность условно отображать части ваших шаблонов.

Выделение студента с лучшим результатом

Еще одна причина ввести макросы в ваш проект Flask — поместить некоторую логику в отдельный раздел. Как и вложенные шаблоны, аутсорсинг функциональности по отношению к макросу может привести в порядок ваш родительский шаблон.

Чтобы выделить лучшего ученика смайликом в виде звезды, очистите макрос results.html и сделайте ссылку на макро add_badge():

{# templates/results.html #}

{# ... #}

{% for student in students|sort(attribute="name") %}
  <li>
    {{ macros.add_badge(student, students) }}
    <em>{{ student.name }}:</em> {{ student.score }}/{{ max_score }}
  </li>
{% endfor %}

{# ... #}

Обратите внимание, что вы не импортируете macros.html в верхней части файла results.html. Вы расширяете base.html, где вы уже импортировали все макросы. Так что нет необходимости импортировать их сюда снова.

Вместо добавления дополнительного кода к элементу списка в results.html, вы ссылаетесь на macros.add_badge() внутри своего for-цикла. Вы пользуетесь возможностью и удаляете условие ifelse, которое отображало счастливый или грустный смайлик. Этот код идеально подходит для добавления значка всем учащимся.

Макрос add_badge() будет ожидать два аргумента:

  1. Текущий словарь student.
  2. Полный список students.

Если бы вы посетили свою страницу сейчас, вы бы получили сообщение об ошибке, потому что Flask не может найти макрос, на который вы ссылаетесь. Продолжайте и добавьте свой новый макрос в macros.html:

{# templates/macros.html #}

{# ... #}

{% macro add_badge(student, students) %}
  {% set high_score = students|map(attribute="score")|max %}

  {% if student.score == high_score %}
    ⭐️
  {% elif student.score > 80 %}
    🙂
  {% else %}
    🙁
  {% endif %}
{% endmacro %}

Jinja позволяет вам определять свои собственные переменные внутри шаблона с блоком {% set %}. Когда вы определяете переменные, вы также можете добавлять фильтры к их значениям и даже связывать их.

В add_badge вы определяете high_score, сначала создавая список всех оценок с помощью фильтра map(), а затем выбирая наивысшую оценку с помощью max(). Оба фильтра ведут себя аналогично функциям Python map() или max().

Как только вы узнаете самый высокий балл среди своих учеников, вы сравниваете его с баллом вашего текущего ученика в строках с 8 по 14.

Перейдите на http://127.0.0.1:5000/results и посмотрите на свой новый макрос в действии:

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

Помещаем текущую страницу в меню навигации

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

Добавьте еще один макрос в macros.html:

{# templates/macros.html #}

{# ... #}

{% macro nav_link(menu_item) %}
  {% set mode = "?mode=dark" if request.args.get("mode") == "dark" else "" %}
  <a href="{{ url_for(menu_item) }}{{ mode }}">{{ menu_item|upper }}</a>
  {% if request.endpoint == menu_item %}
    ←
  {% endif %}
{% endmacro %}

Ваш макрос nav_link() принимает пункт меню в качестве аргумента. Если menu_item совпадает с текущей конечной точкой, ваш макрос отображает стрелку в дополнение к ссылке на пункт меню.

Кроме того, вы проверяете цветовой режим. Если "dark"это часть запроса GET, то ссылка "?mode=dark" будет добавлена ​​в меню. Без проверки и добавления режима вы будете переключаться на светлую тему каждый раз, когда нажимаете на ссылку, потому что "?mode=dark" не будет частью ссылки.

Замените ссылку пункта меню на новый макрос _navigation.html:

{# templates/_navigation.html #}

<nav>
{% for menu_item in ["home", "results"] %}
  {{ macros.nav_link(menu_item) }}
{% endfor %}
</nav>

Добавляя макрос nav_link() в меню навигации, вы сохраняете чистоту шаблона навигации. Вы передаете любую условную логику nav_link().

Перейдите на http://127.0.0.1:5000 и посмотрите на все функции, которые вы реализовали:

Макросы — мощная функция Jinja. Тем не менее не следует злоупотреблять ими. В некоторых ситуациях может быть лучше поместить логику в серверную часть вместо того, чтобы позволить вашим шаблонам делать всю работу.

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

Вывод

Jinja — это многофункциональный движок шаблонов, входящий в состав веб-фреймворка Flask. Но вы также можете использовать Jinja независимо от Flask для создания шаблонов, которые можно программно наполнять содержимым.

***

Материалы по теме

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

admin
11 декабря 2018

ООП на Python: концепции, принципы и примеры реализации

Программирование на Python допускает различные методологии, но в его основе...
admin
28 июня 2018

3 самых важных сферы применения Python: возможности языка

Существует множество областей применения Python, но в некоторых он особенно...
admin
13 февраля 2017

Программирование на Python: от новичка до профессионала

Пошаговая инструкция для всех, кто хочет изучить программирование на Python...