Blueprint - подключение к БД и работа с ней

Файл проекта: https://github.com/selfedu-rus/flasksite-21

Обычно, админ-панель использует БД приложения и выполняет определенные действия. Давайте в качестве примера сделаем вывод списка статей и пользователей, зарегистрированных на сайте. Но для начала нам нужно в Blueprint выполнить соединение с БД. Как это сделать, чтобы максимально сохранить модульность приложения? Хорошей практикой является использование декораторов:

  • before_request – перед выполнением запроса;
  • teardown_request – после выполнения запроса.

В первом происходит установление связи с БД, а во втором – закрытие соединения. В нашем тестовом сайте это можно сделать так (в модуле admin.py):

db = None
@admin.before_request
def before_request():
    """Установление соединения с БД перед выполнением запроса"""
    global db
    db = g.get('link_db')
 
@admin.teardown_request
def teardown_request(request):
    global db
    db = None
    return request

Мы здесь в функции before_request обращаемся к глобальной переменной g контекста приложения и берем оттуда значение 'link_db', которое связано со ссылкой на соединение с БД. Эта связь, в свою очередь, устанавливается в основном приложении, когда вызывается функция декоратора:

@app.before_request

Причем, декораторы уровня приложения как бы «обертывают» выполнение декораторов в Blueprint. То есть, их последовательность вызовов будет такой:

@app.before_request
@admin.before_request
@admin.teardown_request
@app.teardown_appcontext

Таким образом, мы просто берем соединение с БД, которое устанавливается основным приложением. Конечно, если архитектура программы изменится и связь с БД будет настраиваться как-то по-другому, то это повлечет необходимость изменения и в модуле Blueprint. Но, ничего не поделаешь: абсолютной независимости не бывает.

Также, для обращения к приложению можно использовать специальную глобальную переменную:

current_app

В некоторых случаях через нее обращаются к объекту конфигурации, чтобы получить ту или иную константу:

current_app.config['DATABASE']

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

<a href="{{ url_for('admin.index') }}">Админ-панель</a></p>

Обратите внимание на префикс ‘admin’ перед именем функции-представления index. Этот префикс соответствует имени Blueprint, к которому мы обращаемся, чтобы уже там взять функцию index и определить ее URL. В данном случае будет возвращен адрес:

http://127.0.0.1:5000/admin/

Вот так следует формировать маршрутизацию из внешнего модуля, используя функции-представления Blueprint.

Отображение списка статей и пользователей

Теперь, когда у нас есть соединение с БД, мы можем в панели администратора вывести список статей и зарегистрированных пользователей. Начнем со статей. Создадим еще одно представление listpubs со следующим содержимым:

@admin.route('/list-pubs')
def listpubs():
    if not isLogged():
        return redirect(url_for('.login'))
 
    list = []
    if db:
        try:
            cur = db.cursor()
            cur.execute(f"SELECT title, text, url FROM posts")
            list = cur.fetchall()
        except sqlite3.Error as e:
            print("Ошибка получения статей из БД " + str(e))
 
    return render_template('admin/listpubs.html', title='Список статей', menu=menu, list=list)

Вначале идет проверка: если пользователь не вошел в админ-панель, то он перенаправляется на страницу авторизации. Далее, проверяем: если соединение с БД установлено, то из таблицы posts выбираются все записи с полями: title, text, url. Затем, читаются все значения и сохраняются в переменной list. В конце возвращается страница из шаблона 'admin/listpubs.html', который выглядит так:

{% extends 'admin/base_admin.html' %}
 
{% block content %}
{{ super() }}
<h1>{{title}}</h1>
<ul class="list-posts">
{% for p in list %}
<li>
<p class="title"><a href="{{ url_for('showPost', alias=p.url)}}">{{p.title}}</a></p>
<p class="annonce">{{ p.text[:50] | striptags  }}</p>
</li>
{% endfor %}
</ul>
{% endblock %}

Здесь все достаточно очевидно, происходит расширение базового шаблона и формируется список из коллекции list.

Протестируем эту реализацию. Добавим в меню новый пункт:

menu = [{'url': '.index', 'title': 'Панель'},
        {'url': '.listpubs', 'title': 'Список статей'},
        {'url': '.logout', 'title': 'Выйти'}]

И после запуска увидим следующее:

По аналогии сделаем отображение списка пользователей. Добавим обработчик:

@admin.route('/list-users')
def listusers():
    if not isLogged():
        return redirect(url_for('.login'))
 
    list = []
    if db:
        try:
            cur = db.cursor()
            cur.execute(f"SELECT name, email FROM users ORDER BY time DESC")
            list = cur.fetchall()
        except sqlite3.Error as e:
            print("Ошибка получения статей из БД " + str(e))
 
    return render_template('admin/listusers.html', title='Список пользователей', menu=menu, list=list)

Здесь все, практически, то же самое, только из БД выбираются пользователи с полями: name и email. И, затем, возвращается страница по шаблону listusers.html:

{% extends 'admin/base_admin.html' %}
 
{% block content %}
{{ super() }}
<h1>{{title}}</h1>
<ul class="list-posts">
{% for p in list %}
<li>
<p class="title">{{p.name}}</p>
<p class="annonce">{{ p.email }}</p>
</li>
{% endfor %}
</ul>
{% endblock %}

Добавим в меню ссылку для этого обработчика:

menu = [{'url': '.index', 'title': 'Панель'},
        {'url': '.listusers', 'title': 'Список пользователей'},
        {'url': '.listpubs', 'title': 'Список статей'},
        {'url': '.logout', 'title': 'Выйти'}]

И, окончательно, получим следующую админ-панель:

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

https://flask.palletsprojects.com/en/1.1.x/blueprints/

Видео по теме