6、模板
在Web开发中,我们经常会用到模板引擎,什么是模板?就是一个包含响应文本的文件, 用占位符(变量)标识动态部分,告诉模板引擎,其具体值需要从使用的数据中获取。
0x1 视图与模板的关系
在前面的例子中,视图函数的主要作用是生成请求的响应,而实际开发中,视图函数有 两个作用:「处理业务逻辑」和「返回响应内容」。在大型项目中,如果把业务逻辑 和表现内容放在一起的话,会增加代码的复杂度和维护成本。而模板的作用就是: 「承担视图函数返回响应内容这一部分的职责,从而使得代码结构清晰,耦合度低。」 使用真实值替换变量,再(控制)返回最终得到的字符串,这个过程称作「渲染」。 Flask中默认使用「Jinja2」这个模板引擎来渲染模板。
0x2 Jinja2语法
通过一个简单的例子,引入模板,下面是一个没有使用模板的简单程序:
# coding=utf-8 from flask import Flask app = Flask(__name__) @app.route("/<user>") def hello(user): if user == 'admin': return "<h1>管理员,您好!<h1>" else: return "<h1>%s, 您好!</h1>" % user if __name__ == "__main__": app.run()
接着我们使用Jinja2模板进行改写,默认情况下,Flask会在「项目的templates子文件夹」 中寻找模板,我们新建一个templates文件夹,然后新建一个index.html,内容如下:
"<h1>{{name}},您好!<h1>"
接着修改下hello.py文件,修改后的内容如下:
from flask import Flask, render_template app = Flask(__name__) @app.route("/<user>") def hello(user): if user == 'admin': return render_template("index.html", user="管理员") else: return render_template("index.html", user=user) if __name__ == "__main__": app.run()
接着运行hello.py,浏览器键入以下地址,对应输出结果如下:
http://127.0.0.1:5000/admin # 输出结果:管理员,您好! http://127.0.0.1:5000/jay # 输出结果:jay,您好!
以上就是一个简单的模板使用示例,通过调用Flask提供的render_template函数, 生成了一个模板,第一个参数是模板的名称,随后的参数是键值对,表示模板中 变量对应的真实值。接着详细讲解一波Jinja2的语法:
1.变量
Jinja2使用**{{变量名}}
**来表示一个变量,除了基本数据类型外还可以使用列表,字段 或对象等复杂类型,示例如下:
<h1> 账号:{{ user['name'] }},密码:{{ user['passwd'] }}
2.控制结构
Jinja2提供了多种控制结构,比如常见的判断和循环结构,用于修改模板的渲染流程。 我们把上面判断的逻辑也放到模板里,示例如下:
{# 注释,不会输出到浏览器中 #} {% if user == 'admin' or user == '管理员' %} <h1> 管理员,您好! </h1> {% else %} <h1> {{ user }},您好!</h1> {% endif %} {# 循环打印 #} {% for num in num_list %} <h2>{{ num }}</h2> {% endfor %}
接着修改下hello.py文件:
@app.route("/<user>") def hello(user): return render_template("index.html", user=user, num_list=[1, 2, 3])
键入不同的URL,对应浏览器的输出结果如下:
http://127.0.0.1:5000/admin 管理员,您好! 1 2 3 http://127.0.0.1:5000/jay jay,您好! 1 2 3
3.宏
在Python中如果有一些很常用的代码,我们会习惯性地抽取成一个函数,在Jinji2中, 可以使用宏来实现。语法如下:
# 创建宏 {% macro 标签名(key=value) %} 常用代码 {% end macro %} # 调用宏 {{ 标签名(key=value) }}
如果宏比较多,可以抽到单独的HTML中,再import进来。
{% import 'xxx.html' as 别名 %} {{ 别名.标签名(key=value) }}
4.模板继承
另一种代码复用的方式:模板继承,和Python中类继承一样,需要一个基模板,用{% block XXX %}{% endblock %}
标识一个代码块,可以在子模块中重载。 代码示例如下:
# 基模板:base.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> {% block head %} <title>{% block title %} Title {% endblock %} </title> {% endblock %} </head> <body> {% block body %} {% endblock %} </body> </html> # 子模板:son.html {% extends "base.html" %} {% block title %}Son{% endblock %} {% block head %} {{ super() }} {% endblock %} {% block body %} <h1>Hello Flask!</h1> {% endblock %}
代码简述:
第一行代码使用extends命令,表明该模板继承自base.html,接着重写了title,head和 body代码块,上面的super()函数用于获取基模板原先的内容。
5.inclue
可以用include语句包含一个模板,在渲染时会把include语句对应位置添加被包含的模板, 使用示例如下:
{% include 'header.html' %}
另外还可以使用「ignore missing」标记,如果模板不存在,Jinja2会忽略此语句,示例如下:
{% include 'header.html' ignore missing %}
6.赋值
可以使用set标签来进行赋值,示例如下:
{% set a,b = 1,2} <h1>{{ a }}{{ b }}</h1>
7.内建过滤器
过滤器的本质就是函数,有时需要修改,格式化或者执行变量间的计算,而在模板中是不能 直接调用Python中的某些方法的,可以用到过滤器。模板的过滤器支持链式调用:
{{ "hello flask" | reverse | upper}},
这串代码就是反转+转换成大写,常见的内建过滤器有字符串和列表两种。字符串操作过滤器如下表所示:
过滤器 | 描述 |
safe | 禁用转义 |
capitalize | 把变量值的首字母转成大写,其余字母转小写 |
lower | 把值转成小写 |
upper | 把值转成大写 |
title | 把值中的每个单词的首字母都转成大写 |
reverse | 字符串反转 |
format | 格式化输出 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
truncate | 字符串截断 |
列表操作过滤器如下表所示:
过滤器 | 描述 |
first | 取第一个元素 |
last | 取最后一个元素 |
length | 获取列表长度 |
sum | 列表求和 |
sort | 列表排序 |
8.自定义过滤器
直接在py文件中编写,代码示例如下:
# 1.定义过滤器 def do_listreverse(li): temp_li = list(li) temp_li.reverse() return temp_li # 2.添加自定义过滤器 app.add_template_filter(do_listreverse, 'listreverse')
总结:
宏,继承和包含都用实现代码复用,宏功能类似于函数,可以传参,需要定义调用; 继承本质是代码替换,一般用来实现多个页面中重复不变的区域;而包含是直接将 目标模板文件整个渲染出来。
7、请求与相应
在Flask中,HTTP请求被封装成了Request对象,而HTTP响应则被封装成了Response对象。 因此Flask应用开发的逻辑处理,都是基于这两个对象。
0x1 Request请求
Flask会将WSGI服务器转发的http请求数据封装为一个Request对象,这个对象中包含了请求的 相关信息,可以通过下表中的属性来获取对应的信息。
属性 | 描述 | 数据类型 |
form | 记录请求中的表单数据。 | MultiDict |
args | 记录请求中的查询参数。 | MultiDict |
cookies | 记录请求中的cookie。 | Dict |
headers | 记录请求中的报文头。 | EnvironHeaders |
method | 记录请求使用的HTTP方法。 | string |
environ | 记录WSGI服务器转发的环境变量。 | Dict |
url | 记录请求的URL地址。 | string |
1.读取request的查询参数
浏览器以GET请求的方式提交的表单数据,Flask会将其存储在request实例的args,也可以用values属性 来查询,读取代码示例如下:
# coding=utf-8 from flask import Flask, request, json app = Flask(__name__) @app.route("/index") def index(): return ''' <form method="GET" action="/login"> <input type="text" placeholder="账号" name="user"> <br /> <input type="text" placeholder="密码" name="pawd"> <br /> <input type="submit" value="登录"> </form> ''' @app.route("/login") def login(): msg = "" if 'user' in request.args: msg += request.args['user'] + ':' msg += request.values.get('pawd','') return msg if __name__ == "__main__": app.run(debug=True)
代码运行后,打开:http://127.0.0.1:5000/index,浏览器:
输入账号密码后,跳转到:http://127.0.0.1:5000/login?user=zpj&pawd=123, 浏览器:
2.读取request的表单数据
浏览器以GET请求的方式提交的表单数据,Flask会将其存储在request实例的form中,可以使用[]操作符 读取指定键值。读取代码示例如下:
# coding=utf-8 from flask import Flask, request, json app = Flask(__name__) @app.route("/index") def index(): return ''' <form method="POST" action="/login"> <input type="text" placeholder="账号" name="user"> <br /> <input type="text" placeholder="密码" name="pawd"> <br /> <input type="submit" value="登录"> </form> ''' @app.route("/login", methods=['POST']) def login(): msg = "" msg += request.form['user'] + ':' msg += request.form['pawd'] return msg if __name__ == "__main__": app.run(debug=True)
代码运行后,打开:http://127.0.0.1:5000/index,浏览器:
输入账号密码后,跳转到:http://127.0.0.1:5000/login,浏览器:
0x2 Response响应
和Request对应,Response用于给浏览器发送响应信息,根据视图函数的返回结果。这个视图函数 就是我们路由下面的函数,视图函数的返回值会自动转换成一个响应对象,转换逻辑如下:
- 1、返回值是合法的响应对象,从视图直接返回;
- 2、返回值是字符串,会用字符串和默认参数创建以字符串为主体,返回码为200,MIME类型为 text/html的werkzeug.wrappers.Response响应对象。
- 3、返回值是元组,其中的元素可以提供额外信息,但格式必须是(response,status,headers) 的形式,至少包含一个元素,status值会覆盖状态码,headers可以是字典或列表,作为额外的消息头。
- 4、如果都不是,Flask会假设返回值是合法的WSGI程序,通过Response.force(rv.request.environ) 转换为一个请求对象。除了通过return方式返回,还可以显式地调用make_response函数返回,好处是可以 设置一些额外的信息,示例如下:
def hello(): resp = make_response("Hello Flask!", 250) return resp
另外,现在的API接口都是返回JSON格式的,可以用jsonify包装下,修改后的示例代码如下:
# coding=utf-8 from flask import Flask, make_response, jsonify from werkzeug.wrappers import Response app = Flask(__name__) @app.route("/", methods=['GET', 'POST']) def hello(): resp = make_response({'code': '200', 'msg': '请求成功', 'data': [{'data_1': ['数据', '数据'], 'data_2': ['数据', '数据']}]}) return resp class JsonResponse(Response): @classmethod def force_type(cls, response, environ=None): if isinstance(response, dict): response = jsonify(response) return super(JsonResponse, cls).force_type(response, environ) app.response_class = JsonResponse if __name__ == "__main__": app.run(debug=True)
请求接口输出如下:
{ "code": "200", "data": [ { "data_1": [ "数据", "数据" ], "data_2": [ "数据", "数据" ] } ], "msg": "请求成功" }
也可以用Flask的json模块的dumps()函数将数组或字典对象转换为JSON字符串,代码示例如下:
data = {'code': '200', 'msg': '请求成功', 'data': [{'data_1': ['数据', '数据'], 'data_2': ['数据', '数据']}]} return json.dumps(data),200,[('Content-Type','application/json;charset=utf-8')]
- 3.设置CookieResponse类中提供了set_cookie()函数用于设置客户端的cookie,如果要设置cookie,需要我们自己构建Response实例 (通过make_response),可选参数如下:
set_cookie( key, //键 value='', //值 max_age=None, //秒为单位的cookie寿命,None表示http-only expires=None, //失效时间,datetime对象或unix时间戳 path='/', //cookie的有效路径 domain=None, //cookie的有效域 secure=None, httponly=False)