Меню сайта на Flask

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

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

Выполнить запрос к БД для получения меню не так просто. Сначала задумаемся, в каком виде данные будут передаваться в шаблон? Это может быть словарь, в котором ключом каждого элемента является название раздела, а значением ‒ список кортежей. Каждый кортеж будет представлять собой пару: названий статьи и ее адрес. То есть одна запись словаря будет выглядеть примерно так:

"Растения": [("botany/plants", "Общее о растениях"), 
             ("botany/flower", "Строение цветка"),
             …]

Количество подобных элементов в словаре будет равно количеству разделов сайта.

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

con = sqlite3.connect(db_name)
sections = con.execute('SELECT id, name FROM sections \
                        ORDER BY pos').fetchall()
menu = {}
for theme_id, theme_name in sections:
    articles = con.execute(
        'SELECT path, h1 FROM articles WHERE theme=? ORDER BY pos_in_theme',
        (theme_id, )).fetchall()
    menu[theme_name] = articles
con.close()

Передадим значение переменной menu в шаблон в вызовах функции render_template. Например:

render_template('create.html', sections=sections, menu=menu)

В шаблоне base.html, от которого наследует create.html, в том месте, где должно находится навигационное меню сайта, напишем такой код:

{% for theme, articles in menu.items() %}
  <button class="accordion">{{theme}}</button>
  <div class="panel">
    <ul>
      {% for i in articles %}
        <li><a href="/{{i[0]}}">{{i[1]}}</a></li>
      {% endfor %}
    </ul>
  </div>
{% endfor %}

Обратите внимание, что html-теги и классы специфичны для конкретного стиля оформления и html-структуры меню. Если у вас другой шаблон, то код буквально не подойдет. Только извлечение составляющих словаря.

После этого на сайте должно появиться меню со ссылками на ранее созданные страницы.

Однако если добавить новую статью или внести правки в существующую (поменять заголовок, раздел или позицию в нем), то сайдбар не обновится. Новая ссылка не появится, как и не произойдет изменения названия и места существующей. Для обновления меню вам потребуется перезапустить сервер.

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

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

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

def get_sections():
    con = sqlite3.connect(db_name)
    sections = con.execute('SELECT id, name FROM sections \
                            ORDER BY pos').fetchall()
    con.close()
    return sections


def get_menu():
    sections = get_sections()
    con = sqlite3.connect(db_name)
    menu = {}
    for theme_id, theme_name in sections:
        articles = con.execute(
            'SELECT path, h1 FROM articles WHERE theme=? \
            ORDER BY pos_in_theme',
            (theme_id, )).fetchall()
        menu[theme_name] = articles
    con.close()
    return menu

Теперь надо решить, откуда эти функции вызывать? Можно делать это из функций-представлений при передаче аргументов в render_template():

render_template('create.html', sections=get_sections(), menu=get_menu())

В этом случае в шаблоне ничего не придется менять.

Однако поступим немного по-другому. Будем передавать в шаблон сами функции, а их вызов выполнять уже там.

render_template('create.html', 
                get_sections=get_sections, get_menu=get_menu)

или

render_template('create.html',
                get_sections=get_sections, get_menu=get_menu, post=p)

В самом шаблоне, до того как использовать переменные sections и menu, надо вызвать переданные функции. В файл base.html перед кодом разметки меню добавим:

{% set menu = get_menu() %}

В файл create.html надо добавить:

{% set sections = get_sections() %}

Обратим внимание, что в коде flask-приложения функция render_template вызывается несколько раз, и делается это с почти одинаковым набором аргументов. Микрофреймворк Flask предоставляет возможность помещать идентификаторы в словарь глобальных объектов для Jinja. Добавим в конце кода admin.py следующую строку (за пределами какой-либо функции):

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

После этого вызовы render_template следует сократить до

render_template('create.html')

и

render_template('create.html', post=p)

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




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