Selfish, базовый шаблон и клиентская часть приложения

avm

Опубликован:  2018-02-28T07:10:22.331932Z
Отредактирован:  2018-02-28T07:08:00.515845Z
3301
Представлено пошаговое описание процесса создания базового шаблона web-приложения и подключения библиотек, работающих на стороне клиента. В результате выполнения описанных здесь действий Selfish получит первую страницу и исполняемый на стороне клиента код.

1. В предыдущих сериях

Selfish - web-приложение на базе Python3 и Flask, начальная конфигурация Selfish детально описана в Selfish, начальная конфигурация web-приложения с Python3 и Flask. На текущий момент Selfish находится в начальной стадии разработки, имеет собственный менеджер, сконфигурированное приложение (экземпляр Flask) и единственный url-адрес - иконку для избранного. Код приложения доступен на github.com.

2. Клиентские библиотеки и способ их подключения в проект

Selfish для своего клиентского кода будет использовать следующие библиотеки:

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

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

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

sadmin@debian:~$ 
sadmin@debian:~$ pwd
/home/sadmin
sadmin@debian:~$ 

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

sadmin@debian:~$ wget https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip
--2018-02-26 09:30:08--  https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip
Распознаётся github.com (github.com)… 192.30.253.112, 192.30.253.113
Подключение к github.com (github.com)|192.30.253.112|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 302 Found
Адрес: https://github-production-release-asset-2e65be.s3.amazonaws.com/2126244/9c5b6db6-5245-11e6-800b-b1e5008b1179?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20180226%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20180226T043009Z&X-Amz-Expires=300&X-Amz-Signature=c21b8a02d9366173fcb665c4e6bdd85f0488aa98bb599893f2283227759bd334&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dbootstrap-3.3.7-dist.zip&response-content-type=application%2Foctet-stream [переход]
--2018-02-26 09:30:09--  https://github-production-release-asset-2e65be.s3.amazonaws.com/2126244/9c5b6db6-5245-11e6-800b-b1e5008b1179?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20180226%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20180226T043009Z&X-Amz-Expires=300&X-Amz-Signature=c21b8a02d9366173fcb665c4e6bdd85f0488aa98bb599893f2283227759bd334&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dbootstrap-3.3.7-dist.zip&response-content-type=application%2Foctet-stream
Распознаётся github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)… 52.216.164.3
Подключение к github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.164.3|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 370276 (362K) [application/octet-stream]
Сохранение в: «bootstrap-3.3.7-dist.zip»

bootstrap-3.3.7-dis 100%[===================>] 361,60K  30,1KB/s    in 14s     

2018-02-26 09:30:24 (25,4 KB/s) - «bootstrap-3.3.7-dist.zip» сохранён [370276/370276]

sadmin@debian:~$ 

Распаковываю архив.

sadmin@debian:~$ unzip bootstrap-3.3.7-dist.zip 
Archive:  bootstrap-3.3.7-dist.zip
   creating: bootstrap-3.3.7-dist/css/
  inflating: bootstrap-3.3.7-dist/css/bootstrap-theme.css  
  inflating: bootstrap-3.3.7-dist/css/bootstrap-theme.css.map  
  inflating: bootstrap-3.3.7-dist/css/bootstrap-theme.min.css  
  inflating: bootstrap-3.3.7-dist/css/bootstrap-theme.min.css.map  
  inflating: bootstrap-3.3.7-dist/css/bootstrap.css  
  inflating: bootstrap-3.3.7-dist/css/bootstrap.css.map  
  inflating: bootstrap-3.3.7-dist/css/bootstrap.min.css  
  inflating: bootstrap-3.3.7-dist/css/bootstrap.min.css.map  
   creating: bootstrap-3.3.7-dist/fonts/
  inflating: bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.eot  
  inflating: bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.svg  
  inflating: bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.ttf  
  inflating: bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff  
  inflating: bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff2  
   creating: bootstrap-3.3.7-dist/js/
  inflating: bootstrap-3.3.7-dist/js/bootstrap.js  
  inflating: bootstrap-3.3.7-dist/js/bootstrap.min.js  
  inflating: bootstrap-3.3.7-dist/js/npm.js  
sadmin@debian:~$ 

Удаляю ненужные файлы.

sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/css/bootstrap-theme.css
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/css/bootstrap-theme.css.map
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/css/bootstrap.css
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/css/bootstrap.css.map
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/js/bootstrap.js
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist/js/npm.js
sadmin@debian:~$ rm -f bootstrap-3.3.7-dist.zip 
sadmin@debian:~$ 

Создаю каталог для сторонних библиотек в каталоге статических файлов Selfish.

sadmin@debian:~$ mkdir ~/workspace/selfish/selfish/static/vendor
sadmin@debian:~$ 

И перемещаю каталог bootstrap-3.3.7-dist в только что созданный каталог.

sadmin@debian:~$ mv -v bootstrap-3.3.7-dist/ ~/workspace/selfish/selfish/static/vendor/bootstrap
'bootstrap-3.3.7-dist/' -> '/home/sadmin/workspace/selfish/selfish/static/vendor/bootstrap'
sadmin@debian:~$ 

Скачиваю jQuery.

sadmin@debian:~$ wget -P ~/workspace/selfish/selfish/static/vendor/ https://code.jquery.com/jquery-3.3.1.min.js
--2018-02-26 09:56:34--  https://code.jquery.com/jquery-3.3.1.min.js
Распознаётся code.jquery.com (code.jquery.com)… 94.31.29.54, 151.139.237.113
Подключение к code.jquery.com (code.jquery.com)|94.31.29.54|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 86927 (85K) [application/javascript]
Сохранение в: «/home/sadmin/workspace/selfish/selfish/static/vendor/jquery-3.3.1.min.js»

jquery-3.3.1.min.js 100%[===================>]  84,89K  54,6KB/s    in 1,6s    

2018-02-26 09:56:36 (54,6 KB/s) - «/home/sadmin/workspace/selfish/selfish/static/vendor/jquery-3.3.1.min.js» сохранён [86927/86927]

sadmin@debian:~$ 

Скачиваю map-файл jQuery.

sadmin@debian:~$ wget -P ~/workspace/selfish/selfish/static/vendor/ https://code.jquery.com/jquery-3.3.1.min.map
--2018-02-26 09:58:16--  https://code.jquery.com/jquery-3.3.1.min.map
Распознаётся code.jquery.com (code.jquery.com)… 151.139.237.113
Подключение к code.jquery.com (code.jquery.com)|151.139.237.113|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 132382 (129K) [application/octet-stream]
Сохранение в: «/home/sadmin/workspace/selfish/selfish/static/vendor/jquery-3.3.1.min.map»

jquery-3.3.1.min.ma 100%[===================>] 129,28K   553KB/s    in 0,2s    

2018-02-26 09:58:17 (553 KB/s) - «/home/sadmin/workspace/selfish/selfish/static/vendor/jquery-3.3.1.min.map» сохранён [132382/132382]

sadmin@debian:~$ 

Скачиваю Moment.js.

sadmin@debian:~$ wget -P ~/workspace/selfish/selfish/static/vendor/ https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js
--2018-02-26 09:59:55--  https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js
Распознаётся cdnjs.cloudflare.com (cdnjs.cloudflare.com)… 104.19.194.102, 104.19.192.102, 104.19.196.102, ...
Подключение к cdnjs.cloudflare.com (cdnjs.cloudflare.com)|104.19.194.102|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: нет данных [application/javascript]
Сохранение в: «/home/sadmin/workspace/selfish/selfish/static/vendor/moment.min.js»

moment.min.js           [ <=>                ]  50,39K  --.-KB/s    in 0,1s    

2018-02-26 09:59:55 (430 KB/s) - «/home/sadmin/workspace/selfish/selfish/static/vendor/moment.min.js» сохранён [51599]

sadmin@debian:~$ 

Для Moment.js потребуется русская локализация, скачиваю соответствующий файл.

sadmin@debian:~$ wget -P ~/workspace/selfish/selfish/static/vendor/ https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/locale/ru.js
--2018-02-26 10:01:59--  https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/locale/ru.js
Распознаётся cdnjs.cloudflare.com (cdnjs.cloudflare.com)… 104.19.192.102, 104.19.194.102, 104.19.193.102, ...
Подключение к cdnjs.cloudflare.com (cdnjs.cloudflare.com)|104.19.192.102|:443... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: нет данных [application/javascript]
Сохранение в: «/home/sadmin/workspace/selfish/selfish/static/vendor/ru.js»

ru.js                   [ <=>                ]   8,13K  --.-KB/s    in 0,001s  

2018-02-26 10:02:00 (5,64 MB/s) - «/home/sadmin/workspace/selfish/selfish/static/vendor/ru.js» сохранён [8328]

sadmin@debian:~$ 

Перехожу в корневой каталог Selfish.

sadmin@debian:~$ cd ~/workspace/selfish
sadmin@debian:~/workspace/selfish$

На текущий момент после всех выполненных действий каталог статических файлов Selfish имеет следующий вид.

static

3. Макет сайта

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

Таким образом, мне необходимо получить в каталог статических файлов Selfish три изображения. Скачиваю их с auirz.ru. Первый - логотип главного меню.

sadmin@debian:~/workspace/selfish$ wget -P selfish/static/images/ http://auriz.ru/static/images/logo.png
--2018-02-26 10:25:59--  http://auriz.ru/static/images/logo.png
Распознаётся auriz.ru (auriz.ru)… 185.22.152.40
Подключение к auriz.ru (auriz.ru)|185.22.152.40|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 15750 (15K) [image/png]
Сохранение в: «selfish/static/images/logo.png»

logo.png            100%[===================>]  15,38K  --.-KB/s    in 0,04s   

2018-02-26 10:25:59 (430 KB/s) - «selfish/static/images/logo.png» сохранён [15750/15750]

sadmin@debian:~/workspace/selfish$ 

Левый указатель.

sadmin@debian:~/workspace/selfish$ wget -P selfish/static/images/ http://auriz.ru/static/images/footer-left.png
--2018-02-26 10:27:56--  http://auriz.ru/static/images/footer-left.png
Распознаётся auriz.ru (auriz.ru)… 185.22.152.40
Подключение к auriz.ru (auriz.ru)|185.22.152.40|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 16154 (16K) [image/png]
Сохранение в: «selfish/static/images/footer-left.png»

footer-left.png     100%[===================>]  15,78K  --.-KB/s    in 0,05s   

2018-02-26 10:27:56 (339 KB/s) - «selfish/static/images/footer-left.png» сохранён [16154/16154]

sadmin@debian:~/workspace/selfish$ 

Правый указатель.

sadmin@debian:~/workspace/selfish$ wget -P selfish/static/images/ http://auriz.ru/static/images/footer-right.png
--2018-02-26 10:29:07--  http://auriz.ru/static/images/footer-right.png
Распознаётся auriz.ru (auriz.ru)… 185.22.152.40
Подключение к auriz.ru (auriz.ru)|185.22.152.40|:80... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 14488 (14K) [image/png]
Сохранение в: «selfish/static/images/footer-right.png»

footer-right.png    100%[===================>]  14,15K  --.-KB/s    in 0,03s   

2018-02-26 10:29:10 (428 KB/s) - «selfish/static/images/footer-right.png» сохранён [14488/14488]

sadmin@debian:~/workspace/selfish$ 

Чтобы окончательно укомплектовать каталог статических файлов, создаю в нём папку для файлов стилей.

sadmin@debian:~/workspace/selfish$ mkdir selfish/static/css
sadmin@debian:~/workspace/selfish$ 

И папку для JS скриптов.

sadmin@debian:~/workspace/selfish$ mkdir selfish/static/js
sadmin@debian:~/workspace/selfish$ 

Теперь каталог статических файлов Selfish имеет следующий вид.

static

И полностью готов к разработке клиентского кода.

4. Дополнительная конфигурация Selfish

Разработка клиентского когда предполагает необходимость и возможность группирования файлов с кодом (таблиц стилей и JS-скриптов) с учётом повторяемости тех или иных функций на разных страницах web-приложения, принцип DRY никто не отменял. Кроме этого, клиентский код нужно отлаживать и тестировать. Это значит, что Selfish требует дополнительной конфигурации, которая будет учитывать методологию разработки.

Группировать таблицы стилей и JS-скрипты я буду при помощи webassets, и для этого мне потребуется соответствующее расширение Flask. Активирую виртуальное окружение Selfish.

sadmin@debian:~/workspace/selfish$ source venv/bin/activate
(venv) sadmin@debian:~/workspace/selfish$ 

Устанавливаю Flask-Assets.

(venv) sadmin@debian:~/workspace/selfish$ pip install Flask-Assets
Collecting Flask-Assets
  Downloading Flask-Assets-0.12.tar.gz
Requirement already satisfied: Flask>=0.8 in ./venv/lib/python3.5/site-packages (from Flask-Assets)
Collecting webassets>=0.11.1 (from Flask-Assets)
  Downloading webassets-0.12.1.tar.gz (179kB)
    100% |████████████████████████████████| 184kB 973kB/s 
Requirement already satisfied: Werkzeug>=0.7 in ./venv/lib/python3.5/site-packages (from Flask>=0.8->Flask-Assets)
Requirement already satisfied: click>=2.0 in ./venv/lib/python3.5/site-packages (from Flask>=0.8->Flask-Assets)
Requirement already satisfied: itsdangerous>=0.21 in ./venv/lib/python3.5/site-packages (from Flask>=0.8->Flask-Assets)
Requirement already satisfied: Jinja2>=2.4 in ./venv/lib/python3.5/site-packages (from Flask>=0.8->Flask-Assets)
Requirement already satisfied: MarkupSafe>=0.23 in ./venv/lib/python3.5/site-packages (from Jinja2>=2.4->Flask>=0.8->Flask-Assets)
Building wheels for collected packages: Flask-Assets, webassets
  Running setup.py bdist_wheel for Flask-Assets ... done
  Stored in directory: /home/sadmin/.cache/pip/wheels/d9/1b/68/03cff4faa9c5c8a37202d96a6a6015cfc5e4a804ae93b26887
  Running setup.py bdist_wheel for webassets ... done
  Stored in directory: /home/sadmin/.cache/pip/wheels/9d/cb/c2/340b9b695822b6954840bcb6cd147b3a7cfc2bcd922296e63e
Successfully built Flask-Assets webassets
Installing collected packages: webassets, Flask-Assets
Successfully installed Flask-Assets-0.12 webassets-0.12.1
(venv) sadmin@debian:~/workspace/selfish$ 

Для минимизации кода потребуются соответствующие фильтры. Устанавливаю jsmin.

(venv) sadmin@debian:~/workspace/selfish$ pip install jsmin
Collecting jsmin
  Downloading jsmin-2.2.2.tar.gz
Building wheels for collected packages: jsmin
  Running setup.py bdist_wheel for jsmin ... done
  Stored in directory: /home/sadmin/.cache/pip/wheels/fb/5f/9f/8c4a6aaa73d81713a9080d0a7a541da3dc613934eb66ccebcf
Successfully built jsmin
Installing collected packages: jsmin
Successfully installed jsmin-2.2.2
(venv) sadmin@debian:~/workspace/selfish$ 

Устанавливаю cssmin.

(venv) sadmin@debian:~/workspace/selfish$ pip install cssmin
Collecting cssmin
  Downloading cssmin-0.2.0.tar.gz
Building wheels for collected packages: cssmin
  Running setup.py bdist_wheel for cssmin ... done
  Stored in directory: /home/sadmin/.cache/pip/wheels/c3/79/88/647f59be446af4e9867362ca6e961cc7f218bd793fbdc351a6
Successfully built cssmin
Installing collected packages: cssmin
Successfully installed cssmin-0.2.0
(venv) sadmin@debian:~/workspace/selfish$ 

Открываю файл selfish/__init__.py в PyCharm и привожу его к следующему виду.

selfish_init

Здесь я импортировал класс Environment из пакета flask_assets, создал объект assets, который является экземпляром импортированного класса, и инициировал этот объект в теле служебной функции create_app. Следует заметить, что PyCharm выделил зелёным цветом в поле слева все изменения в файле.

Отладка клиентского кода будет осуществляться в браузере Chromium, для удобства отладки мне необходимо предотвратить кэширование браузером статических файлов в режиме разработки. Открываю в PyCharm файл config.py и дописываю в него новый код, импортирую timedelta и вписываю новый атрибут классам Development и Production.

config.py

Замечание: на иллюстрации выше код классов Config и Testing "свёрнут", стандартная возможность редактора PyCharm, в действительности эти классы никак не изменились, и PyCharm не выделил изменения в этом файле цветом в поле слева, так как файл config.py не включен в git-репозиторий.

5. Первая страница приложения

Пришло время создать первую страницу приложения - Index Page будущего сайта. Для этого мне достаточно задать url-адрес и определить функцию представления в файле selfish/main/views.py. Открываю этот файл и привожу его к следующему виду.

show_index

Здесь я определил функцию show_index, при помощи декоратора main.route задал этой функции url-адрес, таким образом сделав эту функцию функцией представления. Далее я импортировал из пакета flask дополнительно ещё одну полезную функцию render_template, которая обрабатывает шаблон с учётом переданных ей аргументов и возвращает соответствующую строку. И наконец в теле функции show_index я вернул вызов render_template, передав только имя шаблона. Возвращаемая функцией представления строка автоматически преобразуется в экземпляр класса Response.

Если сейчас запустить отладочный сервер и постучаться в заданный url, браузер выведет страницу отладчика, так как шаблон index.html не существует. Запускаю отладочный сервер.

(venv) sadmin@debian:~/workspace/selfish$ python manage.py runserver
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 109-671-440

Перехожу в браузер.

chromium

Создаю шаблон index.html, шаблоны хранятся в специальном каталоге selfish/templates. Для начала шаблон будет обычным текстовым файлом без разметки.

(venv) sadmin@debian:~/workspace/selfish$ echo 'Hello, world!' > selfish/temples/index.html
(venv) sadmin@debian:~/workspace/selfish$ 

В итоге получаю стартовую страницу.

index_page

На текущий момент страница не содержит кода, который работал бы на стороне клиента. Сейчас буду это исправлять.

6. Базовый шаблон

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

Создаю базовый шаблон.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/templates/base.html
(venv) sadmin@debian:~/workspace/selfish$ 

Открываю этот файл в редакторе PyCharm и привожу его к следующему виду.

<!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. Эти теги будут определять главное меню приложения и "подвал".

Чтобы воспользоваться базовым шаблоном, достаточно унаследовать его в шаблоне проектируемой страницы. Открываю файл selfish/templates/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 наследует от базового шаблона, и переопределил те блоки index.html, которые будут иметь отличия от соответствующих блоков базового шаблона. Стоит обратить внимание на вызов функции super в блоках styles и scripts, этот вызов позволяет унаследовать код соответствующих блоков базового шаблона и дописать в эти блоки свой код. Без этого вызова эти блоки переопределились бы полностью.

Запускаю отладочный сервер, лезу в браузер и стучусь в соответствующий url.

index

Теперь стоит взглянуть на исходный код этой страницы в браузере.

index_source

У страницы появилась разметка. Далее я буду править сначала базовый шаблон, затем шаблон index.html и демонстрировать изменения отображаемой браузером страницы.

7. Подключаем клиентские библиотеки в базовый шаблон

Приступаю к правке базового шаблона. Буду править каждый выделенный блок в порядке следования. Блок 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 output='generic/css/vendor.css',
            'vendor/bootstrap/css/bootstrap.min.css',
            'vendor/bootstrap/css/bootstrap-theme.min.css' %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
  {% endassets %}
{% endblock styles %}

Необходимо заметить, что контекстная переменная assets определена в файле selfish/__init__.py и является экземпляром класса Environment. В аргументах этой переменной в базовом шаблоне при помощи output задан файл, в который будет генерироваться итоговая таблица стилей - generic/css/vendor.css, а так же заданы файлы из которых итоговая таблица стилей будет состоять. Эти файлы уже существуют (см. "Клиентские библиотеки и способы их подключения" выше).

Посмотрим как изменилась страница в браузере.

styled index

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

{% block scripts %}
  {% assets filters='jsmin', output='generic/js/vendor.js',
            'vendor/jquery-3.3.1.min.js',
            'vendor/bootstrap/js/bootstrap.min.js',
            'vendor/moment.min.js', 'vendor/ru.js' %}
    <script src="{{ ASSET_URL }}"></script>
  {% endassets %}
{% endblock scripts %}

В данном случае контекстная переменная assets получила фильтр jsmin, так как файл vendor/ru.js не минимизирован. Заданный фильтр позволит произвести его минимизацию в результирующем сценарии generic/js/vendor.js.

Модифицирую тег <nav id="navigation"> базового шаблона.

  <nav id="navigation" class="navbar navbar-default">
    <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>

Лезу в браузер и смотрю на изменения на странице.

index

Страница пока выглядит достаточно коряво. Но меня на данный момент интересует код этой страницы и теги <link> и <script>. Браузер отображает их так.

link

script

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

static

Каталог generic и его содержимое сгенерированы автоматически.

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

8. Оригинальная таблица стилей базового шаблона

Базовый шаблон требует собственной таблицы стилей. Создаю новый файл.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/static/css/base.css
(venv) sadmin@debian:~/workspace/selfish$ 

Пишу в него следующий код.

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 шаблона index.html к виду.

{% block styles %}
  {{ super() }}
  {% assets filters='cssmin', output='generic/css/index.css',
            'css/base.css' %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
  {% endassets %}
{% endblock styles %}

Здесь контекстная переменная assets получила фильтр cssmin для минимизации результирующей таблицы стилей, результирующая таблица стилей будет храниться в файле generic/css/index.css и на данном этапе будет получена из единственного файла css/base.css.

Лезу в браузер.

index

Смотрю код страницы, вижу что появился ещё один тег <link>.

index source

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

index.css

На этом работа с базовым шаблоном в текущей стадии разработки завершена.

9. Локализация Moment.js

Немного JavaScript - лекарство от скуки.

Selfish использует стороннюю библиотеку Moment.js, которая значительно облегчает жизнь и позволяет работать с датами и временем. В дефолтном состоянии эта библиотека имеет англоязычную локаль, что не очень подходит для Selfish, и поэтому требуется дополнительная локализация. Русскоязычная локаль для Moment.js находится в файле selfish/static/vendor/ru.js. Мне нужно применить эту локаль и переопределить отображение времени в ней. Создаю новый файл.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/static/js/custom_moment.js
(venv) sadmin@debian:~/workspace/selfish$ 

Пишу в него следующий код.

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;
}

Далее этот файл буду использовать для локализации Moment.js в своих сценариях для страниц Selfish.

10. Оформление первой страницы приложения

Единственная страница Selfish в сущности готова, но при помощи JavaScript и таблиц стилей можно добавить этой странице несколько интересных деталей. Открываю шаблон index.html и пишу в блок scripts этого шаблона следующий код.

{% block scripts %}
  {{ super() }}
  {% assets filters='jsmin', output='generic/js/index.js',
            'js/custom_moment.js',
            'js/main/index.js' %}
    <script src="{{ ASSET_URL }}"></script>
  {% endassets %}
{% endblock scripts %}

Создаю новый каталог и в нём новый файл.

(venv) sadmin@debian:~/workspace/selfish$ mkdir selfish/static/js/main
(venv) sadmin@debian:~/workspace/selfish$ touch selfish/static/js/main/index.js
(venv) sadmin@debian:~/workspace/selfish$ 

Открываю созданный файл в редакторе PyCharm и пишу в него следующий код.

$(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/index.css',
            'css/base.css', 'css/index.css' %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
  {% endassets %}
{% endblock styles %}

Создаю этот файл.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/static/css/index.css
(venv) sadmin@debian:~/workspace/selfish$ 

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

.today-field {
  font-style: italic;
  font-weight: 600;
  font-size: 0.9em;
}

.message-text {
  font-style: italic;
}

Лезу в браузер и обновляю страницу.

index

На странице появилась текущая дата и часы, текст в поле отображается курсивом. Взглянем ещё раз на исходный код страницы, в данном случае меня интересуют теги <link> и <script>. Дело в том, что в режиме отладки мне бы хотелось, чтобы в этих тегах отображались ссылки на реальные файлы а не автоматически сгенерированные и минимизированные. Для этого открываю config.py и классу Development вписываю ещё один атрибут.

class Development(Config):
    DEBUG = True
    ASSETS_DEBUG = True
    SEND_FILE_MAX_AGE_DEFAULT = 0

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

source

Тегов <link> стало больше, и ссылаются они теперь на исходные неминимизированные файлы. Абсолютно та же картина с тегами <script>. Что и требовалось.

11. Обработка исключений

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

not found

Ошибки буду обрабатывать в файле selfish/main/errors.py. Создаю его.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/main/errors.py
(venv) sadmin@debian:~/workspace/selfish$ 

Открываю в редакторе файл selfish/main/__init__.py и дописываю последний импорт.

from flask import Blueprint

main = Blueprint('main', __name__)

from . import views, errors

Открываю в редакторе файл selfish/main/errors.py и пишу в него следующий код.

from flask import render_template

from . import main


@main.app_errorhandler(404)
def notify_not_found_page(e):
    return render_template(
        'error.html', error=404, reason='Страница не найдена.'), 404

Создаю шаблон error.html.

(venv) sadmin@debian:~/workspace/selfish$ touch selfish/templates/error.html
(venv) sadmin@debian:~/workspace/selfish$ 

Открываю этот шаблон в редакторе и пишу в него следующее.

{% extends "base.html" %}

{% block title %}{{ error }}{% endblock title %}

{% block styles %}
  {{ super() }}
  {% assets filters='cssmin', output='generic/css/error.css',
            'css/base.css' %}
    <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
  {% endassets %}
{% endblock styles %}

{% block page_content %}
  <h1>{{ error }}</h1>
  <p>{{ reason }}</p>
{% endblock page_content %}

Возвращаюсь в браузер и снова стучусь в несуществующий url.

not found

Результат заметен. Дописываю в файл selfish/main/errors.py ещё пару функций для обработки ошибок 403 и 405.

@main.app_errorhandler(403)
def refuse_request(e):
    return render_template(
        'error.html', error=403, reason='Доступ запрещён.'), 403


@main.app_errorhandler(405)
def refuse_method(e):
    return render_template(
        'error.html', error=405, reason='Метод не позволен.'), 405

На этом пока можно остановиться, если понадобится что-то ещё, допишу соответствующую функцию представления.

12. Сохранение изменений в git

На текущий момент накопилось достаточно много изменений в корневом каталоге Selfish. Чтобы их сохранить в git-репозиторий нужно сделать коммит.

Так как с момента последнего коммита в виртуальное окружение были установлены новые пакеты, изменения необходимо зафиксировать. Делаю это.

(venv) sadmin@debian:~/workspace/selfish$ pip freeze > requirements.txt 
(venv) sadmin@debian:~/workspace/selfish$ 

Файл config.py подвергся правкам, его изменения тоже нужно сохранить, копирую config.py в файл config.template.py.

(venv) sadmin@debian:~/workspace/selfish$ cp config.py config.template.py 
(venv) sadmin@debian:~/workspace/selfish$ 

В каталоге статических файлов появился автоматически генерируемый каталог, который мне не нужен в git-репозитории. Привожу файл .gitignore к виду.

__pycache__/
*.pyc
venv/
.idea/
config.py
selfish/static/generic
selfish/static/.webassets-cache

При помощи git status проверяю состояние репозитория, если всё устраивает, добавляю изменения.

(venv) sadmin@debian:~/workspace/selfish$ git add .
(venv) sadmin@debian:~/workspace/selfish$ 

Ещё раз просматриваю изменения при помощи команды git diff --staged и делаю коммит.

(venv) sadmin@debian:~/workspace/selfish$ git commit -m "Create the first page"
[master a25654c] Create the first page
 32 files changed, 813 insertions(+), 3 deletions(-)
 create mode 100644 selfish/main/errors.py
 create mode 100644 selfish/static/css/base.css
 create mode 100644 selfish/static/css/index.css
 create mode 100644 selfish/static/images/footer-left.png
 create mode 100644 selfish/static/images/footer-right.png
 create mode 100644 selfish/static/images/logo.png
 create mode 100644 selfish/static/js/custom_moment.js
 create mode 100644 selfish/static/js/main/index.js
 create mode 100644 selfish/static/vendor/bootstrap/css/bootstrap-theme.min.css
 create mode 100644 selfish/static/vendor/bootstrap/css/bootstrap-theme.min.css.map
 create mode 100644 selfish/static/vendor/bootstrap/css/bootstrap.min.css
 create mode 100644 selfish/static/vendor/bootstrap/css/bootstrap.min.css.map
 create mode 100644 selfish/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot
 create mode 100644 selfish/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg
 create mode 100644 selfish/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf
 create mode 100644 selfish/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff
 create mode 100644 selfish/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2
 create mode 100644 selfish/static/vendor/bootstrap/js/bootstrap.min.js
 create mode 100644 selfish/static/vendor/jquery-3.3.1.min.js
 create mode 100644 selfish/static/vendor/jquery-3.3.1.min.map
 create mode 100644 selfish/static/vendor/moment.min.js
 create mode 100644 selfish/static/vendor/ru.js
 create mode 100644 selfish/templates/base.html
 create mode 100644 selfish/templates/error.html
 create mode 100644 selfish/templates/index.html
(venv) sadmin@debian:~/workspace/selfish$ 

Текущая версия кода Selfish доступна на github.com.

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

 
Осталось: 128
Комментарии:

avm

2018-06-06T10:01:46.741598Z

Заключительный топик по теме - Selfish, вопросы деплоймента - содержит ссылки на все серии цикла.

Hello!