带有蓝图的模块化应用程序 — Flask 文档

来自菜鸟教程
Flask/docs/2.0.x/blueprints
跳转至:导航、​搜索

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

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

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


蓝图资源

蓝图也可以提供资源。 有时您可能只想为其提供的资源引入蓝图。

蓝图资源文件夹

与常规应用程序一样,蓝图被视为包含在文件夹中。 虽然多个蓝图可以来自同一个文件夹,但并非一定如此,通常不建议这样做。

该文件夹是从 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 路由优先。 与模板文件夹不同,如果应用程序静态文件夹中不存在该文件,则不会搜索蓝图静态文件夹。


模板

如果您希望蓝图公开模板,您可以通过向 Blueprint 构造函数提供 template_folder 参数来实现:

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.index,以防当前请求被分派到任何其他管理蓝图端点。


蓝图错误处理程序

蓝图像 Flask 应用程序对象一样支持 errorhandler 装饰器,因此很容易制作特定于蓝图的自定义错误页面。

以下是“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

请参阅 处理应用程序错误