模型视图控制器(MVC方程的最后一部分是控制器。我们已经在main.py
文件中看到了视图函数的基本用法。现在,将引入更复杂、更强大的版本,我们将把不同的视图功能转换为内聚的整体。我们还将讨论 Flask 如何处理 HTTP 请求的生存期以及定义 Flask 视图的高级方法。
在某些情况下,所有视图函数都需要一个特定于请求的变量,并且还需要从模板中访问该变量。为了实现这一点,我们可以使用 Flask 的 decorator 函数@app.before_request
和对象g
。每次发出新请求前都会执行功能@app.before_request
。Flask对象g
是一个线程安全存储库,用于存储每个特定请求所需的任何数据。在请求结束时,对象被销毁,并且在新请求开始时生成一个新对象。例如,以下代码检查 Flasksession
变量是否包含登录用户的条目;如果存在,则将User
对象添加到g
:
from flask import g, session, abort, render_template
@app.before_request
def before_request():
if ‘user_id’ in session:
g.user = User.query.get(session[‘user_id’])
@app.route(‘/restricted’)
def admin():
if g.user is None:
abort(403)
return render_template(‘admin.html’)
多个功能可以用@app.before_request
修饰,在执行请求的查看功能之前,都会执行功能。还有一个 decorator@app.teardown_request
,在每个请求结束后调用。请记住,这种处理用户登录的方法只是一个示例,并不安全。推荐方法见第 6 章、保护您的应用中所述。
向最终用户显示浏览器的默认错误页面是不和谐的,因为用户会丢失您应用的所有上下文,他们必须点击返回按钮才能返回您的站点。要在 Flaskabort()
函数返回错误时显示您自己的模板,请使用errorhandler
decorator 函数:
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
errorhandler
还可用于将内部服务器错误和 HTTP 500 代码转换为用户友好的错误页面。app.errorhandler()
函数可以使用一个或多个 HTTP 状态代码来定义它将操作的代码。返回元组而不仅仅是 HTML 字符串允许您定义Response
对象的 HTTP 状态代码。默认设置为200
。
在大多数Flask 应用中,视图由函数处理。然而,当许多视图共享公共功能或有代码片段可以分解为单独的函数时,将视图实现为类以利用继承是很有用的。
例如,如果我们有呈现模板的视图,我们可以创建一个通用视图类来保持代码干燥:
from flask.views import View
class GenericView(View):
def __init__(self, template):
self.template = template
super(GenericView, self).__init__()
def dispatch_request(self):
return render_template(self.template)
app.add_url_rule(
'/', view_func=GenericView.as_view(
'home', template='home.html'
)
)
关于这段代码,首先要注意的是我们视图类中的dispatch_request()
函数。这是我们视图中的函数,它充当普通视图函数并返回 HTML 字符串。app.add_url_rule()
函数模仿app.route()
函数,因为它将路由绑定到函数调用。第一个参数定义函数的路由,view_func
参数定义处理路由的函数。View.as_view()
方法被传递给view_func
参数,因为它将View
类转换为视图函数。第一个参数定义了 view 函数的名称,因此像url_for()
这样的函数可以路由到它。其余参数将传递给View
类的__init__
函数。
与普通视图函数一样,View
类必须明确允许使用GET
以外的 HTTP 方法。要允许使用其他方法,必须添加包含命名方法列表的类变量:
class GenericView(View):
methods = ['GET', 'POST']
…
def dispatch_request(self):
if request.method == ‘GET’:
return render_template(self.template)
elif request.method == ‘POST’:
…
通常,当函数处理多个 HTTP 方法时,由于if
语句中嵌套了大量代码,因此代码可能变得难以读取:
@app.route('/user', methods=['GET', 'POST', 'PUT', 'DELETE'])
def users():
if request.method == 'GET':
…
elif request.method == 'POST':
…
elif request.method == 'PUT':
…
elif request.method == 'DELETE':
…
这个可以通过MethodView
类来解决。MethodView
允许每个方法由不同的类方法处理,以分离关注点:
from flask.views import MethodView
class UserView(MethodView):
def get(self):
…
def post(self):
…
def put(self):
…
def delete(self):
…
app.add_url_rule(
'/user',
view_func=UserView.as_view('user')
)
在Flask中,蓝图是扩展现有Flask app 的方法。蓝图提供了一种将视图组与通用功能相结合的方式,允许开发人员将其应用分解为不同的组件。在我们的架构中,蓝图将充当我们的控制器。
将视图注册到蓝图中;可以为它定义一个单独的模板和静态文件夹,当它包含所有需要的内容时,可以在主 Flask 应用上注册它以添加 blueprint 内容。blueprint 的行为很像 Flask 应用对象,但实际上并不是一个自包含的应用。这就是 Flask 扩展提供视图函数的方式。为了了解什么是蓝图,这里有一个非常简单的例子:
from flask import Blueprint
example = Blueprint(
'example',
__name__,
template_folder='templates/example',
static_folder='static/example',
url_prefix="/example"
)
@example.route('/')
def home():
return render_template('home.html')
蓝图采用两个必需参数:蓝图名称和Flask内部使用的包装名称;将__name__
传递给它就足够了。
其他参数是可选的,用于定义蓝图将在何处查找文件。由于指定了templates_folder
,蓝图将不会在默认模板文件夹中查找,路由将呈现templates/example/home.html
而不是templates/home.html
。url_prefix
选项会自动将提供的 URI 添加到蓝图中每个路由的开始处。因此,主视图的 URL 实际上是/example/
。
url_for()
功能现在必须被告知请求的路线在哪个蓝图中:
{{ url_for('example.home') }}
此外,url_for()
功能现在必须被告知视图是否从同一蓝图中渲染:
{{ url_for('.home') }}
url_for()
功能还将在指定的静态文件夹中查找静态文件。
要将蓝图添加到我们的应用,请执行以下操作:
app.register_blueprint(example)
让我们将当前的应用转换为使用蓝图的应用。我们首先需要在所有路线之前确定蓝图:
blog_blueprint = Blueprint(
'blog',
__name__,
template_folder='templates/blog',
url_prefix="/blog"
)
现在,由于templates 文件夹已定义,我们需要将所有模板移动到 templates 文件夹的子文件夹 blog 中。接下来,我们所有的路线都需要将@app.route
更改为@blog_blueprint.route
,并且任何类视图分配现在都需要注册到blog_blueprint
。请记住,模板中的url_for()
函数调用也必须更改为在前面加上一个句点,以指示路由在同一蓝图中。
在文件末尾的if __name__ == '__main__':
语句前面添加以下内容:
app.register_blueprint(blog_blueprint)
现在,我们所有的内容都回到了应用上,该应用已在 blueprint 下注册。因为我们的基础应用不再有任何视图,所以让我们在基础 URL 上添加一个重定向:
@app.route('/')
def index():
return redirect(url_for('blog.home'))
为什么写博客而不是blog_blueprint
?因为 blog 是蓝图的名称,而 Flask 在内部用于路由的名称。blog_blueprint
是 Python 文件中变量的名称。
现在我们的应用在蓝图中运行,但这给了我们什么?比如说,我们想在我们的网站上增加一个照片共享功能;我们可以将所有视图功能组合到一个蓝图中,其中包含自己的模板、静态文件夹和 URL 前缀,而不必担心会破坏站点其余部分的功能。在下一章中,在升级我们的文件和代码结构后,通过将蓝图分成不同的文件,蓝图将变得更加强大。