Изменение главного flask-приложения для работы с базой данных

Мы написали flask-приложение администратора сайта. Пришло время изменить "пользовательское" в файле project.py таким образом, чтобы оно читало данные из базы данных, а не отдавало при запросах множество разных шаблонов.

При этом было бы удобно запускать на одном локальном сервере одновременно оба flask-приложения. Так мы сможем в одном править сайт, а в другом ‒ сразу наблюдать результат. Для этого запустим одно из приложений привычным способом:

flask --app project run --debug

Второе запустим на другом порту:

flask --app admin run --debug --port 5001

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

Чтобы программа project.py работала с базой данных, достаточно переписать функцию page. В ней из БД должна извлекаться статья, в которой значение поля path совпадает с адресом запрошенной страницы. Полученные таким образом данные должны передаваться в подготовленный специально для отображения статей шаблон (create.html с формой здесь уже не подойдет):

@app.route('/<path:file_name>')
def page(file_name):
    c = sqlite3.connect('database.db')
    c.row_factory = sqlite3.Row
    p = c.execute('SELECT path, h1, short_desc, body \
                  FROM articles WHERE path=?',
                  (file_name,)).fetchone()
    c.close()
    if p:
        return render_template('page.html',
                               address='/' + p['path'],
                               title=p['h1'],
                               desc=p['short_desc'],
                               body=Markup(p['body']))
    else:
        abort(404)

Предварительно надо импортировать модуль sqlite3 и класс Markup из markupsafe.

Markup нужен, чтобы html-теги содержимого статьи интерпретировались на html-странице как теги, а не экранировались и выводились как есть.

Обратите внимание, что здесь при вызове render_template мы передаем атрибут address (его значение используется при формировании канонического адреса страницы). В приложении для администратора мы этим пренебрегли с целью уменьшения кода.

Наследующий от base.html шаблон page.html будет иметь такое содержание:

{% extends 'base.html' %}

{% block desc %}{{ desc }}{% endblock %}

{% block content %}

<h1>{% block title %}{{ title }}{% endblock %}</h1>

{{ body }}

{% endblock %}

В теле base.html у нас находится содержимое главной страницы сайта. Часто его тоже берут из базы данных как своего рода выжимку из разных статей, составляя специальный запрос. Например, ссылки на десять последних страниц, добавленных на сайт, с их кратким описанием и датой создания. Мы же не будем усложнять себе жизнь.

Если сейчас в браузере обратиться к любой странице сайта приложения project.py, то возникнет ошибка, связанная с тем, что в base.html для разворачивания меню требуется функция get_menu, которой у нас нет в этом flask-приложении. Чтобы разрешить проблему, можно скопировать из admin.py в project.py определения функций get_sections и get_menu (или вынести эти функции в отдельный py-файл и импортировать), а также в конце кода добавить:

app.jinja_env.globals.update(get_menu=get_menu)

Определение функции get_section необходимо только потому, что она вызывается в теле get_menu. В шаблонах base.html и page.html эта функция не используется.

Однако запрашивать меню из БД при каждом обращении к страницам в пользовательском приложении не целесообразно. Ведь на работающем в Интернете сайте меню всегда остается одинаковым. Поэтому можно скопировать его из загруженной в браузере html-страницы (нужно открыть код: Ctrl + U), которая отдается приложением admin.py, в шаблон page.html. Перед этим в base.html надо обрамить строку с вызовом get_menu, а также теги, отвечающие за формирование меню, в отдельный контейнер:

{% block menu %}{% endblock %}

В такой же блок надо обрамить готовое меню в page.html.

После этого функции get_menu и get_section в файле project.py уже не нужны.

Однако такой подход не совсем удобен, так как можно забыть выполнить копирование меню после внесения изменений на сайте. Кроме того, вы не увидите изменений меню в запущенном приложении project.py, когда правите сайт посредством admin.py.

Более подходящим вариантом создания статичного меню для project.py может быть генерация файла с меню при каждом изменении в базе данных. Этот файл будет включаться в шаблон base.html с помощью команды include шаблонизатора Jinja.

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

Чтобы реализовать такой подход уберем из page.html блок меню (если вы уже потрудились добавить его) со всем его содержимым.

В файле base.html заменим весь блок меню одной инструкцией:

{% include 'menu.html' %}

А вот код в admin.py придется пересмотреть более основательно.

Во-первых, изменим имя функции get_menu на menu_update. Теперь эта функция будет не только получать меню, выполняя запрос к базе данных, она же будет записывать оформленные данные в файл.

После этого надо удалить аргумент get_menu из вызова функции app.jinja_env.globals.update. В шаблон больше меню передаваться не будет, только разделы для списка выбора тем в форме.

В теле функции menu_update вместо выражения return menu допишем код, который выполняет запись в файл menu.html:

file_menu = open('templates/menu.html', 'w')
for theme, articles in menu.items():
    file_menu.write(f'<button class="accordion">{theme}</button>\n')
    file_menu.write('<div class="panel">\n<ul>\n')
    for i in articles:
        file_menu.write(f'<li><a href="/{i[0]}">{i[1]}</a></li>\n')
    file_menu.write('</ul>\n</div>\n')
file_menu.close()

Если сейчас попробовать запросить страницу сайта, то будет выброшено исключение, так как у нас нет файла menu.html. Мы ниоткуда не вызываем функцию menu_update. Откуда это правильным сделать? Мы договорились, что файл-меню будет обновляться всякий раз, когда что-то меняется в меню как следствие изменения состава страниц, их позиций и др. Это значит, функция должна вызываться из нескольких мест. То есть когда:

  1. новая страница только что создана,
  2. изменяется заголовок статьи,
  3. страница "переезжает" в другой раздел,
  4. меняется позиция статьи в разделе,
  5. изменяется адрес страницы (в этом случае адрес ссылки в меню уже другой),
  6. страница удаляется.

Во все эти места программы надо добавить menu_update().

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

Flask для начинающих




Все разделы сайта