使用蓝本模块化程序

实例化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:\flask\personalBlog>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会优先从全局的模板文件夹中寻找,如果没有找到,再到 蓝本所在的模板文件夹查找。因此,为了避免蓝本的模板文件夹中 和全局模板文件夹中存在同名文件导致冲突,通常会在蓝本的模板文件夹中以篮板名称新建一个子文件夹存储模板。

如果蓝本之间的关联比较大,通用同一个基模板,更常见的方法是只在全局的模板文件夹中存储模板,在其中可以建立子文件夹来进行住址;静态文件的处理方式也一样。

flask实战-个人博客-使用蓝本模块化程序的更多相关文章

  1. flask实战-个人博客-虚拟环境、项目结构

    个人博客 博客是典型的CMS(Content Management system,内容管理系统),通常由两部分组成:一部分是博客前台,用来展示开放给所有用户的博客内容:另一部分是博客后台,这部分内容仅 ...

  2. flask实战-个人博客-编写博客前台

    编写博客前台 博客前台需要开放给所有用户,这里包括显示文章列表.博客信息.文章内容和评论等功能功能. 分页显示文章列表 为了在主页显示文章列表,我们要先在渲染主页模板的index视图的数据库中获取所有 ...

  3. flask实战-个人博客-电子邮件支持

    电子邮件支持 因为博客要支持评论,所以我们需要在文章有了新评论后发邮件通知管理员.而且,当管理员回复了读者的评论后,也需要发送邮件提醒读者. 为了方便读者使用示例程序,personalBlog中仍然使 ...

  4. flask实战-个人博客-模板 --

    模板 personalBlog采用典型的博客布局,左侧三分之二为主体,显示文章列表.正文:右侧三分之一为边栏,显示分为类列表.社交链接等.现在的工作是将HTML文件加工为模板,并创建对应的表单类,在模 ...

  5. flask实战-个人博客-数据库-生成虚拟数据 --

    3.生成虚拟数据 为了方便编写程序前台和后台功能,我们在创建数据库模型后就编写生成虚拟数据的函数. 1)管理员 用于生成虚拟管理员信息的fake_admin()函数如下所示: personalBlog ...

  6. flask实战-个人博客-表单

    表单 下面我们来编写所有表单类,personalBlog中主要包含下面这些表单: 登录表单: 文章表单: 评论表单: 博客设置表单: 这里仅介绍登录表单.文章表单.分类表单和评论表单,其他的表单在实现 ...

  7. flask实战-个人博客-程序骨架、创建数据库模型、临接列表关系 --

    编写程序骨架 personalBlog的功能主要分为三部分:博客前台.用户认证.博客后台,其中包含的功能点如下图所示: 数据库 personalBlog一共需要使用四张表,分别存储管理员(Admin) ...

  8. flask实战-个人博客-视图函数

    视图函数 在上面我们创建了所有必须的模型类.模板文件和表单类.经过程序规划和设计后,我们可以创建大部分视图函数.这些视图函数暂时没有实现具体功能,仅渲染对应的模板,或是重定向到其他视图.以blog蓝本 ...

  9. flask实战-个人博客-使用工厂函数创建程序实例 --

    使用工厂函数创建程序实例 使用蓝本还有一个重要的好处,那就是允许使用工厂函数来创建程序实例.在OOP(Object-Oriented Programming,面向对象编程)中,工厂(factory)是 ...

随机推荐

  1. 让对象支持with语句

    一.with语句的好处 with语句的好处在于,它可以自动帮我们释放上下文,就比如文件句柄的操作, 如果你不使用with语句操作,你要先open一个文件句柄,使用完毕后要close这个文件句柄, 而使 ...

  2. 深入理解Java虚拟机笔记

    1. Java虚拟机所管理的内存 2. 对象创建过程 3. GC收集 4. HotSpot算法的实现 5. 垃圾收集器 6. 对象分配内存与回收细节 7. 类文件结构 8. 虚拟机类加载机制 9.类加 ...

  3. Sonatype Nexus Repository Manager修改密码不成功

    nexus修改用户密码时出现Invalid authentication ticket 搜索一下,说会修改密码操作要在15秒内完成 ,于是快速操作,没想到真成功了

  4. what's the 套期保值

    出自 MBA智库百科(https://wiki.mbalib.com/) 什么是套期保值 套期保值是指把期货市场当作转移价格风险的场所,利用期货合约作为将来在现货市场上买卖商品的临时替代物,对其现在买 ...

  5. what's the 二叉树

    what's the 树 在了解二叉树之前,首先我们得有树的概念. 树是一种数据结构又可称为树状图,如文档的目录.HTML的文档树都是树结构,它是由n(n>=1)个有限节点组成一个具有层次关系的 ...

  6. 【leetcode】部分思路整理

    题目: 求一个树的最小深度. 思路: 思路一:递归     若为空树返回0:     若左子树为空,则返回右子树的最小深度+1:(加1是因为要加上根这一层,下同)     若右子树为空,则返回左子树的 ...

  7. docker machine 使用教程

    之前,Docker的安装流程非常复杂,用户需要登录到相应的主机上,根据官方的安装和配置指南来安装Docker,并且不同的操作系统的安装步骤也是不一样的.而有了Machine后,不管是在笔记本.虚拟机还 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第四周作业

    2018-2019-1 20189221<Linux内核原理与分析>第四周作业 教材学习:<庖丁解牛Linux内核分析> 第 3 章 MenuOS的构造 计算机三大法宝:存储程 ...

  9. 15-Python3 编程第一步

    2018-11-20 11:42:06 ''' 肥婆纳妾数列 斐波那契数列,又称黄金分割数列:这个数列从第3项开始,每一项都等于前两项之和, 随着数列项数的增加,前一项与后一项之比越来越逼近黄金分割的 ...

  10. 线程中使用SaveFileDialog不能弹出窗体

    在子线程中使用 SaveFileDialog 无法弹出窗体,主要是我们需要用主线程去处理SaveFileDialog , 我们可以将子线程进行如下设置: public partial class Fo ...