使有蓝图的模块化应用程序

Changelog

在 0.7 版本加入.

为了在一个或多个应用中,使应用模块化并且支持常用方案, Flask 引入了蓝图 概念。蓝图可以极大地简化大型应用并为扩展提供集中的注册入口。Blueprint 对象与 Flask 应用对象的工作方式类似,但不是一个真正的应用。它更像一个用于构建和扩展应用的蓝图 。

为什么使用蓝图?

Flask中的蓝图适用于以下情况:

  • 将一个应用程序分解为一组蓝图。这对于较大的应用程序是理想的;一个项目可以实例化一个应用程序对象,初始化多个扩展,并注册许多蓝图。

  • 在URL前缀和(或)子域的应用程序上注册蓝图。URL前缀和(或)子域中的参数成为蓝图中所有视图函数的通用视图参数(缺省情况下)。

  • 在具有不同URL规则的应用程序上多次注册蓝图。

  • 通过蓝图提供模板过滤器、静态文件、模板和其他实用程序。蓝图不必执行应用或视图函数。

  • 当初始化一个Flask 扩展时,为以上任意一种用途注册一个蓝图。

flask中的蓝图不是可插入的应用程序,因为它实际上不是一个应用程序——它是一组可以在应用程序上注册的操作,甚至可以多次注册。为什么没有多个应用程序对象?你可以这样做(见 应用程序调度 ,但您的应用程序将有单独的配置,并将在wsgi层进行管理。

而如果使用蓝图,那么应用会在 Flask 层中进行管理,共享配置,并且可以根据需要更改注册的应用程序对象。缺点是,一旦创建了一个应用程序,只有销毁整个应用对象才能注销蓝图。

蓝图的概念

蓝图的基本概念是:在蓝图被注册到应用之后,所要执行的操作的集合。当分配请求时,Flask 会把蓝图和视图函数关联起来,并生成两个端点之前的 URL 。

我的第一张蓝图

以下就是一个非常基本的蓝图。在这种情况下,我们将使用一个蓝图简单地呈现静态模板:

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(f'pages/{page}.html')
    except TemplateNotFound:
        abort(404)

当你使用 @simple_page.route 装饰器绑定一个函数时,蓝图会记录下所登记的 show 函数。当以后在应用中注册蓝图时,这个函数会被注册到应用中。另外,它会把构建 Blueprint 时所使用的名称(在本例为 simple_page )作为函数端点的前缀。

注册蓝图

那么如何注册蓝图呢?可以这样注册蓝图:

from flask import Flask
from yourapplication.simple_page import simple_page

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

如果检查在应用程序上注册的规则,你会发现:

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

第一个显然是来自应用程序本身的静态文件。后面两条是用于蓝图 simple_page 的 show函数的。你可以看到,它们的前缀都是蓝图的名称,并且使用一个点( . )来分隔。

然而,蓝图也可以安装在不同的位置:

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

这样就会形成以下规则:

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

总之,您可以多次注册蓝图,但是并非每个蓝图都会对此做出正确的响应。实际上,是否能够多次注册取决于你的蓝图是如何编写的,是否能根据不同的位置做出正确的响应。

嵌套蓝图

可以将一张蓝图注册到另一张蓝图上。

parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)

子蓝图将获得父的名称作为其名称的前缀,子URL将以父的URL前缀作为前缀。

url_for('parent.child.create')
/parent/child/create

此外,子蓝图将获得其父级的子域,如果存在,则以其子域为前缀,即

parent = Blueprint('parent', __name__, subdomain='parent')
child = Blueprint('child', __name__, subdomain='child')
parent.register_blueprint(child)
app.register_blueprint(parent)

url_for('parent.child.create', _external=True)
"child.parent.domain.tld"

蓝图特定的前请求函数等注册到父级后将为子级触发。如果子对象没有可以处理给定异常的错误处理程序,则将尝试父对象的错误处理程序。

蓝图资源

蓝图还可以提供资源。有时,可能只是想使用一些资源而使用蓝图。

蓝图资源文件夹

和普通应用一样,蓝图一般都放在一个文件夹中。虽然多个蓝图可以共存于同一个文件夹中,但是最好不要这样做。

文件夹由 Blueprint 的第二个参数指定,通常为 __name__ 。这个参数 指定与蓝图相关的逻辑 Python 模块或包。如果它指向实际的python包,那么该包(文件系统上的文件夹)就是资源文件夹。如果它是一个模块,那么该模块所包含的包就是资源文件夹。您可以访问`Blueprint.root_path` 属性来查看资源文件夹是什么:

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

可以使用 Blueprint.open_resource` 函数快速打开此文件夹中的源:

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

静态文件

蓝图的第三个参数是 static_folder 。这个参数用以指定蓝图的静态文件所在的文件夹,它可以是一个绝对路径也可以是相对路径。

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

缺省情况下,路径的最右侧部分是它在Web上暴露的部分。这个可以通过 static_url_path 来改变。因为上例中的文件夹为名称是 static,那么 URL 应该是蓝图的 url_prefix 加上 /static 。如果蓝图注册前缀为 /admin,那么静态文件 URL 就是 /admin/static 。

端点的名称是 blueprint_name.static 。你可以像对待应用中的文件夹一样 使用 url_for() 来生成其 URL:

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

但是,如果蓝图没有 url_prefix ,那么就无法访问蓝图的静态文件夹。这是因为在这种情况下URL将会是 /static ,而应用程序 /static 路线优先。与模板文件夹不同,如果应用程序静态文件夹中不存在该文件,则不会搜索蓝图静态文件夹。

模板

如果你想使用蓝图来暴露模板,那么可以使用 Blueprint的 template_folder 参数:

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

对于静态文件,路径可以是绝对的或相对于蓝图的资源文件夹。

模板文件夹被添加到模板的搜索路径,但优先级低于实际应用的模板文件夹。这样就 可以轻松地重载在实际应用中蓝图提供的模板。这也意味着如果你不希望蓝图模板出现意外重写,那么就要确保没有其他蓝图或实际的应用模板具有相同的相对路径。多个蓝图提供相同的相对路径时,第一个注册的优先。

所以如果在文件夹中你有一个蓝图 yourapplication/admin,并且你想呈现模板 'admin/index.html' ,你已经提供了 templates 作为`template_folder`的参数值,那么 你必须创建这样的文件: :file yourapplication/admin/templates/admin/index.html . 多出一个 admin 文件夹是为了避免模板被实际应用模板文件夹中的 index.html 重载。

进一步重申一下:如果你有一个名为 admin 的蓝图,该蓝图指定的模版文件是 index.html ,那么最好按照如下结构存放模版文件:

yourpackage/
    blueprints/
        admin/
            templates/
                admin/
                    index.html
            __init__.py

然后当你想要呈现模板时就可以使用 admin/index.html 来查找模板。如果没有加载正确的模板,那么应该启用 ``EXPLAIN_TEMPLATE_LOADING``配置变量。启用这个变量以后,每次调用 render_template 时,Flask 会打印出定位模板的步骤,方便调试。

创建URLs

如果要创建页面链接,可以和通常一样使用 url_for() 函数,只是要把蓝图 名称作为端点的前缀,并且用一个点( . )来分隔:

url_for('admin.index')

另外,如果在一个蓝图的视图函数或者被渲染的模板中需要链接同一个蓝图中的其他 端点,那么使用相对重定向,只使用一个点使用为前缀:

url_for('.index')

如果当前请求被分配到 admin 蓝图端点时,上例会链接到 admin.index 。

蓝图错误处理程序

蓝图支持 errorhandler 装饰师就像 Flask 应用程序对象,因此很容易生成特定于蓝图的自定义错误页。

以下是“404页未找到”异常的示例:

@simple_page.errorhandler(404)
def page_not_found(e):
    return render_template('pages/404.html')

大多数错误处理程序只会按预期工作;但是,对于404和405异常的处理程序有一个警告。这些错误处理程序只能从适当的 raise 声明或呼叫 abort 在blueprint的另一个视图函数中;它们不是由无效的URL访问调用的。这是因为blueprint不“拥有”某个URL空间,因此应用程序实例无法知道如果给定的URL无效,它应该运行哪个blueprint错误处理程序。如果您想基于URL前缀对这些错误执行不同的处理策略,可以使用 request 代理对象:

@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
    if request.path.startswith('/api/'):
        return jsonify(error=str(ex)), ex.code
    else:
        return ex

处理应用程序错误 .