蓝图 Blueprint
蓝图 Blueprint
模块化
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过django的子应用管理,flask程序进行可以进行类似的模块化处理保存代码。
简单来说,Blueprint 是一个存储视图方法/模型代码的容器(目录),这些操作在这个Blueprint 被注册到flask的APP实例对象应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理客户端请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个项目可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名,就是路由前缀
- 在一个flask项目中,一个BluePrint模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个flask项目初始化时,就应该要注册需要使用的Blueprint
注意:flask中的Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个flask项目中。
1. 蓝图的简单使用:
方法一:views.py 文件不写路由,防止循环导包。
(1)手动创建一个蓝图的包目录,例如users,并在__init__.py文件中引入蓝图并创建蓝图对象
from flask import Blueprint
# static_folder:静态文件目录, static_url_path:访问静态文件目录路径(不写默认路径是static_folder)。
# 设置了static_folder就可以通过.../st/1.png 访问到static_folder下的文件。
user = Blueprint("users",__name__,static_folder="static",static_url_path="st",url_prefix="/users")
(2)在这个users蓝图目录下, 创建views.py文件,保存当前蓝图使用的视图函数
避免循环导包,views.py文件下的视图函数就不写@app.route("/")了
# 光写视图,不用写路由
def index():
return "ok, users.views.index"
(3)在users/__init__.py中引入views.py中所有的视图函数并绑定路由
from flask import Blueprint
from . import views
# static_folder:静态文件目录, static_url_path:访问静态文件目录路径(不写默认路径是static_folder)。
# 设置了static_folder就可以通过.../st/1.png 访问到static_folder下的文件。
user = Blueprint("users",__name__,static_folder="static",static_url_path="st",url_prefix="/users")
user.add_url_rule(rule="/index",view_func=views.index)
(4)在主应用main.py文件中的app对象上注册这个users蓝图对象
from users import user
app.register_blueprint(user)
当这个应用启动后,通过/users/可以访问到蓝图中定义的视图函数
方法二:views.py 文件写路由,注意导包位置,避免循环导包
users/__init__.py代码:
from flask import Blueprint
user = Blueprint("users",__name__)
# 必须写在蓝图对象下,不然会循环导包
from . import views
views.py代码:
from users import user
@home.route("/users")
def home():
return "users"
最后在manage.py注册蓝图:
app.register_blueprint(user)
2. 循环导包
注意点:
- python中允许循环导包,但是导入的结构要合理
- 例如上面
方法二中,是在蓝图对象下导入就可以,
因为views.py中from users import users本质上是要导入users/__init__.py下的home这个蓝图对象,
第一次导入找到了这个蓝图对象,还会继续往下走,走到from . import views时,就又回到views.py的from home import home,
开始第二次导包,这次就发现 home 这个蓝图对象已经被导过了,就不会继续往下走了。
3. 蓝图运行机制(看源码并掌握)
- 蓝图Blueprint是保存了一组将来可以在flask应用对象上执行的操作,注册路由/视图编写就是一种操作
- 在视图上方调用 app.route 装饰器注册路由时,这个操作本质就是将修改视图和url地址的映射关系添加到app.url_map路由表
- 蓝图对象根本没有路由表,当我们在蓝图中的视图函数上调用route装饰器(或者add_url_role)注册路由时,它只是在蓝图对象的内部的defered_functions(延迟操作记录列表)中添加了一个路由项(路由项实际上就是一个绑定了视图和url地址的匿名函数)
- 当执行app.register_blueprint() 时,应用对象app会将从蓝图对象的 defered_functions 列表中取出每一个路由项,并把app对象自己作为参数执行路由项对应的匿名函数,匿名函数执行以后就调用app.add_url_rule() 方法,这将蓝图下之前暂存的路由信息全部添加到了app.url_map路由表中了。
蓝图的url前缀
- 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(不加默认是"/")
from flask import Blueprint
from . import views
# url_prefix:路由前缀
user = Blueprint("users",__name__,url_prefix="/users")
user.add_url_rule(rule="/index",view_func=views.user)
- 在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的子路由而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
- (知道就行,一般不用)url_for在使用时,如果要生成一个蓝图里面的视图对应的路由地址,则需要声明当前蓝图名称+视图名称
url_for('users.home') # /users + /home /users就是蓝图中的路由前缀 /home就是子路由
注册蓝图中的静态文件
在users/__init__.py中,
from flask import Blueprint
from . import views
# static_folder:静态文件目录, static_url_path:访问静态文件目录路径(不写默认路径是static_folder)
user = Blueprint("users",__name__,static_folder="static",static_url_path="st")
user.add_url_rule(rule="/index",view_func=views.user)
蓝图下保存与加载模板
(1)在views.py下,
from flask import render_template
def user():
data={}
data['name'] = '大明'
return render_template("index.html",**data)
(2)若要模板保存在蓝图目录下,则在蓝图目录下创建templates目录存放模板文件。
并制定模板文件的目录:
from flask import Blueprint
from . import views
# template_folder:指定访问模板文件的目录
user = Blueprint("hcjusers",__name__,template_folder="templates")
user.add_url_rule(rule="/index",view_func=views.user)
(3)在templates下创建index.html。
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是模板</h1>
<h2>{{ name }}</h2>
</body>
</html>
此模式并不好,因为前端代码并不喜欢夹杂在后端,而且还有bug:
在项目工作目录下刚好也有个templates文件夹存放html代码,而flask默认就是访问工作目录下的templates,
app注册蓝图后还是会覆盖蓝图指定的templates,所以一访问就访问到了工作目录下的templates里的html文件了。

解决办法:
在实例化Flask时,指定模板目录为空,或者None
app = Flask(__name__,template_folder="")
4. 项目中蓝图的初始化
蓝图初始化
在application下创建apps目录,apps以后专门用于保存项目的每一个蓝图,并在apps创建home蓝图目录,并在__init__.py文件中创建蓝图对象
(1)通过自定义终端命令, 创建一个自动生成蓝图目录的命令.application/utils/commands.py,代码:
import os
from flask_script import Command, Option
class BlueprintCommand(Command):
"""生成蓝图"""
name = "blue"
option_list = [
Option('--name', '-n', dest='name'),
]
def run(self, name):
# 生成蓝图名称对象的目录
os.mkdir(name)
open("%s/__init__.py" % name, "w")
open("%s/views.py" % name, "w")
open("%s/models.py" % name, "w")
open("%s/urls.py" % name, "w")
open("%s/test.py" % name, "w")
open("%s/tasks.py" % name, "w")
open("%s/marshmallow.py" % name, "w")
open("%s/socket.py" % name, "w")
open("%s/api.py" % name, "w")
print("蓝图%s创建完成...." % name)
上面的命令就可以帮我们完成项目中生成蓝图的功能,就下来我们就可以直接把命令注册到manage对象中就可以使用了.
但是, 我们往后的开发中肯定还会继续的需要进行自定义终端命令,
所以我们声明一个load_command的自动注册函数,可以自动帮我们完成加载注册自定义终端命令的过程.
(2)声明load_command自动注册命令函数
application/utils/commands.py,代码:
import os,inspect
from flask_script import Command, Option
from importlib import import_module
# 调用的时候传个manager对象
def load_commands(manager, command_path=None):
"""根据指定导包路径注册自定义命令到manage中"""
if command_path is None:
command_path = "application.utils.commands"
# 根据路径导包
module = import_module(command_path)
# 内置模块:inspect 反射 ,从当前模块把所有成员提取出来
# 把模块中为类的成员提取出来(isclass)
class_list = inspect.getmembers(module, inspect.isclass)
# print( f"class_list={class_list}" )
"""
[
('BlueprintCommand', <class 'application.utils.commands.BlueprintCommand'>),
('Command', <class 'flask_script.commands.Command'>),
('Option', <class 'flask_script.commands.Option'>)
]
"""
for class_item in class_list:
if issubclass(class_item[1], Command) and class_item[0] != "Command":
# 注册命令类到manage中
manager.add_command(class_item[1].name, class_item[1])
class BlueprintCommand(Command):
"""生成蓝图"""
name = "blue"
option_list = [
Option('--name', '-n', dest='name'),
]
def run(self, name):
# 生成蓝图名称对象的目录
os.mkdir(name)
open("%s/__init__.py" % name, "w")
open("%s/views.py" % name, "w")
open("%s/models.py" % name, "w")
open("%s/urls.py" % name, "w")
open("%s/test.py" % name, "w")
open("%s/tasks.py" % name, "w")
open("%s/marshmallow.py" % name, "w")
open("%s/socket.py" % name, "w")
open("%s/api.py" % name, "w")
print("蓝图%s创建完成...." % name)
打印class_list:
打开pycharm终端命令:
(mofang) moluo@ubuntu:~/Desktop/mofangapi$ python manage.py shell
>>> from application.utils import commands
>>> from application import manage
>>> commands.load_commands(manage)
class_list=[
('BlueprintCommand', <class 'application.utils.commands.BlueprintCommand'>),
('Command', <class 'flask_script.commands.Command'>),
('Option', <class 'flask_script.commands.Option'>)
]
>>>
(3)自动调用声明的load_command函数
在application/__init__.py下,
...
from flask_script import Manager
from application.utils.commands import load_commands
# 终端脚本工具初始化
manage = Manager()
def init_app(config_path):
...
# 自动注册自定义命令
load_commands(manage)
return manage
测试效果:
先启动项目,然后在pycharm终端上,输入
python manage.py
结果如下:

这样就自动把自定义命令注册进去了。
自动注册蓝图
有了蓝图以后,接下来我们就可以把视图代码,模型代码,路由代码等存储到蓝图目录下了,但是我们需要把蓝图注册到app应用对象下,想想以后,是不是会出现很多的蓝图?
所以我们能不能像上面的自动注册命令那样去自动注册蓝图?可以的,我们也可以封装注册蓝图的功能到一个函数中.让程序自动识别并注册.
项目中的蓝图可以有很多,但是有些蓝图可能并不能提供给客户端访问,所以我们需要在配置文件中声明一个蓝图注册列表, 在蓝图自动注册的函数中只注册列表中填写的蓝图。
(1)在dev配置文件下,添加一个蓝图列表
application/settings/dev.py,代码:
"""蓝图列表"""
INSTALL_BLUEPRINT = [
"application.apps.home",
]
(2)在application.utils下创建并声明一个resgister_blueprint函数
函数中针对注册到项目配置文件中的INSTALL_BLUEPRINT蓝图列表选项的蓝图内容实现自动注册到app应用对象里面。
application/utils/blueprint.py,代码:
from flask import Blueprint
def register_blueprint(app):
"""自动注册蓝图"""
# 从配置文件中读取需要注册到项目中的蓝图路径信息
blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
# 遍历蓝图路径列表,对每一个蓝图进行初始化
for blueprint_path in blueprint_path_list:
# 获取蓝图路径中最后一段的包名作为蓝图的名称
blueprint_name = blueprint_path.split(".")[-1]
# 创建蓝图对象
blueprint = Blueprint(blueprint_name,blueprint_path)
# 把蓝图对象注册到app实例对象
# todo url_prefix 是地址前缀,将来我们将来实现一个总路由来声明它
app.register_blueprint(blueprint,url_prefix="")
(3)项目入口文件application.__init__中, 调用register_blueprint方法, 自动注册蓝图.
application/__init__.py,代码:
from application.utils.blueprint import register_blueprint
def init_app(config_path):
"""用于创建app实例对象并完成初始化过程的工厂函数"""
app = Flask(__name__)
...
# 自动注册蓝图
register_blueprint(app)
return manage
(4)注册了蓝图对象以后,蓝图下面的视图方法和视图对应的路由关系也要进行注册!
所以, 在蓝图home下面的urls.py文件中,通过path方法把url地址和视图方法进行处理成字典,然后把字典作为成员添加到路由列表的固定变量urlpatterns列表中.
application/utils/blueprint.py,生成path函数,代码:
...
def path(rule, view_func,**kwargs):
"""绑定url地址和视图的映射关系"""
return {"rule":rule,"view_func":view_func,**kwargs}
# 加**kwargs的作用是因为后面可能加别的参数,例如:methods=["POST"]。
在application.__init__入口文件中,导入path路由映射函数,将来方便再其他地方调用该函数,application.__init__,文件添加代码如下:
from application.utils.blueprint import register_blueprint,path
(5)在home蓝图下的urls.py中,使用path函数绑定视图和路由的映射关系并添加urlpatterns中,home.urls.py,代码:
from application import path
from . import views
urlpatterns = [
path("/", views.index,methods=["POST"]),
path("/demo", views.demo),
]
为了方便测试,先到当前home蓝图下视图文件views.py中,添加2个测试的视图,代码:
def index():
return "ok!!!!"
def demo():
return "demo"
(6)把url和视图加载到蓝图对象中
完成上面步骤,将来即便有新的蓝图也可以参考上面的写法,把当前蓝图中所有的路由全部集中在urlpatterns中,接着下来,我们就可以在application.utils.blueprint.register_blueprint蓝图注册函数中, 生成蓝图对象以后添加自动加载并注册蓝图的路由和视图了。application/utils/blueprint.py,代码:
from flask import Blueprint
from importlib import import_module
def register_blueprint(app):
"""自动注册蓝图"""
# 从配置文件中读取需要注册到项目中的蓝图路径信息
blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
# 遍历蓝图路径列表,对每一个蓝图进行初始化
for blueprint_path in blueprint_path_list:
# 获取蓝图路径中最后一段的包名作为蓝图的名称
blueprint_name = blueprint_path.split(".")[-1]
# 创建蓝图对象
blueprint = Blueprint(blueprint_name,blueprint_path)
# 导入子路由关系,blueprint_url_path就是当前蓝图下的urls模块的导包路径
blueprint_url_path = blueprint_path + ".urls"
urls_module = import_module(blueprint_url_path)
# 获取urls模块下路由列表urlpatterns
urlpatterns = urls_module.urlpatterns
# 把urlpatterns的每一个路由信息添加注册到蓝图对象里面
for url in urlpatterns:
blueprint.add_url_rule(**url)
# 把蓝图对象注册到app实例对象
# todo url_prefix 是地址前缀,将来我们将来实现一个总路由来声明它
app.register_blueprint(blueprint,url_prefix="")
def path(rule, view_func,**kwargs):
"""绑定url地址和视图的映射关系"""
return {"rule":rule,"view_func":view_func,**kwargs}
此时,运行项目,就可以通过url地址访问蓝图下的视图方法了.
增加路由前缀
上面蓝图注册到app示例对象时, 没有设置url_prefix路由前缀, 接下来我们可以单独设置一个总路由application/urls.py,进行路由前缀的设置.
(1)在application.utils.blueprint.py下声明一个include函数,负责把路由前缀和蓝图进行绑定映射
def include(url_prefix, blueprint_url_subffix):
"""
绑定蓝图和路由前缀的关系
:param url_prefix: 路由前缀
:param blueprint_url_subffix: 蓝图名称,
格式:蓝图包名.路由模块名
例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
:return:
"""
return {"url_prefix": url_prefix, "blueprint_url_subffix": blueprint_url_subffix }
(2)创建总路由文件application/urls.py并注册蓝图和路由前缀的关系,
代码:
from application import include
urlpatterns = [
include("", "home.urls")
]
(3)在项目默认配置文件中,application/settings/__init__.py,新增总路由的配置项URL_PATH
"""总路由"""
URL_PATH = "application.urls"

浙公网安备 33010602011771号