作者 | ipython
写代码最关键的是要易于维护且结构清晰整洁。目前为止,你看到的例子都过于简单从而没有做这方面的要求。Flask视图函数希望将两个应该完全独立的任务一并处理,两个任务有两种代码,一并处理势必会引发问题。
明摆着的任务就是生成响应,就像你在第二章看到的示例那样。对于最简单的请求确实已经足够,但是一般而言一个请求会触发应用程序的状态发生改变,而视图函数就是产生这个改变的地方。
例如,用户在网站上注册一个新的账户。用户输入邮箱地址和密码到表单并点击提交按钮。一个包含数据的请求从用户到服务器,Flask将其派发给视图函数去处理这个注册请求。这个视图函数需要告诉数据库添加新的用户并生成一个响应发回给浏览器。这两类任务通常被称作业务逻辑和 显示逻辑。
混合业务逻辑和显示逻辑会导致代码非常的难以理解和维护。想象一下,将从数据库获取的数据和必要的HTML字符串文字进行连接,生成一堆的HTML代码是多么蛋疼的一件事情。而如果将显示逻辑移到模板中去,则势必极大提高应用程序的可维护性。
模板就是包含响应文本的文件,带有占位符变量的动态部分只在请求上下文中被知道。用真实的值替换变量并返回最终响应字符串,这个过程称为渲染。对于渲染模板任务,Flask使用强大的模板引擎Jinja2。
1、Jinja2模板引擎
Jinja2模板最简单的形式就是一个包含响应文本的文件。示例3-1展示了Jinja2模板匹配示例2-1中index()视图函数响应。
示例3-1. templates/index.html:Jinja2模板
<h1>Hello World!</h1>
示例2-2中视图函数user()返回的响应有一个动态部分,该部分由一个变量代表。示例3-2展示模板实现该响应。
示例3-2. templates/user.html:Jinja2模板
<h1>Hello, {{ name }}!</h1>
1.1、渲染模板
Flask默认会在应用程序的templates子目录中寻找模板。下个版本的hello.py,你需要将之前定义的index.html和user.html模板存放到新的templates文件夹下。
应用程序中的视图函数需要修改来渲染这些模板。示例3-3中会展示这些变化。
示例3-3. hello.py:渲染模板
from flask import Flask, render_template
# ...
@app.route('/index')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
render_template函数由集成Jinja2模板引擎的Flask提供。这个函数将模板的文件名作为它的第一个参数。任何附加的参数都是一个键/值对,分别对应被模板引用的变量和真实值。在这个示例中,第二个模板收到name变量。
类似于name=name的关键字参数在前面的示例中是非常常见的,但如果你不使用就很容易混淆且非常难理解。左边的“name”代表参数名称,一般在模板中用于占位符。右边的“name”在当前范围内是一个变量,为同名的参数提供真实的值。
建议:如果你有克隆在GitHub上的应用程序,你现在可以运行git checkout 3a来切换到这个版本的应用程序。
1.2、变量
示例3-2的模板中的{{ name }}结构引用一个变量,特殊的占位符告诉模板引擎,那个地方的值应该从模板被渲染时提供的数据中获取。
Jinja2识别任何类型的变量,包括复杂的列表、字典和对象。下面是一些模板中使用变量的示例:
<p>A value from a dictionary: {{ mydict['key'] }}.</p>
<p>A value from a list: {{ mylist[3] }}.</p>
<p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A value from an object's method: {{ myobj.somemethod() }}.</p>
变量可以通过filters来修改,以|为分隔符添加在变量名后。例如,下面的模板展示变量大写:
Hello, {{ name|capitalize }}
表格3-1列出一些常用的Jinja2过滤器。
表格3-1. Jinja2变量过滤器
safe是比较有趣的一个过滤器。出于安全考虑,默认情况下,Jinja2会转义所有变量。例如,如果设置变量的值为'<h1>Hello</h1>'
,Jinja2将渲染字符串为'<h1>Hello</h1>',这将使得h1元素显示出来且不能被浏览器解释。很多时候有必要显示存储在变量中的HTML代码,对于这些情况就可以使用safe过滤器。
警告:永远不要在不信任的值中使用safe过滤器,例如用户在web表单中输入的文本。
完整的过滤器列表可以从Jinja2官方文档中获取。
1.3、控制结构
Jinja2提供一些控制结构用于改变模板流。这一节用简单例子来介绍一些最有用的。
下面的示例展示条件语句是怎样在模板中使用的:
{ % if user %}
Hello, {{ user }}!
{ % else %}
Hello, Stranger!
{ % endif %}
渲染一列元素在模板中是经常用到的。这个示例展示如何使用for循环做到这些:
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
Jinja2同样支持宏,这和Python代码中的函数很像。例如:
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>
为了提高宏的复用性,可以将它们保存为单独的文件,然后在所有需要的地方import即可:
{% import 'macros.html' as macros %}
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>
需要在一些地方重复使用的模板代码可以保存为一个单独的文件,所有模板都可以include它来避免重复:
{% include 'common.html' %}
另一个强大的复用方式是通过模板继承,这类似于Python代码中类的继承。首先创建一个名为base.html的基础模板:
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
这里block标签定义的派生模板是可变的。在这个示例中,有head、title、body块;注意title包含在head中。下面的示例是一个基础模块的派生模块:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
extends指令定义这个模板由base.html派生。紧随这个指令之后的是三个在基础模板中定义的块,它们将在合适的位置插入。注意在基础模板中新定义的head块是不为空的,所以使用super() 保留原来的内容。
本节呈现的所有控制结构的实际运用将在后面展示,你会有机会看到它们是怎样工作的。