使用蓝本模块化程序
实例化flask提供的blueprint类就创建一个蓝本实例。像程序实例一样,我们可以为蓝本实例注册路由、错误处理函数、上下文处理函数,请求处理函数,甚至是单独的静态文件文件夹和模板文件夹。在使用上,它和程序实例也很相似。比如,蓝本实例同样拥有一个route()装饰器,可以用来注册路由,但实际上蓝本对象和程序对象却有很大的不同。
实例化Blueprint类时,除了传入构造函数的第一个参数是蓝本名称之外,创建蓝本实例和使用flask对象创建程序实例的代码基本相同。例如,下面的代码创建了一个blog蓝本:
from flask import Blueprint blog = Blueprint('blog', __name__)
使用蓝本不仅仅是对视图函数分类,而是将程序某一部分的所有操作组织在一起。这个蓝本实例以及一系列注册在蓝本实例上的操作的集合被称为一个蓝本。你可以把蓝本想象成模子,它描述了程序某一部分的细节,定义了相应的路由、错误处理器、上下文处理器、请求处理器等一系列操作。但是它本身却不能发挥作用,因为它只是一个模子。只有当你把它注册到程序上时,它才会把物体相应的部分印刻出来----把蓝本中的操作附加到程序上。
使用蓝本可以将程序模块化(modular)。一个程序可以注册多个蓝本,我们可以把程序按照功能分离成不同的组件,然后使用蓝本来组织这些组件。蓝本不仅仅是在代码层面上的组织程序,还可以在程序层面上定义属性,具体的形式即为蓝本下的所有路由设置不同的URL前缀或子域名。
举一个常见的例子,为了让移动设备拥有更好的体验,我们为移动设备创建了单独的视图函数,这部分视图函数可以使用单独的mobile蓝本注册,然后为这个蓝本设置子域名m。用户访问m.example.com的请求会自动被该蓝本的视图函数处理。
1、创建蓝本
蓝本一般在子包中创建,比如创建一个blog子包,然后再构造文件中创建蓝本实例,使用包管理蓝本允许你设置蓝本独有的静态文件和模板,并在蓝本内对各类函数分模块存储。
在简单的程序中,我们也可以直接在模块中创建蓝本实例。根据程序的功能,我们分别创建了三个脚本:auth.py(用户认证)、blog.py(博客前台)、admin.py(博客后台),分别存储各自蓝本的代码。以auth.py为例,蓝本实例auth_bp在auth.py脚本顶端创建:
from flask import Blueprint auth_bp = Blueprint('auth', __name__)
在蓝本对象的名称后添加一个_bp后缀(即blueprint的简写)并不是必须的,这里是为了更容易区分蓝本对象,而且可以避免潜在的命名冲突。
在上面的代码中,我们从Flask导入Blueprint类,实例化这个类就获得了我们的蓝本对象。构造方法中的第一个参数是蓝本的名称;第二个参数是包或模块的名称,我们可以使用__name__变量。Blueprint类的构造函数还接收其他参数来定义蓝本,我们会在后面进行学习。
2、装配蓝本
蓝本实例时一个用于注册路由等操作的临时对象。这一节我们来学习在蓝本上可以注册哪些操作,以及其中的一些细节。
我们在下面介绍的方法和属性都是在表示蓝本的Blueprint类中定义的,因此可以通过我们的蓝本实例调用,在提及这些方法和属性时,我们会省略掉类名称,比如Blueprint.route()会写为route()。
1)视图函数
蓝本中的视图函数通过蓝本实例提供的route()装饰器注册,即auth_bp.route()。我们把和认证相关的视图函数移动到这个模块,然后注册到auth蓝本上,如下所示:
from flask import Blueprint auth_bp = Blueprint('auth', __name__) @auth_bp.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('blog.index')) form = LoginForm() if form.validate_on_submit(): username = form.username.data password = form.password.data remembet =form.remember.data admin = Admin.query.first() if admin: if username == admin.username and admin.validate_password(password): login_user(admin, remember) flash('Welcom back.', 'info') return redirect_back() flash('Invalid username or password.', 'warning') else: flash('No account.', 'Warning') return render_template('auth/login.html', form=form) @auth_bp.route('/logout') @login_required def logout(): logout_user() flash('Logout success.', 'info') return redirect_back()
现在的auth.py脚本就像一个完整的单脚本Flask程序,和之前练习的例子非常相似。
2)错误处理函数
使用蓝本实例的errorhandler()装饰器可以把错误处理器注册到蓝本上,这些错误处理器只会捕捉访问该蓝本中的路由发生的错误;使用蓝本实例的app_errorhandler()装饰器则可以注册一个全局的错误处理器。
404和405错误仅会被全局的错误处理函数捕捉,如果你想区分蓝本URL下的404和405错误,可以在全局定义的404错误处理函数中使用request.path.startswith('<蓝本的URL前缀>’)来判断请求的URL是否属于某个蓝本。下面我们会介绍如何为蓝本设置URL前缀。
3)请求处理函数
在蓝本中,使用before_request、after_request、teardown_request等装饰器注册的请求处理函数时蓝本独有的,也就是说,只有该蓝本中的视图函数对应的请求才会触发相应的请求处理函数。另外,在蓝本中也可以使用before_app_request、after_app_request、teardown_app_request、before_app_first_request方法,这些方法注册的请求处理函数是全局的。
4)模板山下文处理函数
和请求钩子类似,蓝本实例可以使用context_processor装饰器注册蓝本特有的模板上下文处理器;使用app_context_processor装饰器则会注册程序全局的模板上下文*处理器。
另外,蓝本对象也可以使用app_template_global()、app_template_filter()和app_template_test()装饰器,分别用来注册全局的模板全局函数、模板过滤器和模板测试器。
并不是所有程序实例提供的方法和属性都可以在蓝本对象上调用,蓝本对象只提供了少量用于注册处理函数的方法,大部分的属性和方法我们仍然需要通过程序实例获取,比如表示配置的config属性,或是注册自定义命令的cli.command()装饰器。
3、注册蓝本
我们在本章开始曾把蓝本比喻成模子,为了让这些模子发挥作用,我们需要把蓝本注册到程序实例上:
from personalBlog.blueprints.auth import auth_bp def register_blueprints(app): app.register_blueprint(auth_bp)
蓝本使用Flask.register_blueprint()方法注册,必须传入的参数是我们在上面创建的蓝本对象。其他的参数可以用来控制蓝本的行为。比如,我们使用url_prefix参数为auth蓝本下的所有视图URL附加一个URL前缀:
def register_blueprints(app): app.register_blueprint(auth_bp, url_prefix='/auth')
这时,auth蓝本下的视图的URL前都会添加一个/auth前缀,比如login视图的URL规则会变为/auth/login。使用subdomain参数可以为蓝本下的路由设置子域名。比如,下面蓝本中的所有视图会匹配来自auth子域的请求:
app.register_blueprint(auth_bp, subdomain='auth')
这时访问auth.example.com/login的URL才会触发auth蓝本中的login视图。
register_blueprint()方法接收的额外参数和Blueprint类的构造方法基本相同,在这里传入的参数会覆盖传入蓝本构造方法的参数。
4、蓝本的路由端点
端点作为URL规则和视图函数的中间媒介,是我们之前介绍url_for()函数时提及的概念。
(端点:用来标记一个视图函数以及对应的url规则,就是端点。端点的默认值是视图函数的名称)
下面先来深入了解一下端点,我们使用app.route()装饰器将视图函数注册为路由:
@app.route('/hello') def say_hello(): return "Hello!"
如果你没有接触过装饰器,可能会感到很神秘,其实它只是一层包装而已。如果不用app.route()装饰,使用app.add_url_rule()方法同样也可以注册路由:
def say_hello(): return 'Hello!' app.add_url_rule('/hello', 'say_hello', say_hello)
add_url_rule(rule, endpoint, view_func)的第二个参数即指定的端点(endpoint),第三个参数是视图函数对象。
在路由里,URL规则和视图函数并不是直接映射的,而是通过端点作为中间媒介。类似这样:
/hello (URL规则) -> say_hello(端点) -> say_hello(视图函数)
默认情况下,端点是视图函数的名称,在这里即say_hello。我们也可以显示地使用endpoint参数改变它:
@app.route('/hello', endpoint = 'give_greeting') def say_hello(): return 'Hello!'
这时端点变成了give_greeting,映射规则也相应改变:
/hello (URL规则) -> give_greeting (端点) -> say_hello (视图函数)
现在使用flask routes命令查看当前注册的所有路由:
(personalBlog-2ooe7Iqy) D:flaskpersonalBlog>flask routes
Endpoint Methods Rule
----------------------------- --------- --------------------------------------------
auth.login GET, POST /auth/login
auth.logout GET /auth/logout
blog.about GET /about
从上面的输出可以到,每个路由的URL规则(Rule)对应的端点(Endpoint)值不再仅仅是视图函数名,而是“蓝本命.视图函数名”的形式(这里的蓝本命即我们实例化Blueprint类时传入的第一个参数)。前面我们留下了一个疑问:为什么不直接映射URL规则到视图函数呢?现在是揭晓答案的时候了。答案就是---使用端点可以实现蓝本的视图函数命名空间。
当使用蓝本时,你可能会在不同的蓝本中创建同名的视图函数。比如,在两个蓝本中都有一个index视图,这时在模板中使用url_for()获取URL时,因为填入的端点参数值是视图函数的名称,就会产生冲突。flask在端点前添加蓝本的名称,扩展了端点的命名空间,解决了视图函数重名的问题。正因为这样,一旦使用蓝本,我们就要对程序中所有的url_for()函数中的端点值进行修改,添加蓝本命来明确端点的归属。比如,在生成auth蓝本下的login视图的URL时,需要使用下面的端点:
url_for(‘auth.login’)
端点也有一种简写的方式,在蓝本内部可以使用“.视图函数名”的形式来省略蓝本名称,比如“auth.login”可以简写为“.login”。但是在全局环境中,比如在多个蓝本中都要使用的基模板,或是在A蓝本中的脚本或渲染的模板中需要生成B蓝本的URL,这时的端点值则必须使用完整的名称。
使用蓝本本可以避免端点值的重复冲突,但是路由的URL规则还是会产生重复。比如,两个蓝本中的主页视图的URL规则都是“/home”,当在浏览器中访问这个地址时,请求只会分配到第一个注册的蓝本中的主页视图。为了避免这种情况,可以在注册蓝本时使用关键字参数url_prefix在蓝本的URL规则前添加一个URL前缀来解决。
一个蓝本可以注册多次,有时你需要让程序在不同的URL规则下都可以访问,这时就可以为同一个蓝本注册多次,每次设置对应的URL前缀或子域名。
5、蓝本资源
如果程序的不同蓝本的页面需要截然不同的样式,可以为蓝本定义独有的静态文件和模板。这时我们需要把蓝本升级为包,在构造文件中创建蓝本实例,并在蓝本包中创建静态文件夹static和模板文件夹templates。和程序实例一样,实例化时传入的__name__变量会被用来判断蓝本的根目录,并以此作为基础寻找模板文件夹和静态文件文件夹。
有时,你引入蓝本的唯一目的就是用来提供资源文件。比如,提供内置本地资源的扩展会使用刚注册蓝本的形式提供静态文件和模板。
要使用蓝本独有的静态文件,你需要在定义蓝本时使用static_folder关键字指定蓝本的静态文件文件夹的路径:
auth_bp = Blueprint('auth', __name__, static_folder = 'static', static_url_path='/auth/static')
这个参数的值可以是绝对路径或相对于蓝本所在文件夹的相对路径。另外,因为蓝本内置的static路由的URL规则和程序的static路由的URL规则相同,都是“/static”,为了避免冲突,我们使用个可选的static_url_path参数为蓝本下的static指定了新的 URL规则。
如果你在注册蓝本时为蓝本定义了URL前缀,即设置了url_prefix参数,那么最终的蓝本静态文件路径会自动设为“/蓝本前缀/static”,这时可以省略static_url_path的定义。
在生成用来获取蓝本静态文件的URL时需要写出包含蓝本名称的完整端点,即“蓝本命.static”,下面的调用会返回“admin/static/style.css”:
url_for('admin.static', filename = 'style.css')
当篮本包含独有的模板文件夹时,我们可以在实例化蓝本类时使用template_folder关键字指定模板文件夹的位置:
admin = Blueprint('admin', __name__, template_folder = 'templates')
当我们在蓝本中的视图函数渲染一个index.html模板时,Flask会优先从全局的模板文件夹中寻找,如果没有找到,再到 蓝本所在的模板文件夹查找。因此,为了避免蓝本的模板文件夹中 和全局模板文件夹中存在同名文件导致冲突,通常会在蓝本的模板文件夹中以篮板名称新建一个子文件夹存储模板。
如果蓝本之间的关联比较大,通用同一个基模板,更常见的方法是只在全局的模板文件夹中存储模板,在其中可以建立子文件夹来进行住址;静态文件的处理方式也一样。