Верстаем статический web-сайт с помощью Python3x, конфигурация

Jazz

Опубликован:  2024-01-24T07:25:02.893950Z
Отредактирован:  2024-01-24T07:25:02.893950Z
Статус:  публичный
25
0
0

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

Выбираем название проекта

Web-сайт - это проект, и этот проект нужно как-то назвать. Поскольку в этой демонстрации я показываю только процесс разработки, а полученное в итоге приложение не будет развёрнуто в сеть, хотя отработка такого развёртывания в виртуальную сеть будет показана, я дам этому приложению простое имя - sitefish. Под этим именем приложение будет храниться в одноимённом каталоге на рабочем компьютере web-мастера, в Git и, впоследствии, на сервере сети.

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

Определяем место хранения файлов проекта

Работать над sitefish я буду под управлением операционной системы Debian - это основная и единственная операционная система на моём домашнем компьютере, она очень хорошо совместима с web-разработкой и удобна для использования и на десктопе, и на сервере. Проект впоследствии необходимо развернуть на сервер сети, и там тоже будет Debian.

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

Для sitefish я создам отдельный одноимённый каталог внутри workspace - этот каталог далее в процессе описания я буду называть корневым каталогом sitefish. В корневом каталоге будет храниться вложенный каталог с именем sitefish и вспомогательные файлы проекта - этот каталог далее в процессе этого описания я буду называть базовым каталогом sitefish. В базовом каталоге я буду хранить файлы web-приложения.

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

$ mkdir -p ~/workspace/sitefish/sitefish

Как видно по содержанию команды, в моём распоряжении появились два одноимённых каталога, один из которых вложен внутрь другого. Вхожу в корневой каталог sitefish.

$ cd ~/workspace/sitefish/

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

Создаём структуру каталогов и файлов приложения

В корневом каталоге sitefish мне будут необходимы следующие файлы:

  • файл настроек web-приложения с именем .env;

  • файл сценария для запуска отладочного сервера с именем runserver.py.

В рабочем терминале выполняю последовательно две команды.

$ touch .env
$ touch runserver.py

В базовом каталоге sitefish мне будут необходимы следующие каталоги и файлы:

  • sitefish/__init__.py - файл конфигурации ASGI-приложения Starlette;

  • sitefish/main - каталог для хранения файлов главной подпрограммы sitefish;

  • sitefish/templates - каталог для хранения html-шаблонов страниц будущего сайта;

  • sitefish/main/views.py - файл обработчиков HTTP запросов главной подпрограммы;

  • sitefish/static и вложенные в него css, js и images - каталог для хранения статических файлов web-приложения, которыми определяется фронтэнд web-приложения;

  • index.html, about.html и contacts.html - шаблоны html-страниц будущего сайта, все три шаблона будут храниться внутри каталога templates в соответствующем подкаталоге main - шаблоны принадлежат главной подпрограмме;

  • index.js - файл JavaScript сценария, пока не привязанный ни к одной из страниц, на этом этапе нужен только для тестирования URL-адреса из каталога static.

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

$ touch sitefish/__init_.py
$ mkdir sitefish/main
$ mkdir -p  sitefish/templates/main
$ mkdir -p sitefish/static/css
$ mkdir -p sitefish/static/js
$ mkdir -p sitefish/static/images
$ touch sitefish/main/__init__.py
$ touch sitefish/main/views.py
$ touch sitefish/templates/main/index.html
$ touch sitefish/templates/main/about.html
$ touch sitefish/templates/main/contacts.html
$ touch sitefish/static/js/index.js

Вот как выглядит мой терминал после проделанных действий.

eSqXDtEncf.png

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

eHw7kNjNRv.png

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

Конфигурация ASGI приложения Starlette

Полученное только что дерево файлов и каталогов мне нужно каким-то образом встроить в ASGI-приложение Starlette, для этого открываю в текстовом редакторе, для этой демонстрации я использовал Vim - с ним удобно, файл __init__.py из базового каталога sitefish.

$ vim sitefish/__init__.py

Пишу в этот файл следующий код.

# sitefish/__init__.py

## модуль os нужен для привязки ключевых каталогов
import os

## с помощью jinja2 и typing я настрою шаблонизатор 
import jinja2
import typing

## базовые инструменты Starlette для создания и настройки
## ASGI-приложения Starlette 
from starlette.applications import Starlette
from starlette.config import Config
from starlette.routing import Mount, Route
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates

## библиотека webassets даст возможность минимизировать
## код фронтэнда и скомпоновать таблицы стилей и сценарии
## избегая повторения кода
from webassets import Environment as AssetsEnvironment
from webassets.ext.jinja2 import assets

## подключаю обработчики запросов web-клиентов
## эти функции я создам на следующем шаге разработки
from .main.views import show_index, show_favicon, show_page 

## выполняю привязку к ключевым каталогам приложения
base = os.path.dirname(__file__)
static = os.path.join(base, 'static')
templates = os.path.join(base, 'templates')

## выполняю привязку к файлу настроек .env
settings = Config(os.path.join(os.path.dirname(base), '.env'))
## настраиваю шаблонизатор и подключаю к нему
## инструменты webassets
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)
        assets_env = AssetsEnvironment(static, '/static')
        assets_env.debug = settings.get('ASSETS_DEBUG', bool)
        env_options.setdefault("loader", loader)
        env_options.setdefault("autoescape", True)
        env_options.setdefault("extensions", [assets])
        env = jinja2.Environment(**env_options)
        env.assets_environment = assets_env
        return env

## создаю ASGI приложение и определяю его ключевые
## URL-адреса с помощью классов Route и Mount
app = Starlette(
    debug=settings.get('DEBUG', cast=bool),
    routes=[
        Route('/', show_index, name='index'),
        Route('/favicon.ico', show_favicon, name='favicon'),
        Route('/{page}', show_page, name='page'),
        Mount('/static', app=StaticFiles(directory=static), name='static')])

## подключаю в ASGI приложение файл настроек
app.config = settings
## подключаю шаблонизатор html-файлов
app.jinja = J2Templates(directory=templates)

Здесь следует обратить внимание, что на данный момент я определил в web-приложении sitefish четыре ключевых URL-адреса:

  • / - стартовая страница сайта;

  • /favicon.ico - иконка для избранного;

  • /{page} - группа адресов html-страниц сайта, которая обрабатывается единым обработчиком, адреса в которой имеют единую форму, может дополняться новыми страницами в зависимости от желания web-мастера и наличия в каталоге шаблонов шаблона с именем page, об этом чуть позже обязательно поговорим;

  • /static - группа адресов для файлов из каталога static - статические файлы сайта, таблицы стилей, сценарии JS, изображения и так далее.

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

$ vim .env

И пишу в этот файл следующий код.

DEBUG = True
ASSETS_DEBUG = True

Поскольку в карте адресов будущего сайта я определил адрес /favicon.ico, мне будет необходим файл изображения с таким именем. О макете сайта мы будем говорить чуть позже в этом цикле статей, пока оговорюсь, что для разрабатываемого сайта я буду использовать макет CodeJ, и оформлены его страницы будут точно так же, как страницы сайта, на котором вы сейчас читаете это описание. Копирую favicon.ico в каталог images, который расположен внутри каталога static.

Созданное только что ASGI-приложение требует три обработчика с именами:

  • show_index;

  • show_page;

  • show_favicon.

Эти три функции представления будут размещаться в файле main/views.py, открываю его в текстовом редакторе.

$ vim sitefish/main/views.py

Пишу в этот файл следующий код:

# sitefish/main/views.py

## с помощью модуля os я выполню привязку
## к файлу favicon.ico в теле соответствующей
## функции представления
import os

## мне потребуются три типа HTTP ответа,
## два из них я возьму из соответствующего модуля Starlette
from starlette.responses import FileResponse, RedirectResponse

## обработчик запросов к URL-адресу
## стартовой страницы сайта
async def show_index(request):
    ## здесь я при помощи шаблонизатора
    ## рендерю заданный html-шаблон,
    ## формирую HTTP ответ и возвращаю его клиенту
    return request.app.jinja.TemplateResponse(
        'main/index.html',
        {'request': request})

## обработчик URL-адреса /{page}
## в адресе клиент передаёт имя страницы
async def show_page(request):
    ## получаю переданное клиентом имя
    ## запрашиваемой страницы
    page = request.path_params.get('page')
    ## если это имя совпадает с index.html
    if page == 'index.html':
        ## в этом случае делаю перенаправление
        ## на стартовую страницу сайта со статус кодом 301
        return RedirectResponse(request.url_for('index'), 301)
    ## если клиент в URL-адресе передал другое имя
    ## формирую адрес шаблона запрашиваемой страницы
    template = f'main/{page}'
    ## нахожу этот шаблон в каталоге templates
    ## по адресу в только что созданной переменной
    ## и рендерю этот шаблон шаблонизатором
    ## полученный в итоге HTTP ответ возвращаю клиенту
    return request.app.jinja.TemplateResponse(
        template,
        {'request': request})

## обработчик URL-адреса иконки для избранного
async def show_favicon(request):
    ## если полученный от клиента запрос
    ## является GET запросом
    if request.method == 'GET':
        ## привязываю базовый каталог sitefish
        base = os.path.dirname(os.path.dirname(__file__))
        ## в базовом каталоге, во вложенном каталоге
        ## static, в его вложенном каталоге images
        ## нахожу файл изображения и отдаю его адрес
        ## классу FileResponse
        ## полученный в итоге HTTP ответ возвращаю клиенту
        return FileResponse(
            os.path.join(base, 'static', 'images', 'favicon.ico'))

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

Сценарий запуска отладочного сервера

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

Сценарий запуска будет храниться в файле runserver.py, файл в структуре приложения уже имеется, открываю его в текстовом редакторе.

$ vim runserver.py

Код этого сценария достаточно прост, вот как он выглядит.

# runserver.py

## подключаю uvicorn
import uvicorn

if __name__ == '__main__':
    ## в стандартной идиоме Питона
    ## запускаю отладочный сервер,
    ## в качестве параметра передаю серверу
    ## объект app из файла конфигурации __init__.py
    ## кроме этого передаю адрес хоста и порт
    ## на котором отладочный сервер будет слушать сеть
    uvicorn.run(
        'sitefish:app', host='127.0.0.1',
        reload=True, port=5000, log_level='info')

Теперь у меня есть всё, чтобы запустить web-приложение в отладочном сервере, но нет самого главного - я не установил используемые в предложенном коде библиотеки.

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

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

$ python3 -m venv venv

В результате этого в корневом каталоге sitefish появится вложенный каталоге с именем venv - как заказывал в команде. Это виртуальное окружение нужно активировать.

$ source venv/bin/activate

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

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

$ pip install --upgrade wheel

И теперь я могу установить в это виртуальное окружение все использованные в коде sitefish библиотеки. Делаю это вот такой простой командой.

$ pip install starlette jinja2 webassets uvicorn

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

2hA2RyTz1g.png

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

Верстаем шаблоны

Мне необходимо запустить отладочный сервер и зайти на хост, который сервер обслуживает web-браузером. Файлы html-шаблонов у нас уже есть, но в них нет кода. И сейчас я должен сверстать начальные версии html-страниц будущего сайта. В рамках этой демонстрации достаточно будет трёх страниц. Открываю в текстовом редакторе шаблон стартовой страницы сайта.

$ vim sitefish/templates/main/index.html

На текущем этапе разработки меня устроит простой и поддающийся валидации html-код, вот как он выглядит у стартовой страницы.

<!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>Index Page</title>
  </head>
  <body>
    <div>Сайт в стадии разработки...</div>
    <div>Стартовая страница.</div>
    <div><a href="/about.html">О компании</a></div>
    <div><a href="/contacts.html">Контакты</a></div>
  </body>
</html>

Открываю в текстовом редакторе шаблон второй страницы.

$ vim sitefish/templates/main/about.html

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

<!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>About</title>
  </head>
  <body>
    <div>Сайт в стадии разработки...</div>
    <div>О компании.</div>
    <div><a href="/index.html">На главную</a></div>
    <div><a href="/contacts.html">Контакты</a></div>
  </body>
</html>

И, наконец, открываю шаблон третьей страницы.

$ vim sitefish/templates/main/contacts.html

Ему даю вот такой код.

<!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>Contacts</title>
  </head>
  <body>
    <div>Сайт в стадии разработки...</div>
    <div>Страница с контактами.</div>
    <div><a href="/index.html">На главную</a></div>
    <div><a href="/about.html">О компании</a></div>
  </body>
</html>

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

У нас есть ещё и файл сценария index.js, с его помощью я протестирую в браузере файлы каталога /static, его тоже открываю в текстовом редакторе и даю ему следующий простой код.

// index.js
console.log('Hello, world!');

Чуть позже этот код мы увидим в окне браузера при запросе к соответствующему URL-адресу приложения.

Тестируем приложение в браузере

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

(venv) jazz@desktop:~/workspace/sitefish$ python runserver.py

Здесь я показал кроме самой команды ещё и внешний вид приглашения командной строки терминала. В этом терминале активно созданное выше виртуальное окружение venv. Убеждаюсь, что программа не выдала в терминал ошибок. Запускаю браузер и стучусь по URL-адресу стартовой страницы web-приложения - localhost:5000.

RJiqapGIVq.png

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

uFS4njaj8Z.png

Опять вижу две ссылки, обращаю внимание на URL-адрес в адресной строке браузера. Пробую перейти по второй ссылке - "Контакты".

Jhfah6E91W.png

И оказываюсь на третьей странице будущего сайта. В моём распоряжении опять есть две ссылки, и я могу вернуться на любую из предложенных страниц. Обращаю внимание на иконку на активной вкладке браузера - она соответствует файлу favicon.ico из каталога static.

Попробуем получить доступ к файлу JS-сценария из каталога static, в этом каталоге есть файл index.js, вбиваю в адресную строку соответствующий адрес.

e0oFOPmYW6.png

Браузер показал код, который я написал в этом файле ранее. Все имеющиеся в карте URL-адресов ASGI-приложения адреса работают в штатном режиме, как запрограммировано, и отладочный сервер на этих адресах показывает ожидаемый контент. Давайте посмотрим в терминал и на его выхлоп.

GCPrY5Hmc2.png

Для каждого отправленного браузером запроса отладочный сервер показал отчёт, все обработанные сервером запросы имеют статус код 200 и отметку OK. Ещё раз обращаю внимание на окно браузера, меня интересует иконка на вкладке - иконка отображается. В выхлопе сервера отсутствуют запросы со статус кодом 404 - все протестированные составляющие web-приложения работают. Цель этого этапа разработки достигнута.

Продолжение следует

В следующих стадиях разработки sitefish я последовательно покажу вёрстку уже существующих страниц будущего сайта, поговорим о макете, оформлении страниц, фронтэнде и базовом шаблоне, с помощью которого я минимизирую повторение html-кода в шаблонах страниц и упорядочу таблицы стилей и JS-сценарии. Полное описание стадий разработки этого web-приложения можно найти по одноимённой ссылке в начале этого абзаца или по одноимённой метке в разделе "Метки" ниже.

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