使用蓝图的模块化应用¶
更新日志
版本 0.7 新增。
Flask 使用 *蓝图* 的概念来创建应用组件,并支持应用内或跨应用的常见模式。蓝图可以极大地简化大型应用的工作方式,并为 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
)。蓝图的名称不会修改 URL,只会修改端点。
注册蓝图¶
那么如何注册该蓝图呢?像这样
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'
要从这个文件夹快速打开源文件,你可以使用 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_prefix
+ /static
处可用。如果蓝图的前缀为 /admin
,则静态 URL 将为 /admin/static
。
端点被命名为 blueprint_name.static
。你可以使用 url_for()
生成指向它的 URL,就像你对应用的静态文件夹所做的那样
url_for('admin.static', filename='style.css')
但是,如果蓝图没有 url_prefix
,则无法访问蓝图的静态文件夹。这是因为在这种情况下 URL 将是 /static
,并且应用的 /static
路由优先。与模板文件夹不同,如果文件在应用静态文件夹中不存在,则不会搜索蓝图静态文件夹。
模板¶
如果你希望蓝图公开模板,你可以通过将 template_folder
参数提供给 Blueprint
构造函数来实现
admin = Blueprint('admin', __name__, template_folder='templates')
对于静态文件,路径可以是绝对路径,也可以是相对于蓝图资源文件夹的相对路径。
模板文件夹被添加到模板的搜索路径中,但优先级低于实际应用的模板文件夹。这样,你可以轻松地覆盖蓝图在实际应用中提供的模板。这也意味着,如果你不希望蓝图模板被意外覆盖,请确保没有其他蓝图或实际应用模板具有相同的相对路径。当多个蓝图提供相同的相对模板路径时,第一个注册的蓝图优先于其他蓝图。
因此,如果你的蓝图在文件夹 yourapplication/admin
中,并且你想渲染模板 'admin/index.html'
,并且你已将 templates
作为 template_folder
提供,你将需要创建一个像这样的文件: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
配置变量,这将指示 Flask 打印出它在每次 render_template
调用时查找模板所经历的步骤。
构建 URL¶
如果你想从一个页面链接到另一个页面,你可以使用 url_for()
函数,就像你通常会做的那样,只需在 URL 端点前加上蓝图的名称和一个点(.
)
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
的调用中调用;它们不是由例如无效的 URL 访问调用的。这是因为蓝图不“拥有”特定的 URL 空间,因此如果给定无效的 URL,应用实例无法知道它应该运行哪个蓝图错误处理程序。如果你想根据 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
请参阅 处理应用错误。