Пишем web на Python3, создаём базовый шаблон приложения
newbie
Опубликован: | 2019-02-14T10:51:08.850991Z |
Отредактирован: | 2019-02-18T09:45:09.504288Z |
Продолжаем работу над проектом selfish. В этом выпуске блога рассмотрим создание базового шаблона приложения, продолжим разбираться с кодом приложения, работающим на стороне клиента, и напишем первую функциональную страницу selfish с использованием сторонних библиотек, файлы которых были прикреплены к проекту в одном из предыдущих выпусков блога, посвященных selfish.
Новым участникам обсуждения следует иметь ввиду, что совсем недавно проект был успешно перенесён в новое окружение Debian buster и теперь серверный код приложения будет основан на Python3.7.2 и PostgreSQL версии 10.
Разработка клиентского кода предполагает необходимость и возможность группирования файлов с кодом (таблиц стилей и JavaScript-сценариев) с учётом повторяемости тех или иных функций на разных страницах web-приложения, принцип DRY никто не отменял. Кроме этого, клиентский код нужно отлаживать и тестировать, это значит, что selfish требует дополнительной конфигурации, которая будет учитывать методологию разработки. Группировать таблицы стилей и JS я буду при помощи webassets, для этого мне потребуется соответствующее расширение Flask.
Вхожу в базовый каталог приложения и активирую виртуальное окружение.
cd ~/workspace/selfhis source venv/bin/activate
Вся работа в терминале далее будет предполагать, что я нахожусь в базовом каталоге проекта с активированным виртуальным окружением.
Устанавливаю Flask-Assets.
pip install Flask-Assets
Для минимизации клиентского кода потребуются соответствующие фильтры. Устанавливаю jsmin и cssmin.
pip install jsmin cssmin
Открываю файл selfish/__init__.py
в редакторе PyCharm, редактирую его и привожу к следующему виду.
Здесь я импортировал класс Environment из пакета flask_assets, создал объект assets, который является экземпляром импортированного класса, и инициировал этот объект в теле служебной функции create_app. Нужно обратить внимание, что PyCharm выделил зелёным в поле слева все изменения в файле.
Отладку клиентского кода я буду осуществлять в браузере Chromium, для удобства отладки мне необходимо предотвратить кэширование браузером статических файлов в режиме разработки. Открываю в редакторе PyCharm файл config.py и дописываю в него новый код: импортирую timedelta и вписываю новый атрибут классам Development и Production.
Файл config.py не входит в репозиторий Git, поэтому PyCharm не выделил правки этого файла зелёным, как это случилось в случае с файлом __init__.py
, а красные отметки на полях поставил я, чтобы сконцентрировать внимание на новых строчках файла.
Пришло время создать первую страницу приложения - Index Page будущего сайта. Это делается просто, достаточно задать url-адрес и определить для него функцию представления в файле selfish/main/views.py. Открываю этот файл в редакторе и привожу его к следующему виду.
Здесь я определил функцию show_index, при помощи соответствующего декоратора задал этой функции url-адрес, таким образом сделав её функцией представления. Далее я импортировал из пакета flask дополнительно ещё одну полезную функцию - render_template, которая обрабатывает шаблон с учётом переданных ей аргументов и возвращает соответствующую строку. В теле функции show_index я вернул вызов render_template, передав ему только имя шаблона. Возвращаемая функцией представления строка автоматически преобразуется декоратором main.route в экземпляр класса Response.
Если сейчас запустить отладочный сервер и постучаться браузером в заданный url, браузер выведет страницу отладчика, так как шаблон index.html в проекте не существует.
Шаблоны в приложениях Flask обычно хранятся в специальном каталоге, в данном случае это selfish/templates, если в конфигурации приложения не задан другой адрес. Для начала сделаю шаблон index.html обычным текстовым файлом, содержащим традиционную строчку - Hello, world!.
echo 'Hello, world!' > selfish/templates/index.html
В итоге получаю в браузере очень простую стартовую страницу.
На текущий момент полученная страница не содержит кода, который работал бы на стороне клиента. Сейчас будем это обстоятельство исправлять.
Приложение selfish в перспективе будет иметь некоторое множество страниц. Для каждой страницы потребуется определить свой шаблон. Но, поскольку все страницы seflish будут иметь общие функциональные элементы оформления, наряду с индивидуальным для каждой страницы шаблоном, будет разумно определить базовый шаблон и в нём выделить те самые общие функциональные элементы. Подобный подход даст возможность в будущем редактировать общие функциональные элементы всех страниц приложения в одном месте - базовом шаблоне. Создаю базовый шаблон.
touch selfish/templates/base.html
Открываю этот файл в редакторе и привожу его к следующему виду.
<!DOCTYPE html> <html lang="ru"> <head> <title></title> </head> <body> </body> </html>
Теперь мне необходимо в базовом шаблоне выделить функциональные блоки, которые могут быть переопределены в наследниках базового шаблона. Дописываю следующий код.
<!DOCTYPE html> <html lang="ru"> <head> {% block metas %}{% endblock metas %} <title>{% block title %}{% endblock title %}</title> {% block styles %}{% endblock styles %} </head> <body> <nav id="navigation"></nav> {% block page_body %} {% block page_content %}{% endblock page_content %} {% endblock page_body %} <footer id="footer"></footer> {% block scripts %}{% endblock scripts %} </body> </html>
В базовом шаблоне я выделил следующие функциональные блоки:
- block metas - будет содержать теги
<meta>
; - block title - будет определять заголовки страниц;
- block styles - предназначен для стилей страниц;
- block page_body - будет содержать контент страниц;
- block page_content - дополнительный блок для контента страниц, даст возможность разделить страницы приложения на несколько видов;
- block scripts - будет содержать JavaScript-сценарии.
Тело страницы базового шаблона кроме блока page_body получило ещё два тега с заданными id
. Эти теги будут определять главное меню приложения и так называемый подвал.
Чтобы воспользоваться базовым шаблоном, достаточно унаследовать его в шаблоне проектируемой страницы. Вернёмся к шаблону index.html, открываю этот файл в редакторе и пишу в него следующий код.
{% extends "base.html" %} {% block title %}Исток{% endblock title %} {% block styles %} {{ super() }} {% endblock styles %} {% block page_content %} <!-- index.html --> <div class="flashed-message"> <div class="alert alert-warning"> <div class="today-field"></div> <div class="message-text"> Сайт в стадии разработки, попробуйте зайти позже. </div> </div> </div> {% endblock page_content %} {% block scripts %} {{ super() }} {% endblock scripts %}
Здесь я указал, что index.html является наследником базового шаблона base.html, а затем переопределил те блоки index.html, которые будут иметь отличия от соответствующих блоков базового шаблона. Стоит обратить внимание на вызов функции super в блоках styles и scripts, этот вызов позволяет унаследовать код соответствующих блоков базового шаблона и дописать в эти блоки уникальный для index.html код. Без этого вызова данные блоки переопределились бы полностью.
Запускаю отладочный сервер, лезу в браузер и стучусь в соответствующий url.
Теперь стоит взглянуть на исходный код этой страницы в браузере.
У страницы появилась разметка. Далее в процессе разработки этой страницы я буду править сначала базовый шаблон, затем шаблон страницы index.html и демонстрировать изменения отображаемой браузером страницы.
Приступим к правке базового шаблона. Я буду править каждый выделенный блок в порядке следования. Блок metas базового шаблона приобретает следующий вид.
{% block metas %} <meta charset="utf-8"> <meta http-equiv="X-UA-COMPATIBLE" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> {% endblock metas %}
Для приложения selfish можно определить имя сайта, которое будет транслироваться в заголовок и в подвал всех страниц. Определяю имя сайта в файле config.py, добавляю соответствующее свойство классу Config.
class Config: SECRET_KEY = 'My Secret Key' SITE_NAME = 'Selfish' @staticmethod def init_app(app): pass
Перехожу в базовый шаблон и привожу тег <title>
к виду.
<title>{{ config.SITE_NAME }}: {% block title %}{% endblock title %}</title>
Подключаю стили Bootstrap в базовый шаблон при помощи контекстной переменной assets, для этого привожу блок styles базового шаблона к следующему виду.
{% block styles %} {% assets filters='cssmin', output='generic/css/vendor.css', 'vendor/bootstrap-3.4.0/css/bootstrap.css', 'vendor/bootstrap-3.4.0/css/bootstrap-theme.css' %} <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}"> {% endassets %} {% endblock styles %}
Необходимо заметить, что контекстная переменная assets определена в файле selfish/__init__.py
и является экземпляром класса Environment. В аргументах этой переменной в базовом шаблоне при помощи output задан файл, в который будет генерироваться итоговая таблица стилей - generic/css/vendor.css, а также заданы файлы, из которых итоговая таблица стилей будет состоять. Эти файлы уже существуют и скопированы в проект в предыдущем выпуске блога. Аргумент filters задаёт фильтр для минимизации результирующего файла, этот фильтр уже установлен в виртуальное окружение проекта.
Посмотрим, как в итоге изменилась страница в браузере.
Теперь мне нужно подключить в базовый шаблон JavaScript библиотеки, которые я наметил использовать. Привожу блок scripts базового шаблона к следующему виду.
{% block scripts %} {% assets filters='jsmin', output='generic/js/vendor.js', 'vendor/jquery-3.3.1.js', 'vendor/bootstrap-3.4.0/js/bootstrap.js', 'vendor/moment.js', 'vendor/ru.js' %} <script src="{{ ASSET_URL }}"></script> {% endassets %} {% endblock scripts %}
Модифицирую тег <nav id="navigation">
базового шаблона.
<nav id="navigation"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{{ url_for('main.show_index') }}"> <img alt="logo" src="{{ url_for('static', filename='images/logo.png') }}" width="28" height="28"> </a> </div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><a href="">События</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Действия <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><a href="">Войти</a></li> <li><a href="">Зарегистрироваться</a></li> </ul> </li> </ul> </div> </div> </nav>
Блок page-body базового шаблона привожу к следующему виду.
{% block page_body %} <div id="main-container" class="container-fluid"> <div class="row"> <div id="content" class="col-lg-8 col-md-10 col-sm-10 col-lg-offset-2 col-md-offset-1 col-sm-offset-1"> <div class="row"> <div id="sub-content" class="col-lg-10 col-md-10 col-lg-offset-1 col-md-offset-1"> {% block page_content %}{% endblock page_content %} </div> </div> </div> </div> </div> {% endblock page_body %}
И, наконец, изменяю <footer>
.
<footer id="footer"> <div class="container-fluid"> <div class="footer-block"></div> <div class="footer-content"> <div class="footer-left text-left"> <img alt="right finger" src="{{ url_for( 'static', filename='images/footer-left.png') }}" width="24" height="24"> </div> <div class="footer-center text-center"> <a id="footer-link" href="{{ url_for('main.show_index') }}"> {{ config.SITE_NAME }} </a> </div> <div class="footer-right text-right"> <img alt="left finger" src="{{ url_for( 'static', filename='images/footer-right.png') }}" width="24" height="24"> </div> <div class="footer-bottom"></div> </div> <div class="footer-block"></div> </div> </footer>
Возвращаюсь в браузер и фиксирую изменения в отображении страницы.
Страница пока выглядит достаточно коряво. Но на данный момент меня больше интересует код этой страницы и теги <link>
и <script>
. Браузер отображает их так.
Если в браузере проследовать по ссылкам в этих тегах, то можно увидеть содержимое соответствующих файлов, код в этих файлах минимизирован. И, если теперь взглянуть на каталог статических файлов приложения, то он будет выглядеть так.
Каталог generic и его содержимое сгенерированы автоматически. Для полного благополучия и реализации задуманного базовому шаблону не хватает оригинальной таблицы стилей, она появится чуть позже.
Оригинальная таблица стилей базовго шаблона будет храниться в отдельном файле в каталоге selfish/static/css/. Этот каталог уже существует, создаю в нём новый файл.
touch selfish/static/css/base.css
Открываю этот файл в редакторе и пишу в него следующий код.
html { position: relative; min-height: 100%; } body { margin-bottom: 60px; } #navigation { margin: 0; background-image: linear-gradient(to bottom, #fffef2, #f3f2e7); box-shadow: 0 0 6px #f1dbc2; } .navbar-brand { padding-top: 10px; } #main-container { padding-top: 8px; padding-bottom: 8px; color: #83858f; } .error { color: #a94442; padding-left: 8px; margin-bottom: 0; } .alert { margin: 0; } #footer { position: absolute; bottom: 0; width: 100%; height: 60px; box-shadow: 0 0 1px #7f8c7f; background-color: #687368; background-image: linear-gradient(to bottom, #a2a19a, #74736e); } .footer-block { height: 15px; } .footer-left { width: 5%; float: left; margin-top: 5px; } .footer-center { width: 90%; float: left; padding-top: 4px; color: white; } #footer-link { color: white; text-decoration: none; text-shadow: 0 0 8px #d9dcec; } #footer-link:hover { cursor: pointer; text-shadow: 0 0 0 #d9dcec; } .footer-right { width: 5%; float: left; margin-top: 5px; } .footer-bottom { clear: both; }
Каждый новый шаблон selfish будет включать этот файл, в том числе уже созданный шаблон index.html. Открываю его в редакторе и привожу блок styles этого шаблона к следующему виду.
{% block styles %} {{ super() }} {% assets filters='cssmin', output='generic/css/main/index.css', 'css/base.css' %} <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}"> {% endassets %} {% endblock styles %}
Следует обратить внимание на оформление контекстной переменной assets, она получила фильтр для минимизации кода результирующей таблицы стилей, которая будет храниться в файле generic/css/main/index.css, и на данном этапе будет получена из единственного файла css/base.css.
Возвращаюсь в браузер и отмечаю изменения на странице.
Если заглянуть в исходный код страницы, можно увидеть, что появился ещё один тег <link>
.
Следую по ссылке в этом теге и убеждаюсь, что результирующий файл минимизирован.
JavaScript - лекарство от скуки. Настал момент чуть-чуть развлечься. Так как у меня есть сторонняя библиотека Moment.js, которая значительно облегчает жизнь при работе с датами и временем, неплохо было бы начать её использовать уже на первой странице приложения. В дефолтном состоянии эта библиотека имеет англоязычную локаль, что не очень подходит для selfish, и поэтому требуется дополнительная локализация. Русскоязычная локаль Moment.js находится в файле selfish/static/vendor/ru.js, необходимо применить эту локаль и переопределить отображение времени в ней. Создаю новый файл.
touch selfish/static/js/custom_moment.js
Открываю этот файл в редакторе и пишу в него следующий код.
function customizeMoment(moment) { moment.locale('ru'); moment.updateLocale('ru', { longDateFormat : { LT : 'HH:mm', LTS : 'HH:mm:ss', L : 'DD.MM.YYYY', LL : 'D MMMM YYYY г.', LLL : 'D MMMM YYYY г., HH:mm', LLLL : 'dddd, D MMMM YYYY г., HH:mm' } }); return moment; }
Единственная на текущий момент страница selfish в сущности готова, но при помощи JavaScript и таблиц стилей можно добавить этой странице несколько интересных деталей. Открываю шаблон index.html и пишу в блок scripts этого шаблона следующий код.
{% block scripts %} {{ super() }} {% assets filters='jsmin', output='generic/js/main/index.js', 'js/custom_moment.js', 'js/main/index.js' %} <script src="{{ ASSET_URL }}"></script> {% endassets %} {% endblock scripts %}
Создаю новый каталог и в нём новый файл.
mkdir selfish/static/js/main touch selfish/statis/js/main/index.js
Открываю созданный файл в редакторе и пишу в него следующий код.
$(document).ready(function() { moment = customizeMoment(moment); $('.today-field') .text(moment().format('LL') + ', ' + moment().format('HH:mm:ss')); setInterval( function() { $('.today-field') .text(moment().format('LL') + ', ' + moment().format('HH:mm:ss')); }, 1000); });
Затем в блоке styles шаблона index.html вписываю через запятую ещё один файл.
{% block styles %} {{ super() }} {% assets filters='cssmin', output='generic/css/main/index.css', 'css/base.css', 'css/main/index.css' %} <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}"> {% endassets %} {% endblock styles %}
Создаю этот файл.
mkdir selfish/static/css/main touch selfish/static/css/main/index.css
Открываю этот файл в редакторе и пишу в него следующий код.
.today-field { font-style: italic; font-weight: 600; font-size: 0.9em; } .message-text { font-style: italic; }
Возвращаюсь в браузер и обновляю страницу.
На странице появилась текущая дата и тикающие часы, а текст в поле отображается курсивом - это результат работы JavaScript-сценариев и таблиц стилей, только что добавленных в проект и исполняющихся на стороне клиента. Взглянем ещё раз на исходный код страницы, в данном случае меня интересуют теги <link>
и <script>
. Дело в том, что в режиме отладки приложения мне бы хотелось, чтобы в этих тегах отображались ссылки на реальные файлы, а не автоматически сгенерированные и минимизированные. Открываю файл config.py и классу Development вписываю ещё один атрибут.
class Development(Config): DEBUG = True SEND_FILE_MAX_AGE_DEFAULT = 0 ASSETS_DEBUG = True
Возвращаюсь в браузер, обновляю страницу и смотрю на исходный код.
Тегов <link>
стало больше, и ссылаются они теперь на исходные неминимизированные файлы. Абсолютно та же картина с тегами <script>
.
На текущий момент накопилось достаточно много изменений в базовом каталоге selfish.
Эти изменения проекта необходимо сохранить в Git, для этого нужно сделать коммит, но перед этим следует зафиксировать изменения виртуального окружения. Изменяю файл requirements.txt.
pip freeze > requirements.txt
Файл config.py подвергся правкам, его изменения тоже нужно сохранить, копирую его в файл шаблона.
cp config.py config.template.py
В каталоге статических файлов появился автоматически генерируемый каталог, который мне не нужен в Git-репозитории. Привожу файл .gitignore к следующему виду.
venv/ .idea/ __pycache__/ config.py selfish/static/vendor selfish/static/generic selfish/static/.webassets-cache
Добавляю изменения в проект.
git add .
Напоминаю, что перед созданием очередного коммита очень неплохо будет ещё раз просмотреть изменения при помощи команды git diff --staged.
Текущая версия кода selfish доступна в моём профиле на gitlab.com.
Работу над проектом selfish я продолжу в следующем выпуске блога, на текущий момент я обнаружил некоторые сложности с отображением страницы, их нужно будет исправить, ну и кроме этого необходимо научить приложение обрабатывать ошибки. Об этом и поговорим далее. Продолжение следует. Напоминаю, что вопросы мне можно задать прямо в комментариях к этому топику, правда для этого потребуется регистрация на ресурсе. Имейте ввиду, мне интересно знать ваше мнение.