Подключаем Jinja2 в web-приложение на Python и Starlette

programming

Опубликован:
2024-11-29T02:41:08.395806Z
Отредактирован:
2024-11-29T02:41:08.395806Z
Статус:
публичный
31
0
0

Предполагается, что web-приложение на Python и Starlette, разработку которого мы исследуем в этом цикле статей, на запросы клиентов по некоторым своим ключевым url-адресам будет отвечать HTML-страницами. В этой демонстрации я покажу подключение Jinja2 в ASGI-приложение с помощью соответствующих инструментов Starlette, создам в структуре каталогов приложения каталог для хранения шаблонов и, для начала, перепишу обработчик стартовой страницы с использованием шаблона Jinja2. Напоминаю, что весь цикл статей в хронологическом порядке доступен по метке webapp, последовательность имеет значение, рекомендую читать с начала цикла.

О Jinja2

Jinja2 — это современный, дружественный к разработчику интерпретатор шаблонов для языка программирования Python. С его помощью можно интерпретировать как шаблоны любые текстовые файлы. При этом специализированный синтаксис Jinja2 даёт возможность использовать различные логические блоки уже на уровне шаблона. Подробная документация для разработки шаблонов доступна на официальном сайте, первая ссылка в этом абзаце ведёт к ней.

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

Восстанавливаю окружение

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

Запускаю терминал и вхожу в корневой каталог web-приложения, в терминале это можно сделать с помощью команды cd.

$ cd ~/sites/website

Активирую виртуальное окружение, которое расположено во вложенном в корневой каталог каталоге с именем venv.

$ source venv/bin/activate

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

Установка Jinja2

Jinja2 является сторонней библиотекой. Чтобы использовать инструменты этой библиотеки, её необходимо установить в активное виртуальное окружение. Сделать это можно с помощью менеджера Pip вот такой простой командой.

$ pip install jinja2

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

aY2bsfRFK9.png

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

Привязка каталога для хранения шаблонов

В предыдущей демонстрации этого цикла статей я создал структуру каталогов для хранения файлов с кодом этого приложения, и на текущий момент в этой структуре пока не предусмотрено каталога для хранения шаблонов. Такой каталог необходимо создать. Его имя будет определяться целевым назначением — templates в данном случае, и расположен он будет в базовом каталоге приложения. Вместе с ним я создам вложенный в него каталог с именем main для хранения шаблонов главной подпрограммы приложения. Реализовать задуманное мне поможет команда mkdir.

$ mkdir -p webapp/templates/main

Созданный таким образом каталог необходимо интегрировать в объект ASGI-приложения, для этого адрес каталога в файловой системе я опять выражу в терминах Python с помощью соответствующих инструментов.

Открываю в текстовом редакторе Vim файл dirs.py из базового каталога.

$ vim webapp/dirs.py

В этот файл я впишу одну единственную строчку. Вот как она выглядит.

templates = os.path.join(base, 'templates')

Объект templates является адресом каталога templates в файловой системе, расположить этот объект можно в конце файла, либо между объектами base и settings, которые уже существуют. Сохраняю изменения в файл, но покидать текстовый редактор пока не стоит, предстоит правка ещё нескольких файлов.

Интегрируем Jinja2 в ASGI приложение

Конфигурация ASGI приложения хранится в "ините" базового каталога. Соответственно, чтобы интегрировать Jinja2, необходимо отредактировать определённым образом этот "инит". Поскольку в текущей сессии разработки текстовый редактор Vim уже запущен, открываю файл прямо в командном режиме Vim.

:edit webapp/__init__.py

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

import jinja2
import typing

В данном случае jinja2 — это главный модуль библиотеки Jinja2, которую я только что установил в виртуальное окружение. Второй импорт можно считать вспомогательным, составляющие модуля typing далее будут упомянуты в аннотациях некоторых новых объектов.

Starlette — достаточно эффективный и всеобъемлющий web-фреймворк общего назначения, и в нём в том числе предусмотрены собственные инструменты для интеграции Jinja2. Вписываю в файл ещё одну процедуру импорта.

from starlette.templating import Jinja2Templates

В модуле dirs.py появился новый объект с именем templates, и он мне будет необходим уже на этой стадии редактирования кода конфигурации ASGI. Дополняю соответствующую процедуру импорта.

from .dirs import settings, templates

Поскольку класс Jinja2Templates из арсенала Starlette необходимо будет некоторым образом преобразовать и дополнить, создаю на его основе новый класс с именем J2Templates, делаю его наследником и перегружаю метод _create_env.

DI = '''typing.Union[str, os.PathLike[typing.AnyStr],
typing.Sequence[typing.Union[str,
os.PathLike[typing.AnyStr]]]]'''.replace('\n', ' ')


class J2Templates(Jinja2Templates):
    def _create_env(
            self,
            directory: DI, **env_options: typing.Any) -> "jinja2.Environment":
        loader = jinja2.FileSystemLoader(directory)
        env_options.setdefault("loader", loader)
        env_options.setdefault("autoescape", True)
        env = jinja2.Environment(**env_options)
        return env

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

Класс с именем StApp был создан на предыдущем этапе разработки, он уже присутствует в файле. В метод __call__ этого класса дописываю инициализацию ещё одной переменной с именем self.jinja, которая в свою очередь будет являться экземпляром класса J2Templates.

class StApp(Starlette):
    async dev __call__(
           self, scope: Scope, receive: Receive, send: Send) -> None:
        ...
        self.jinja = J2Templates(directory=templates)
        ...

Здесь я привязываю созданный чуть ранее каталог templates, именно в нём производная Jinja2 будет искать файлы шаблонов. Остальные хранящиеся в этом файле объекты оставляю без изменения. Вот как внесённые правки выглядят в окне моего текстового редактора.

RJ4yCF8dLO.png

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

Использование шаблонов Jinja2

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

Открываю в текстовом редакторе модуль views.py главной подпрограммы приложения. Вот такой командой это можно сделать в командном режиме Vim.

:tabnew webapp/main/views.py

Удаляю из этого файла процедуру импорта и переписываю функцию представления show_index следующим образом.

async def show_index(request):
    return request.app.jinja.TemplateResponse(
        'main/index.html',
        {'request': request})

В аргументах каждой функции представления присутствует объект request. В одном из свойств этого объекта хранится ASGI приложение, определённое в "ините" базового каталога и переданное параметром отладочному серверу. И с этого конкретного момента в одном из свойств объекта app хранится экземпляр jinja с вложенными в него объектами. Таким образом, через request я могу получить доступ к любому инструментарию данного конкретного ASGI приложения. Именно это я и сделал в этой функции представления — вернул экземпляр класса TemplateResponse с соответствующими параметрами. Первый параметр указывает на шаблон, который следует использовать. Второй параметр является обычным словарём, и с его помощью я могу передавать в шаблон дополнительные переменные. Обязательным элементом этого словаря является ключ request с соответствующим значением. Сохраняю изменения в файл, но остаюсь в окне текстового редактора.

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

:split webapp/templates/main/index.html

Обратите внимание, что в функции представления я указал адрес шаблона как main/index.html, а в команде текстового редактора я указал относительный путь, начиная от базового каталога приложения. В этот файл я впишу самый обычный HTML-код, но, поскольку в данном перформансе мы имеем дело с Jinja2, а логика функции представления, учитывая начальную стадию разработки, довольно скудна, я всё же не могу не продемонстрировать в этом шаблоне некоторые представляемые шаблонизатором инструменты.

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-COMPATIBLE" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Website Index Page</title>
  </head>
  <body>
    <div>Сайт в стадии разработки, попробуйте зайти позже.</div>
    {% if request.app.config.get('DEBUG') == 'True' %}
      <div>В приложении включен отладчик.</div>
    {% endif %}
  </body>
</html>

В этом шаблоне я использовал блок {% if ... %}{% endif %}. С помощью этого блока я буду отображать на странице расположенный в рамках этого блока тег <div> и текст в нём, или не отображать, в зависимости от значения поля DEBUG файла настроек приложения .env, доступ к которому внутри шаблона получу с помощью переданного в шаблон объекта request.

Вот как только что отредактированные файлы выглядят в окне моего текстового редактора.

38tiUKlwAZ.png

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

UFCmcgG0K0.png

На снимке экрана выше видно, что на текущий момент в приложении появился новый формат файлов — HTML, а хранение файлов с исходным кодом упорядочено по типу и назначению. Шаблоны c HTML-кодом — это место где backend пересекается с frontend, и об этом мы поговорим чуть позже уже в ближайшей перспективе...

Тестируем новые возможности

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

$ python runserver.py

nbqkVETMoc.png

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

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

Z19NdsRz3e.png

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

Хоть логика обработчика страницы на текущем этапе довольно скудна, тем не менее, использованный шаблон имеет собственный логический блок, и его следует протестировать тоже. Останавливаю отладочный сервер. Редактирую файл .env из корневого каталога приложения в текстовом редакторе. В этом файле изменяю значение поля DEBUG с True на False, сохраняю изменения в файл, покидаю текстовый редактор и вновь запускаю отладочный сервер. Как теперь браузер отобразит стартовую страницу приложения..? Иду в окно браузера и обновляю страницу.

Y0PRfml5To.png

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

Подводим промежуточный итог

Web-приложение в самом общем случае довольно сложный продукт с весьма трудоёмким, длительным и запутанным процессом разработки, оно включает в себя серверный код, клиентский код, дизайн, вёрстку и прочие непонятные неосведомлённому пользователю составляющие. В этой статье я описал процесс подключения интерпретатора шаблонов Jinja2 в web-приложение на языке программирования Python и Starlette. С этого момента в приложении появились хранящиеся в отдельном каталоге HTML-файлы, а разработчик получил возможность конструировать для страниц web-приложения шаблоны с разнообразными логическими блоками. Увы, на текущий момент я пока не могу приступить к полноценной вёрстке стартовой страницы, в приложении отсутствуют статические файлы — именно они станут главной целью следующего этапа разработки.

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