Армин Роначер. Модульные приложения Flask с использованием blueprint'ов, 2012

Перевод статьи: Modular Applications with Blueprints.

Автор: Армин Роначер (Armin Ronacher)

Blueprint'ы доступны с версии 0.7

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

1. Для чего нужны blueprint'ы?

Blueprint'ы во Flask могут пригодиться в случае, если нужно:

Blueprint во Flask не является подключаемым приложением, потому что это на самом деле не приложение - это набор операций, которые могут быть зарегистрированы в приложении, возможно даже не один раз. Почему бы не воспользоваться несколькими объектами приложений? Вы можете это сделать (обратитесь к разделу Диспетчирезация приложений), но ваши приложения будут иметь раздельные файлы конфигурации и будут управляться слоем WSGI.

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

2. Концепция blueprint'ов

Основная концепция blueprint'ов заключается в том, что они записывают операции для выполнения при регистрации в приложении. Flask связывает функции представлений с blueprint'ами при обработке запросов и генерировании URL'ов от одной конечной точки* к другой.

3. Мой первый blueprint

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

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
                        template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
    try:
        return render_template('pages/%s.html' % page)
    except TemplateNotFound:
        abort(404)

При связывании функции при помощи декоратора @simple_page.route, blueprint записывает намерение зарегистрировать в приложении функцию show, когда blueprint будет зарегистрирован. Кроме того, декоратор предварит название конечной точки префиксом - именем blueprint'а, который был указан конструктору Blueprint (в данном случае это тоже simple_page).

4. Регистрация blueprint'ов

Как теперь зарегистрировать этот blueprint? Например, так:

from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page)

Если теперь посмотреть на правила, зарегистрированные в приложении, то можно обнаружить следующее:

[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
<Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>]

Первым обычно является правило для статических файлов самого приложения. Следующие два правила - правила для функции show из blueprint'а simple_page. Как можно заметить, они тоже предварены именем blueprint'а и отделены от него точкой.

Однако, blueprint'ы можно связывать с другими местами:

app.register_blueprint(simple_page, url_prefix='/pages')

И, чтобы убедиться в этом, посмотрим на правила, сгенерированные на этот раз:

[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
<Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>]

Плюс ко всему, можно зарегистрировать blueprint'ы несколько раз, хотя не каждый blueprint будет работать правильно. Это зависит от того, был ли реализован blueprint'е с учётом возможности многократного монтирования.

5. Ресурсы blueprint'а

Blueprint'ы могут, кроме всего прочего, предоставлять ресурсы. Иногда может потребоваться ввести дополнительный blueprint только ради предоставления ресурсов.

5.1. Каталог ресурсов blueprint'а

Как и обычные приложения, blueprint'ы задуманы для размещения в отдельном каталоге. Хотя несколько blueprint'ов можно разместить в одном и том же каталоге, так делать не рекомендуется.

Имя каталога берётся из второго аргумента blueprint'а, которым обычно является __name__. Этот аргумент указывает, какой логический модуль или пакет Python соответствует blueprint'у. Если он указывает на существующий пакет Python (который является каталогом файловой системы), то он и будет каталогом ресурсов. Если это модуль, то каталогом ресурсов будет тот каталог, в котором содержится модуль. Можно обратиться к свойству Blueprint.root_path, чтобы увидеть, что это за каталог:

>>> simple_page.root_path
'/Users/username/TestProject/yourapplication'

Для быстрого открытия ресурсов из этого каталога можно воспользоваться функцией open_resource():

with simple_page.open_resource('static/style.css') as f:
    code = f.read()

5.2. Статические файлы

Blueprint может выставлять наружу каталог со статическими файлами, если в его конструкторе указан каталог файловой системы с помощью аргумента с ключевым словом static_folder. Аргумент может быть абсолютным путём или каталогом относительно каталога blueprint'а:

admin = Blueprint('admin', __name__, static_folder='static')

По умолчанию самая правая часть пути выставляется наружу в веб. Поскольку в данном случае указан каталог с именем static, он будет располагаться внутри каталога blueprint'а и будет называться static. В данном случае при регистрации blueprint'а в каталоге /admin, каталог static будет находиться в /admin/static.

Конечная точка будет иметь имя blueprint_name.static, так что можно генерировать URL'ы точно так же, как это делается для статического каталога приложения:

url_for('admin.static', filename='style.css')

5.3. Шаблоны

Если нужно выставить наружу каталог с шаблонами, это можно сделать указав параметр template_folder конструктору Blueprint:

admin = Blueprint('admin', __name__, template_folder='templates')

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

Например, если есть blueprint в каталоге yourapplication/admin и нужно отрисовать шаблон 'admin/index.html', а в параметре template_folder указан каталог templates, тогда нужно создать файл yourapplication/admin/templates/admin/index.html.

6. Генерирование URL'ов

Если нужно вставить ссылку с одной страницы на другую, можно воспользоваться функцией url_for(), как обычно: нужно просто добавить к конечной точке URL'а префикс с именем blueprint'а и точкой:

url_for('admin.index')

Наконец, если в функции представления blueprint'а или в отрисованном шаблоне нужно добавить ссылку на другую конечную точку того же blueprint'а, можно воспользоваться относительным перенаправлением, добавив префикс, состоящий только из точки:

url_for('.index')

Получится ссылка на admin.index в случае обработки текущего запроса в любой другой конечной точке blueprint'а.

7. Примечания переводчика

* Нашёл такое объяснение понятия конечной точки: Что такое конечная точка?

@app.route('/user/<name>', endpoint='user'):
def view_user(name):
    pass

@app.route('/user/new', endpoint='user'):
def new_user():
    pass

url_for('user') выдаст URL для new_user

url_for('user', name='krace') выдаст URL для view_user

Такой способ не работает, если не динамические части одинаковые.

В данном случае конечной точкой является альтернативное имя user для двух разных представлений. Это имя можно использовать в функции url_for, как показано в примере, или в шаблонах, в виде тега {{ url_for('user') }}, {{ url_for('user', name='krace') }} или {{ url_for('user', name = user.name) }}

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

Написать автору перевода