Blueprint - что это такое, где и как использовать

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

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

Так вот, все эти слова я, конечно, говорю не просто так. Фреймворк Flask позволяет разделять большой, сложный проект на набор независимых модулей. Например, на сайте могут быть модули авторизации, админ-панели, подсчета числа просмотров страниц и так далее. Причем, каждый модуль может иметь свои шаблоны, стили оформления, наборы изображений. Все это хранится в отдельном подкаталоге, что делает фрагмент приложения совершенно независимым. И при необходимости, этот каталог можно просто скопировать в другой проект и подключить к другому сайту.

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

Структура подкаталога admin

Вся наша админ-панель будет реализована в отдельном подкаталоге admin со следующей структурой:

В корне этого каталога будет лежать файл admin.py, в котором и реализуем админ-панель с использованием Blueprint. В подкаталоге templates/admin будут располагаться файлы шаблонов, а в static – файлы оформления, js, изображений и другие статические данные, подключаемые к HTML-страницам в панели администратора. Таким образом, мы строго разделяем данные, относящиеся к нашему модулю admin от всех остальных файлов приложения. В дальнейшем, при необходимости, можно будет просто скопировать этот подкаталог в другой проект и в нем реализовать похожую админ-панель.

Здесь может возникнуть вопрос: зачем в подкаталоге templates создавать еще один каталог admin? Фактически, здесь используется Django’вский подход, когда шаблоны в каждом модуле помещаются в дополнительный подкаталог с тем же именем. Это необходимо, т.к. при компиляции проекта все шаблоны собираются в одну кучу и может возникнуть конфликт имен, когда в разных модулях будут файлы с одинаковыми именами. Чтобы этого избежать, как раз и создается дополнительный подкаталог. В этом случае в момент выполнения, шаблоны будут отделяться от других этим подкаталогом.

Создание и регистрация Blueprint

Итак, теперь у нас все готово, чтобы создать Blueprint в модуле admin.py. В начале выполним импорт класса Blueprint:

from flask import Blueprint

и ниже создадим экземпляр этого класса:

admin = Blueprint('admin', __name__, template_folder='templates', static_folder='static')
  • 'admin' – имя Blueprint, которое будет суффиксом ко всем именам методов, данного модуля;
  • __name__ – имя исполняемого модуля, относительно которого будет искаться папка admin и соответствующие подкаталоги;
  • template_folder – подкаталог для шаблонов данного Blueprint (необязательный параметр, при его отсутствии берется подкаталог шаблонов приложения);
  • static_folder – подкаталог для статических файлов (необязательный параметр, при его отсутствии берется подкаталог static приложения).

После создания эскиза его нужно зарегистрировать в основном приложении. Перейдем в файл flsite.py и выполним импорт переменной admin:

from admin.admin import admin

Обратите внимание, мы импортируем именно переменную, а не класс или функцию. Далее, ниже выполним непосредственно регистрацию Blueprint:

app.register_blueprint(admin, url_prefix='/admin')

Здесь admin – ссылка на созданный Blueprint; url_prefix – префикс для всех URL модуля admin. Это необязательный параметр. Без него все URL внутри Blueprint будут записываться непосредственно после домена сайта. Но это не лучшая практика, так как, подключая несколько таких модулей, можно опять же столкнуться с проблемой дублирования URL. Поэтому лучше использовать префик, по которому они будут четко разделяться.

Итак, мы создали Blueprint и зарегистрировали его в приложении. Если теперь перейти по URL:

http://127.0.0.1:5000/admin

то получим ошибку 404 – страница не найдена, так как внутри эскиза не создано ни одного представления. Давайте его добавим.

Маршрутизация в Blueprint

Перейдем в модуль admin.py и пропишем декоратор route:

@admin.route('/')
def index():
    return "admin"

Смотрите, мы вызываем route для admin, а не app, как это делали в основном приложении. Тем самым указываем, что корневая (главная) страница – это страница Blueprint, а не приложения app. Причем, адрес этого URL определяется по правилу:

домен/<url_prefix>/<URL-blueprint>

и в нашем случае будет выглядеть так:

http://127.0.0.1:5000/admin/

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

Авторизация в админ-панели

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

@admin.route('/login', methods=["POST", "GET"])
def login():
    if request.method == "POST":
        if request.form['user'] == "admin" and request.form['psw'] == "12345":
            login_admin()
            return redirect(url_for('.index'))
        else:
            flash("Неверная пара логин/пароль", "error")
 
    return render_template('admin/login.html', title='Админ-панель')

Здесь все достаточно просто и очевидно. Сначала проверяем, что пришли данные по POST-запросу, затем, проверяем правильность логина и пароля и при истинности условий, выполняем авторизацию с помощью функции login_admin, которую пропишем чуть позже. Далее, делается перенаправление на главную страницу админ-панели, а иначе – формируется мгновенное сообщение «Неверная пара логин/пароль». В конце возврашается шаблон 'admin/login.html' с заголовком 'Админ-панель'.

Обратите внимание, как здесь записан параметр в функции

url_for('.index')

Перед index указана точка. Эта точка означает, что функцию-представления index следует брать для текущего Blueprint, а не глобальную из приложения. Если убрать точку, то будет возвращен URL-адрес главной страницы сайта, а не панели администратора. Как вариант, функцию url_for можно еще вызвать и так:

url_for('admin.index’)

Здесь admin – это имя Blueprint, а не название файла admin.py. Например, если изменить имя эскиза на bp, то придется уже прописывать:

url_for(bp.index’)

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

Итак, возвращаясь к обработчику login, добавим функцию login_admin в модуль admin.py:

def login_admin():
    session['admin_logged'] = 1

Мы здесь просто в сессии создаем и сохраняем запись 'admin_logged' со значением 1. И в дальнейшем будем полагать, если она существует, то пользователь зашел в админ-панель.

Некоторые из вас могут задаться вопросом: почему бы нам здесь не использовать рассмотренный ранее модуль Flask-Login? Дело в том, что нельзя создать еще один его экземпляр в рамках одного приложения. А Blueprint – это лишь дополнение, расширение, но не самостоятельная программа. Конечно, мы могли бы передать ссылку на Flask-Login в наш модуль admin и как то его использовать, но тогда теряется концепция независимости и модульности Blueprint. И наша реализация будет ничем не лучше обычного дополнительного вспомогательного класса, записанного в отдельном файле проекта. Поэтому, я авторизацию сделал через сессии.

Давайте здесь же рядом объявим еще две вспомогательные функции:

def isLogged():
    return True if session.get('admin_logged') else False
 
def logout_admin():
    session.pop('admin_logged', None)

Первая проверяет: авторизован ли администратор, а вторая – удаляет из сессии запись об авторизации и будет использоваться при выходе из админ-панели. И сразу пропишем функцию представления logout:

@admin.route('/logout', methods=["POST", "GET"])
def logout():
    if not isLogged():
        return redirect(url_for('.login'))
 
    logout_admin()
 
    return redirect(url_for('.login'))

Шаблоны для страницы админ-панели и авторизации

Теперь давайте добавим первые шаблоны для нашей панели администратора. Я специально для демонстрации сделаю другое оформление и пропишу новый базовый шаблон. В каталоге templates/admin добавлю файл base_admin.html, который будет иметь следующий вид:

<!DOCTYPE html>
<html>
<head>
<link type="text/css" href="{{ url_for('.static', filename='css/styles.css')}}" rel="stylesheet" />
<title>{{ title }}</title>
</head>
<body>
{% if menu -%}
         <ul class="mainmenu">
         {% for p in menu %}
         <li><a href="{{ url_for(p.url) }}">{{p.title}}</a></li>
         {% endfor %}
         </ul>
         <div class="clear"></div>
{%- endif -%}
<div class="content">
{% block content -%}
{% endblock %}
</div>
</body>
</html>

Здесь все, опять же, достаточно очевидно. Обратите внимание, для обращения к каталогу static первый параметр функции

url_for('.static', filename='css/styles.css')

записан с точкой вначале. Эта точка будет указывать брать каталог static из подкаталога admin, то есть, сформируется следующий путь:

admin/static/css/styles.css

Далее, в шаблоне идет отображение меню, если оно передается в шаблон и записан именованный блок content для добавления информации в дочерних шаблонах. И первый такой шаблон будет login.html со следующим содержимым:

{% extends 'admin/base_admin.html' %}
 
{% block content %}
{{ super() }}
<div id="login" class="wnd_dlg_back login_wnd">
<div class="login">
         <p class="title">Админ-панель</p>
{% for cat, msg in get_flashed_messages(True) %}
<div class="flash {{cat}}">{{msg}}</div>
{% endfor %}
         <form action="" method="post">
                   <label class="form-label">Логин: </label><input type="text" name="user" />
                   <label class="form-label">Пароль: </label><input type="password" name="psw" />
                   <p align="center"><input class="login_button" type="submit" value="Войти">
         </form>
</div>
</div>
{% endblock %}

Мы здесь отображаем форму авторизации, где пользователь вводит логин/пароль.

А шаблон index.html будет пока пустой:

{% extends 'admin/base_admin.html' %}
 
{% block content %}
{{ super() }}
{% endblock %}

Далее, нам нужно прописать стили оформления. Они представлены в файле

admin/static/css/styles.css

(подробнее см. в файле проекта).

И перед первым пробным тестированием, изменим обработчик главной страницы админ-панели:

@admin.route('/')
def index():
    if not isLogged():
        return redirect(url_for('.login'))
 
    return render_template('admin/index.html', menu=menu, title='Админ-панель')

Вначале идет проверка: если пользователь не авторизован, то он перенаправляется на страницу авторизации. Иначе, будет отображена панель администратора. Здесь в шаблон 'admin/index.html' передаются два параметра: menu и title. И для menu пропишем следующую коллекцию:

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

Все, теперь при запуске, мы увидим окно авторизации и, вводя admin/12345, перейдем в панель администратора.

Видео по теме