Сообщения между запросами в web-приложении на Python и Starlette
programming
В web-приложении на Python и Starlette, процесс разработки которого мы сосредоточенно исследуем в этом цикле статей, уже в ближайшей перспективе появятся обработчики запросов по методу POST с довольно сложной логикой. Вместе с ними, появится необходимость передавать между запросами короткие сообщения о результатах выполнения тех или иных действий. В рамках этого обзора я подготовлю пару вспомогательных функций, с помощью которых эту задачу не составит труда успешно решить.
О задаче
Все читали великолепный манускрип Мигеля о программировании web-сервисов с помощью Python и Flask, все помнят отправку так называемых flash-сообщений из логики запроса по методу POST обработчику запроса по методу GET при переадресации. Там этот функционал реализован уже в рамках самого фреймворка вспомогательной функцией flash.
В Starlette аналогичной вспомогательной функции нет, но при этом в Starlette есть сессия. Именно с её помощью я и собираюсь реализовать функционал, подобный flash сообщениям Flask.
Увы, на текущем этапе разработки приложение пока не имеет ни одного запроса по методу POST, поэтому отладку и тестирование я буду осуществлять в рамках логики обработчика стартовой страницы. Получится в некотором смысле масло маслянное, потому что отправлять сообщение я буду в том же запросе, в котором и принимать, но далее в процессе разработки все составляющие приложения сложатся в гармоничный и продуманный пазл. В общем, приступим...
Разработка вспомогательных функции
Весь процесс решения поставленной задачи сводится к разработке двух вспомогательных функций. С помощью одной из них я собираюсь прикреплять короткое сообщение к сессии запроса. С помощью второй — извлекать приклеплённое к сессии запроса сообщение. Первая функция будет работать в логике обработчика запроса по методу POST, за которым следует переадресация на запрос по методу GET — в его обработчике будет работать вторая функция. Всё просто.
Вспомогательные функции приложения, их будет достаточно обширное множество, я буду использовать во всех подпрограммах, где это необходимо. В структуре каталогов приложения необходимо предусмотреть место для хранения модулей с вспомогательными функциями.
Запускаю терминал, вхожу в корневой каталог приложения и создаю в базовом каталоге приложения ещё один вложенный каталог, даю ему имя common
, с английского переводится, как общий, всеобщий.
$ mkdir webapp/common
Поскольку common
будет пакетом, а пакеты Python я привык обозначать вложенным "инитом", создаю внутри этого каталога новый файл.
$ touch webapp/common/__init__.py
Создаю в текстовом редакторе Vim вложенный в каталог common
файл с именем flashed.py
.
$ vim webapp/common/flashed.py
В этом файле я и собираюсь хранить две целевые вспомогательные функции для обработки (отправки и получения) так называемых flash сообщений. Вот их код с комментариями.
async def get_flashed(request):
## функция получает сообщения из сессии запроса
## проверяю, имеется ли в словаре сессии ключ
## с именем *flashed*
if current := request.session.get('flashed'):
## если условие выполняется, из значения этого ключа,
## которое является списком, получаю все элементы списка
res = [message for message in current]
## удаляю ключ *flashed* из словаря сессии
del request.session['flashed']
## и возвращаю полученный список
return res
async def set_flashed(request, message):
## функция прикрепляет сообщение в сессию запроса
## проверяю наличие в словаре сессии ключа с именем *flashed*
if request.session.get('flashed', None) is None:
## если условие выполняется, в сессии отсутствует искомый ключ,
## создаю список с одним элементом, взятым из второго аргумента функции
## и прикрепляю этот список в словарь функции
request.session['flashed'] = [message]
else:
## если условие не выполняется, добавляю второй аргумент функции
## значение ключа *flashed* словаря сессии запроса
request.session['flashed'].append(message)
Пишу этот код в открытый в текстовом редакторе файл, сохраняю изменения. В командном режиме Vim открываю файл views.py
из каталога подпрограммы main
.
:edit webapp/main/views.py
В этом файле хранится обработчик стартовой страницы — функция представления с именем show_index
. В рамках этой функции я собираюсь разработанные только что вспомогательные функции get_flashed
и set_flashed
протестировать. Для этого подключаю их в этот модуль с помощью процедуры импорта.
from ..common.flashed import get_flashed, set_flashed
В тело функции представления show_index
дописываю процедуру вызова функции set_flashed
.
await set_flashed(request, 'Сайт ещё не готов...')
А в словарь обрабатываемого шаблона добавляю новый ключ — flashed
.
'flashed': await get_flashed(request)
Вот как код этого модуля выглядит в моём тестовом редакторе после всех правок, внесённые правки я выделил красным фломастером, внимание на следующий снимок экрана.
Прямо сейчас в этом коде заложена небольшая каверза. Дело в том, что в текущей инкарнации кода set_flashed
отправляет сообщение, а get_flashed
это сообщение получает в рамках одного обработчика запроса, и это не имеет большого смысла. Просто на текущем этапе разработки я ограничен в ресурсах и не имею ни одного обработчика запросов по методу POST. На следующих этапах разработки все части пазла встанут каждый на своё место, вызов set_flashed
уйдёт в обработчик запроса по методу POST, из которого переадресация приведёт клиента на стартовую страницу. А пока, сохраняю изменения в файл, но не покидаю текстовый редактор, для него есть ещё работа.
Определяем место для сообщений на странице
Замысел разработчика предполагает, что отправленное в обработчике запроса сообщение конечный пользователь должен увидеть на соответствующей странице — в данном случае на стартовой странице приложения. Чтобы это произошло, можно предположить, необходимо внести правки в шаблон стартовой страницы. Но... Сообщения между запросами могут быть получены на любой странице приложения, потенциально я должен предусмотреть возможность получения таких сообщений на всех страницах приложения впоследствии. Поэтому править я буду базовый шаблон.
Открываю файл базового шаблона в текстовом редакторе, сделать это можно опять из командного режима Vim.
:edit webapp/templates/base.html
В коде базового шаблона нахожу тег <div>
с идентификатором page-content
и вписываю в тело этого тега первым отпрыском следующий код.
{% if flashed %}
<div class="top-flashed-block">
{% for message in flashed %}
<div class="flashed-message">
<div class="flashed-alert closeable"
title="close">
{{ message|safe }}
</div>
</div>
{% endfor %}
</div>
{% endif %}
Здесь я использую инструменты Jinja2. Интерпретатор шаблона должен проверить наличие переменной flashed
, эту переменную в шаблон передаёт функция представления. Если переменная с таким именем существует, интерпретатор шаблонов вставит первым отпрыском в page-content
ещё один тег <div>
с классом top-flashed-block
. Далее, в цикле for
для каждого сообщения из списка сообщений, который сформировала функция set_flashed
, интерпретатор шаблонов вставит тег <div>
с классом flashed-message
и вложенный в него тег с классами flashed-alert
и closeable
. На перечисленные классы следует обратить особое внимание, для этих классов уже определены таблицы стилей в шаблоне index.html
.
Внесённые только что правки в моём текстовом редакторе выглядят следующим образом, внимание на снимок экрана.
Шаблон index.html
, поскольку является наследником базового шаблона, получает эту разметку тоже, как и любой другой шаблон, наследующий от base.html
.
На данном этапе уже можно запустить отладочный сервер и протестировать стартовую страницу в браузере. Поскольку добавленные в базовый шаблон классы уже определены в таблицах стилей index.html
, сообщение будет отображаться на странице в полном соответствии с замыслом разработчика.
Определяем события JavaScript
Замысел разработчика предполагает, что пользователь сайта, получив сообщение после переадресации, может закрыть на странице блок с сообщением простым тычком левой кнопки мыши. Клик мыши на блоке сообщения — это событие JavaScript — event. И это событие необходимо определить в коде, чтобы оно сработало на странице, когда пользователь наведёт на сообщение указатель мыши и сделает клик левой кнопкой.
Создаю в текстовом редакторе Vim, его окно всё ещё открыто, новый файл с именем close-topflashed.js
, говорящее имя файла даст возможность другим участникам проекта не заблудиться в коде приложения.
:edit webapp/static/js/close-topflashed.js
В этот файл вписываю следующий код.
function closeTopFlashed() {
let flashed = $(this).parents('.flashed-message');
let next = flashed.next('.flashed-message');
let cond = next.length + flashed.prev('.flashed-message').length;
let ttop = flashed.parents('.top-flashed-block');
flashed.remove();
if (!cond) {
ttop.remove();
}
}
В этом коде я проверяю, имеются ли в теле тега <div>
с классом top-flashed-block
кроме сообщения, по которому сделан клик мышкой, другие сообщения. Если другие сообщения имеются, то функция closeTopFlashed
удалит только блок текущего сообщения, по которому сделан клик. Если других сообщений не имеется, то вместе с блоком текущего сообщения будет удалён ещё и тег <div>
с классом top-flashed-block
.
Код этой функции я написал не из головы, а отладил его, глядя на разметку базового шаблона и определённые в ней теги и их классы, и оперируя инструментами jQuery. Чтобы такая отладка стала возможной, только что созданный файл с именем close-topflashed.js
необходимо внести в список обрабатываемых файлов соответствующего assets
в шаблоне index.html
. Открываю этот файл в текстовом редакторе.
:edit webapp/templates/main/index.html
Нахожу в его коде блок scripts
и в этом блоке блок assets
— в его список файлов добавляю ещё одну строчку. Перенесём внимание на снимок экрана.
Но этого ещё не достаточно. Пока я разработал только определение функции closeTopFlashed
, а чтобы трюк сработал, мне нужен вызов этой функции. Вызов этой функции я привяжу в параметр соответствующего события, определённого с помощью инструментов jQuery в файле index.js
. Открываю этот файл.
:edit webapp/static/js/main/index.js
В код этого файла, в определённое его место я дописываю одну единственную строчку.
$('body').on('click', '.closeable', closeTopFlashed);
В этой строчке я привязал к событию click
(клик левой кнопки мыши) на блоке с классом .closeable
вызов функции closeTopFlashed
. Вот как эта строчка выглядит в моём текстовом редакторе.
Сохраняю в окне текстового редактора Vim все открытые файлы, это можно сделать командой :wa
в его командном режиме.
Как видно из демонстрации, процесс разработки web-приложения довольно запутан, чтобы реализовать одно простое событие, мне пришлось редактировать целую уйму файлов, увы, таковы реалии современной web-разработки. Пришло время протестировать только что разработанный функционал.
Тестируем в браузере
Чтобы протестировать только что разработанный код в браузере, необходимо запустить отладочный сервер в терминале с активным виртуальным окружением. Открою небольшой секрет, мой отладочный сервер был запущен на продолжении редактирования всех файлов в рамках этой демонстрации, и код я не писал, а отлаживал постепенно, допуская ошибки, анализируя и исправляя их. Но будем считать, что отладочный сервер я запускаю вновь, после команды reset в окне терминала.
На снимке экрана выше видно, что отладочный сервер запустился в штатном режиме, в его отчёте отсутствуют сообщения об ошибках, так называемые трейсбэки Python.
Запускаю браузер, в его адресной строке вбиваю адрес стартовой страницы приложения и жму enter
. После того, как страница в окне браузера полностью загрузится и отрисуется, навожу указатель мыши на полученное сообщение.
Как видно на снимке экрана выше, указатель мыши при этом событии изменил свой внешний вид, и рядом с указателем мыши появилась подсказка. Запускаю в окне браузера инструменты разработчика (ctrl+shift+i
), перехожу на вкладку Console
. Мне необходимо убедиться, что клик мышкой на полученном сообщении не породит ошибок JavaScript. Делаю клик левой кнопкой мыши по блоку с полученным сообщением.
Как видно на снимке экрана, в результате этого пользовательского действия блок сообщения исчез, а консоль в панели инструментов разработчика так и осталась "чистой", никаких сообщений об ошибках в ней не появилось. Если перейти на вкладку Elements
, то можно убедиться, что тега <div>
с классом top-flashed-block
в разметке страницы тоже не существует, событие сработало в полном соответствии со сценарием функции closeTopFlashed
. А я констатирую, что все цели этой демонстрации достигнуты в полной мере.
Подводим промежуточный итог
Разрабатываемое приложение в результате описанных в этой демонстрации действий получило новую редакцию стартовой страницы и новую функциональную возможность — можно отправлять короткие сообщения между запросами пользователя. Эту функциональную возможность я буду использовать довольно часто в процессе дальнейшей разработки, в обработчиках каждого запроса по методу POST. Текущую версию кода приложения можно увидеть в моём профиле на github.com по этой короткой ссылке.
На текущий момент разработка web-приложения приблизилась к стадии, когда для реализации замысла программе потребуется обращаться к базе данных. В этом приложении я буду использовать базу данных PostgreSQL, которая уже установлена и настроена на десктопе разработчика соответствующим образом. Продолжение следует.
Напоминаю, что продолжение этого цикла статей возможно, только если в блоге будет активность. Ваши посещения, подписки, лайки, комментарии и донаты имеют большое значение и качественно мотивируют автора, заряжают его соответствующей жизненной силой. Не оставайтесь в стороне, будет интересно...