第三章 Jinja2模板
在前面的实例中,视图函数的主要作用是生成请求的响应,这是最简单请求.实际上,视图函数有两个作用:
- 处理业务逻辑
- 返回响应内容
在大型应用中,把业务逻辑和表现内容放在一起,会增加代码的复杂度和维护成本.
- 模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体的值需要从使用的数据中获取
- 使用真实值替换变量,再返回最终得到的字符串,这个过程称为’渲染’
- Flask是使用Jinja2这个模板引擎来渲染模板
使用模板的好处
- 视图函数只负责业务逻辑和数据处理(业务逻辑方面)
- 而模板则取到视图函数的数据结果进行展示(试图展示方面)
- 代码结构清晰,耦合度低
1、Jinja2模板介绍
Jinja2:是Python的Web项目中被广泛应用的模板引擎,是由Python实现的模板语言,Jinja2 的作者也是 Flask 的作者。他的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
jinja2之所以被广泛使用是因为它具有以下优点:
- 1、相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等。
- 2、相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑。
- 3、相对于Django模板,jinja2性能更好。
- 4、Jinja2模板的可读性很棒。
要渲染一个模板,通过 render_template 方法即可。
模板传参
1、在使用 render_template 渲染模版的时候,可以传递关键字参数(命名参数)。以后直接在模版中使用就可以了。
2、 如果你的参数项过多,那么可以将所有的参数放到一个字典中,或者列表中都可以。一般如果想将字典打散成关键字参数可以在参数的前面加 **
from flask import Flask, render_template, redirect, request
app = Flask(__name__)
STUDENT = {'name': 'Old', 'age': 38, 'gender': '中'}
STUDENT_LIST = [
{'name': 'Old', 'age': 38, 'gender': '中'},
{'name': 'Boy', 'age': 73, 'gender': '男'},
{'name': 'EDU', 'age': 84, 'gender': '女'}
]
STUDENT_DICT = {
'a': {'name': 'Old', 'age': 38, 'gender': '中'},
'b': {'name': 'Boy', 'age': 73, 'gender': '男'},
'c': {'name': 'EDU', 'age': 84, 'gender': '女'},
}
@app.route("/student")
def detail():
#print(url_for("detail"))
return render_template("student.html", **STUDENT)
@app.route("/detail_list", )
def detail_list():
return render_template("student_list.html", stu_list=STUDENT_LIST)
@app.route("/detail_dict")
def detail_dict():
return render_template("student_dict.html", stu_dict=STUDENT_DICT)
语法
在jinja2中,存在三种语法:
- 控制结构 (逻辑代码){% %}
- 变量取值 {{ }}
- 注释 {# #}
2、表达式
-
最常用的是变量,由Flask渲染模板时传过来,比如name
-
也可以是任意一种Python基础类型,比如字符串{{stu_list}};或者数值,列表,元祖,字典,布尔值。
-
运算。包括算数运算,如{{ 2 + 3 }};比较运算,如{{ 2 > 1 }};逻辑运算,如{{ False and True }}
-
过滤器|和测试器is
-
函数调用,如{{ current_time() }};
-
数组下标操作,如{{ arr[1] }}
-
in操作符,如{{ 1 in [1,2,3] }}
-
字符串连接符~,作用同Python中的 “+” 一样,如{{ "Hello " ~ name ~ “!” }}
-
None值处理{{name or “”}
3、控制语句
Jinja2的控制语句主要就是条件控制语句if,和循环控制语句for,语法类似于Python if-else:
条件判断语句:
{% if name and name == 'admin' %}
<h1>This is admin console</h1>
{% elif name %}
<h1>Welcome {{ name }}!</h1>
{% else %}
<h1>Please login</h1>
{% endif %}
for循环语句:
{% for stu in stu_list%}
{{ stu }}
{% endfor %}
###
比如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ stu_list }}
<table border="1px">
<tr>
<td>name</td>
<td>age</td>
<td>gender</td>
</tr>
{% for stu in stu_list %}
{% if stu.name != "Old" %}
{% if stu.age != 73 %}
<tr>
<td>{{ stu.name }}</td>
<td>{{ stu.get("age") }}</td>
<td>{{ stu["gender"] }}</td>
</tr>
{% endif %}
{% endif %}
{% endfor %}
</table>
</body>
</html>
或者:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ stu_dict }}
<table border="1px">
<tr>
<td>id</td>
<td>name</td>
<td>age</td>
<td>gender</td>
</tr>
{% for stu_key,stu_value in stu_dict.items() %}
<tr>
<td>{{ stu_key }}</td>
<td>{{ stu_value.get("name") }}</td>
<td>{{ stu_value.age }}</td>
<td>{{ stu_value.gender }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
4、过滤器
什么是过滤器? 实质上就是一个转换函数。变量可以通过“过滤器”进行修改,过滤器可以理解为是jinja2里面的内置
函数和字符串处理函数。
常用的过滤器有:
过滤器名称 | 说明 |
---|---|
safe | 渲染时值不转义 |
capitialize | 把值的首字母转换成大写,其他子母转换为小写 |
lower | 把值转换成小写形式 |
upper | 把值转换成大写形式 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格去掉 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
join | 拼接多个值为字符串 |
replace | 替换字符串的值 |
round | 默认对数字进行四舍五入,也可以用参数进行控制 |
int | 把值转换成整型 |
1、字符串的过滤器
<body>
{# 当变量未定义时,显示默认字符串,可以缩写为d #}
<p>{{ name | default('No name') }}</p>
{# 单词首字母大写 #}
<p>{{ 'hello world' | capitalize }}</p>
{# 单词全小写 #}
<p>{{ 'XML' | lower }}</p>
{# 去除字符串前后的空白字符 #}
<p>{{ ' hello ' | trim }}</p>
{# 字符串反转,返回"olleh" #}
<p>{{ 'hello' | reverse }}</p>
{# 格式化输出,返回"Number is 99" #}
<p>{{ '%s is %d' | format("Number", 99) }}</p>
{# 关闭HTML自动转义 #}
<p>{{ '<em>name</em>' | safe }}</p>
{% autoescape false %}
{# HTML转义,即使autoescape关了也转义,可以缩写为e #}
<p>{{ '<em>name</em>' | escape }}</p>
{% endautoescape %}
2、数值的过滤器
</body>
{# 四舍五入取整,返回13.0 #}
<p>{{ 12.98 | round }}</p>
{# 向下截取到小数点后2位,返回12.88 #}
<p>{{ 12.8888 | round(2, 'floor') }}</p>
{# 绝对值,返回12 #}
<p>{{ -12 | abs }}</p>
3、列表相关的过滤器
{# 取第一个元素 #}
<p>{{ [1,2,3] | first }}</p>
{# 取最后一个元素 #}
<p>{{ [1,2,3] | last }}</p>
{# 返回列表长度,可以写为count #}
<p>{{ [1,2,3,4,5] | length }}</p>
{# 列表求和 #}
<p>{{ [1,2,3,4,5] | sum }}</p>
{# 列表排序,默认为升序 #}
<p>{{ [3,2,1,5,4] | sort }}</p>
{# 合并为字符串,返回"1 | 2 | 3 | 4 | 5" #}
<p>{{ [1,2,3,4,5] | join(' | ') }}</p>
{# 列表中所有元素都全大写。这里可以用upper,lower,但capitalize无效 #}
<p>{{ ['alex','bob','ada'] | upper }}</p>
4**、字典相关的过滤器**
{% set users=[{'name':'Tom','gender':'M','age':20},
{'name':'John','gender':'M','age':18},
{'name':'Mary','gender':'F','age':24},
{'name':'Bob','gender':'M','age':31},
{'name':'Lisa','gender':'F','age':19}]
%}
{# 按指定字段排序,这里设reverse为true使其按降序排 #}
<ul>
{% for user in users | sort(attribute='age', reverse=true) %}
<li>{{ user.name }}, {{ user.age }}</li>
{% endfor %}
</ul>
{# 列表分组,每组是一个子列表,组名就是分组项的值 #}
<ul>
{% for group in users|groupby('gender') %}
<li>{{ group.grouper }}<ul>
{% for user in group.list %}
<li>{{ user.name }}</li>
{% endfor %}</ul></li>
{% endfor %}
</ul>
{# 取字典中的某一项组成列表,再将其连接起来 #}
<p>{{ users | map(attribute='name') | join(', ') }}</p>
5、自定义过滤器
# 第一种方式
def get_even_list(l):
return l[::2]
# 函数的第一个参数是过滤器函数,第二个参数是过滤器名称
app.jinja_env.filters['even_filter'] =get_even_list
# 第二种方式
@app.template_filter() # 过滤器函数
def is_even(num):
if num % 2 == 0:
return "even number"
else:
return "odd number"
模板中:
<p>{{ [1,2,3,4,5] | even_filter }}</p>
<p>{{ 2 | is_even }}</p>
5、测试器
测试器总是返回一个布尔值,它可以用来测试一个变量或者表达式,使用”is”关键字来进行测试。
{% set name='ab' %}
{% if name is lower %}
<h2>"{{ name }}" are all lower case.</h2>
{% endif %}
测试器本质上也是一个函数,它的第一个参数就是待测试的变量,在模板中使用时可以省略去。如果它有第二个参数,模板中就必须传进去。测试器函数返回的必须是一个布尔值,这样才可以用来给if语句作判断。
1、Jinja2中内置的测试器
官网:https://jinja.palletsprojects.com/en/master/templates/#builtin-tests
{# 检查变量是否被定义,也可以用undefined检查是否未被定义 #}
{% if name is defined %}
<p>Name is: {{ name }}</p>
{% endif %}
{# 检查是否所有字符都是大写 #}
{% if name is upper %}
<h2>"{{ name }}" are all upper case.</h2>
{% endif %}
{# 检查变量是否为空 #}
{% if name is none %}
<h2>Variable is none.</h2>
{% endif %}
{# 检查变量是否为字符串,也可以用number检查是否为数值 #}
{% if name is string %}
<h2>{{ name }} is a string.</h2>
{% endif %}
{# 检查数值是否是偶数,也可以用odd检查是否为奇数 #}
{% if 2 is even %}
<h2>Variable is an even number.</h2>
{% endif %}
{# 检查变量是否可被迭代循环,也可以用sequence检查是否是序列 #}
{% if [1,2,3] is iterable %}
<h2>Variable is iterable.</h2>
{% endif %}
{# 检查变量是否是字典 #}
{% if {'name':'test'} is mapping %}
<h2>Variable is dict.</h2>
{% endif %}
2、自定义测试器
# 自定义测试器
# 第一种方式
import re
def test_tel(tel_num):
tel_re = r'\d{11}'
return re.match(tel_re,tel_num)
app.jinja_env.tests['is_tel'] = test_tel
# 第二种方式
@app.template_test('start_with')
def start_with(str, suffix):
return str.lower().startswith(suffix.lower())
模板中:
{% set tel = '18910171111' %}
{% if tel is is_tel %}
<h2>{{ tel }} is mobile phone</h2>
{% endif %}
{% set name = 'Hello world' %}
{% if name is start_with 'hello' %}
<h2>"{{ name }}" start_with "hello"</h2>
{% endif %}
6、块和继承
一般我们的网站虽然页面多,但是很多部分是重用的,比如页首,页脚,导航栏之类的。对于每个页面,都要写这些代码,很麻烦。
Flask的Jinja2模板支持模板继承功能,省去了这些重复代码。
父模板:
<body>
你好,template1
{% block template1 %}
{% endblock %}
你好,template2
{% block template2 %}
{% endblock %}
你好,template
{% block template %}
{% endblock %}
</body>
子模版:
{% extends "he.html" %}
{% block template %}
{{ super() }}
<h1>yuan</h1>
{% endblock %}
{% block template1 %}
{{ super() }}
<h1>alex</h1>
{% endblock %}
include标签
这个标签相当于是直接将指定的模版中的代码复制粘贴到当前位置。 include
标签,如果想要使用父模版中的变量,直接用就可以了。 include
的路径,也是跟 import
一样,直接从 templates
根目录下去找,不要以相对路径去找。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SXT</title>
</head>
<body>
<!--通过include 引入头部log信息-->
{% include "common/head.html" %}
<div>
这是首页内容
{{ major }}
</div>
<hr>
<!--通过include 引入底部版权信息-->
{% include "common/footer.html" %}
</body>
</html>
7、模板中使用url_for
模版中的 url_for
跟我们后台视图函数中的 url_for
使用起来基本是一模一样的。也是传递视图函数的名字,也可以传递参数。使用的时候,需要在 url_for
左右两边加上一个 {{ url_for('func') }}
1、动态超链接
html页面使用如:
<a href="{{ url_for('login',p1='abc',p2='ddd',name='momo') }}">登录</a>
本质上就是动态路由
点击变为:
http://127.0.0.1:5000/accounts/login/momo/?p1=abc&p2=ddd
对应的视图函数:
@app.route('/accounts/login/<name>/')
def login(name):
print(name)
return render_template('login.html')
或者也可以不用动态路由:
html页面使用如:
<a href="{{ url_for('login',p1='abc',p2='ddd') }}">登录3</a>
点击变为:
http://127.0.0.1:5000/accounts/login/?p1=abc&p2=ddd
2、加载静态文件
静态文件:css文件 js文件 图片文件等文件
加载静态文件使用的是 url_for
函数。然后第一个参数需要为 static
,第二个参数需要为一个关键字参数
filename='路径'
。
语法:
{{ url_for(“static”,fifilename=‘xxx’) }}
<link href="{{ url_for('static',filename='css/main.css') }}" rel="stylesheet">
<script src="{{ url_for('static',filename='js/main.js') }}"></script>
<img src="{{ url_for('static',filename='img/main.jpg') }}"/>