蓝图 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文件了。

image

解决办法:

在实例化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

结果如下:
image

这样就自动把自定义命令注册进去了。


自动注册蓝图

有了蓝图以后,接下来我们就可以把视图代码,模型代码,路由代码等存储到蓝图目录下了,但是我们需要把蓝图注册到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"
posted @ 2021-06-14 00:59  芜湖〃  阅读(222)  评论(0)    收藏  举报
∧