Flask 极致细节:2. 模板(参数传递,表格展示,模板继承,宏)
提示:此博客包含如下概念的介绍:模板的介绍,参数传递,列表与表格的制作,(自定义)过滤器,模板继承,引入样式表,引入图片,宏(macro)的介绍与使用。
此章节可能会比较枯燥,之后我们会跟新真实的案例,但打好基础总是必须的。另外,我们会一如既往地分享所有代码,并附有非常详细的注解。
喜欢的朋友点个赞哦:)
代码链接:https://pan.baidu.com/s/1sjshcJPgsYvP_Pb_zw8sIA
密码:yu34
文章目录
0. 准备工作
当你下载并打开代码后,里面的结构应该是这样的:
--项目名
|---static (静态)
|---templates (模板)
|---app.py (运行/启动)
|---venv1 (虚拟环境)
|---requirements.txt (所有安装包以及版本)
|---config.py (参数配置文件)
|---readme.md (说明文档)
我们可以创建虚拟环境:virtualenv [venv]
,或直接使用已经存在的虚拟环境venv1
。接下来进入虚拟环境:.\[venv]\Scripts\activate
。如是自己创建的新虚拟环境,还需要安装依赖:pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
。相关的配置以及解释请参见前一篇博文:Flask 极致细节:0. VS Code 手动新建Flask项目。
1. 参数的传递
1.1 案例
第一个问题是,我们在app.py
中的一些参数,如何传递/显示到html网页中。
这里,我们首先考虑最基础的参数传递,包含以下几种:string,int,list中的元素,dictionary中的元素,以及创建的对象。
app.py
中的代码:
class Attendance:
def __init__(self, name, address) -> None:
self.name = name
self.address = address
self.gender = 'Male'
def __str__(self) -> str:
return self.name
@app.route('/show')
def show():
name = 'Alex' # 将string传入html
age = 52 # 将int传入html
fruits = ['apple','banana','pineapple','orange'] # 将list中的元素传入html
dict1 = {'Marry':59,'Beny':63,'Eric':75,'Peter':100} # 将dictionary中的元素传入html
attendance1 = Attendance(name='Peter', address='Berlin') # 将类的实例传入html
return render_template('app_templateEx.html', names=name, \
ages=age, fruits=fruits, dict=dict1, attends=attendance1)
app_template.html
中的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Template例子</title>
</head>
<body>
<h1>这是关于模板的例子</h1>
<p>段落1</p>
<div>用户信息展示</div>
<p>
<!-- 将string,int传入html -->
用户名是:Name {{ names }}, with age {{ ages }}.
<br>
<!-- 将list中的元素传入html -->
选择的水果是:{{ fruits.1 }}
<br>
<!-- 将dictionary中的元素传入html -->
Marry获得的分数:{{ dict.get('Marry') }};Eric获得的分数:{{ dict.Eric }}
<br>
<!-- 将对象中的元素传入html,这里attends会直接引用__str__里面的内容,所以attends返回的就是attends.name的值 -->
参会者:{{ attends }};性别:{{ attends.gender }};地址:{{ attends.address }}。
</p>
</body>
</html>
1.2 小节
在模板中获得view中传递的变量的格式:{{ 变量名key }}
。
在app.py
中,我们通过render_template('模板名称', key=value, key=value)
来实现参数的传递。如果我们点进去render_template
这个函数,我们就可以明确地看到一个参数context
,它的注释是:the variables that should be available in the context of the template
。
render_template
的源代码:
def render_template(
template_name_or_list: t.Union[str, t.List[str]], **context: t.Any
) -> str:
"""Renders a template from the template folder with the given
context.
:param template_name_or_list: the name of the template to be
rendered, or an iterable with template names
the first one existing will be rendered
:param context: the variables that should be available in the
context of the template.
"""
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
return _render(
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
context,
ctx.app,
)
在模板中的取值方式:
- string a -> {{ a }}
- int/float a -> {{ a }}
- list a=[aa,ab,ac,ad] -> 取第一个元素:{{ a.0 }} 同 {{ a[0] }}
- dictionary a={‘aa’:1,‘ab’:2,‘ac’:3,‘ad’:4} -> 取第一个元素:{{ a.aa }} 同 {{ a.get(‘aa’) }}
- 对象 a=Attendance(name=‘Peter’, address=‘Berlin’) -> 取对象中的地址属性:{{ a.address }} 同 {{ 对象.属性 }}
2. 表格的展示
此章节中,我们会先解释如何在html中书写if
和for
循环,然后进入表格的制作。在此之前,关于html中出现的关键字,可以参考此链接,也可以在VSCode中把鼠标移至关键字上方,会出现相关的英文提示。
2.1 if
和for
循环
控制块的书写逻辑如下:
{% if 条件 %}
...
{% else %}
...
{% endif %}
{% for 变量 in 可迭代对象 %}
...
{% endfor %}
在app.py
中,添加:
girlnames = ['Marry','Linda','Alexi','Mia','Bessie','Adam','韩梅梅','安吉丽娜','王刚']
users = [
{'username': 'AABB', 'password': 'abab', 'addr': 'shanghai', 'phone': '4321'},
{'username': 'AABB2', 'password': 'abab2', 'addr': 'shanghai2', 'phone': '43212'},
{'username': 'AABB3', 'password': 'abab3', 'addr': 'shanghai3', 'phone': '43213'},
{'username': 'AABB4', 'password': 'abab4', 'addr': 'shanghai4', 'phone': '43214'},
{'username': 'AABB5', 'password': 'abab5', 'addr': 'shanghai5', 'phone': '43215'},
{'username': 'AABB6', 'password': 'abab6', 'addr': 'shanghai6', 'phone': '43216'},
{'username': 'AABB7', 'password': 'abab7', 'addr': 'shanghai7', 'phone': '43217'}
]
return render_template('app_templateEx.html', names=name, \
ages=age, fruits=fruits, dict=dict1, attends=attendance1,\
girls=girlnames, usersTable = users)
在app_templateEx.html
中:
<hr>
<ul>
{% for girl in girls %}
{% if girl|length >=4 %} <!-- 当字符串长度大于等于4,这条字符串的颜色会变成红色 -->
<li class="a"> {{ girl }} </li> <!-- 注意页面最顶端的 style -->
{% else %}
<li> {{ girl }} </li>
{% endif %}
{% endfor %}
</ul>
<hr>
<table border="1" cellpadding="0" cellspacing="0" width="80%">
{% for user in usersTable %}
<tr {% if loop.index == 3 %} class="a" {% endif %}> <!-- loop.first, loop.last 第一行和最后一行 -->
<td> {{ loop.index }} </td> <!-- 如果loop.index0,那么序号从0开始,否则从1开始 -->
<td> {{ user.username }} </td> <!-- 当index为3的时候,对应的这行都变成红色,注意style对class a的描述 -->
<td> {{ user.password }} </td>
<td> {{ user.addr }} </td>
<td> {{ user.phone }} </td>
</tr>
{% endfor %}
对应的样式:
<style>
.a{
color: red;
font-weight: bold;
}
</style>
这里,第一部分(<ul>...</ul>
)通过for
循环,将girls
里面的每一个字符串作为bullet罗列出来。并且,如果字符串长度大于4,则该字符串显示红色。效果如下:
第二部分是表格的制作。我们将users
这个包含着dict的list作为变量导入进来,然后同样用for
循环去遍历整个列表。列表中的每一个元素是一个字典user
,字典的每一个元素表达为字典.元素
。当index为3的时候,对应的这行都变成红色。效果如下:
2.2 过滤器
过滤器本质就是一个函数,只是换了一种方式去使用而已。
{{ 变量名 | 过滤器(*args) }} # 需要传参的话
{{ 变量名 | 过滤器 }}
常见过滤器:
- safe 禁用转义
- capitalize 首字母大写,其余小写
- lower和upper
- title 一句话中的每个首字母大写
- reverse 反转
- format 格式化输出
- 列表过滤器:first, last, length, sum, sort等等
- 字典过滤器:.keys, .values, .items用于获取值,键,键值等等。
例子见app_templateEx.html
:
<p>过滤器的使用</p>
<p>
当前用户共:{{ girls | length }}人。
<br>
{{ msg | safe }} <!-- 如果没有safe这个禁止转义,这里就会直接显示<p>这是一条测试信息</p>,因为<这个符号会被转义成< -->
<br>
{{ fruits.1 }}。首字母大写:{{ fruits.1 | capitalize }}
<br>
{{ '%s is in %d days.' | format(fruits.1, ages) }}
<br>
<!-- 列表过滤器的例子 -->
{{ fruits | first }}<br>
{{ fruits | last }}<br>
{{ fruits | length }}<br>
{{ fruits | sort }}<br>
<!-- 字典过滤器的例子 -->
<p>获取字典过滤器的值</p>
{% for v in usersTable.0.values() %}
<p> {{v}} </p>
{% endfor %}
<p>获取字典过滤器的健</p>
{% for k in usersTable.0.keys() %}
<p> {{k}} </p>
{% endfor %}
<p>获取字典过滤器的健值</p>
{% for k,v in usersTable.0.items() %}
<p> {{k}} -- {{v}} </p>
{% endfor %}
</p>
2.3 自定义过滤器
两种方式:
1. 通过flask模块中的add_template_filter
方法
- 定义函数,带有参数和返回值(例如`app_template`中的`filter_replace`函数)
- 添加过滤器:`app.add_template_filter(function,name='')`
- 在模板中使用:{{ 变量 | 自定义过滤器 }}
在app.py
中,我们举了一个例子:
# 自定义过滤器,用法1
def filter_replace(value):
print('---->',value)
value = value.replace('hello','')
print('====>',value)
return value.strip() # 去除首尾的空格
app.add_template_filter(filter_replace, 'replace_')
2. 使用修饰器完成
- 定义函数,带有参数和返回值(例如`app_template`中的`filter_reverseList`函数)
- 通过装饰器完成:`@app.template_filter('过滤器名称')`装饰上面的函数
- 在模板中使用:{{ 变量 | 自定义过滤器 }}
在app.py
中,我们也举了一个例子:
# 自定义过滤器,用法2,使用装饰器
@app.template_filter('reverse_')
def filter_reverseList(li):
temp_li = list(li)
temp_li.reverse()
return temp_li
在app_templateEx.html
中,
<p>
自定义过滤器<br>
{{ filtermsg | replace_ }}<br> <!-- 第一种自定义过滤器的书写方式 -->
Original list: {{ fruits }}<br>
List after reverse: {{ fruits | reverse_ }}<br> <!-- 第二种自定义过滤器的书写方式 -->
</p>
3. 模板继承
3.1 类的继承
这里我们先大概描述一下类的继承,因为模板的继承与类的继承相似。
一个类继承另一个类时,会自动获得另一个类的所有属性和方法,被继承的类称之为父类,新类称为子类。子类拥有父类所有的属性和方法,并且可以定义自己的属性和方法。具体的例子可以参见链接1以及链接2。
3.2 模板继承大体步骤
父模板:
- 定义一个父模板,一般名字起
base.ml
- 分析这个模板中哪些是变化的,然后对需要变化的地方进行挖坑(对变化的部分用
block
进行预留位置)。{% block 名字 %} {% endblock %}
- 注意:关于样式和脚本。一般来说,子模板都会自己定制一部分的样式和脚本,所以需要在父模板中提前挖坑预留出来。预留的模块可以是空的,比如
{% block myjs %} {% endblock %}
,因为预留的目的是子模板需要用来进行填充。
子模板使用父模板:
{% extends '父模板的名称' %}
将父模板继承过来。- 找到对应的
block
(坑)进行填充,每一个block都有名字。
3.3 模板继承的一个例子:结果
我们先来看最终呈现的效果。当我们运行python app.py
后,我们会看到主界面如下:
如果我们点击了类的继承界面:base界面
,界面变成:
如果我们点击了类的继承界面:继承界面
,界面变成:
如果我们点击一个按钮
按钮:
如果我们点击两个返回主页
按钮,我们就会看到主界面。
3.4 模板继承的一个例子:代码
我们首先在app.py
中定义了两个路由,分别导向父模板和子模板的页面:
@app.route('/inherit',methods = ['GET','POST'])
def inherit():
# 在子模板中有一个按钮,点击后会回到主界面。
if request.method == "POST":
return redirect(location=url_for('mainpage'))
# 进入子模板的界面
return render_template('inherit_test.html')
@app.route('/base',methods = ['GET','POST'])
def base():
# 在父模板中有一个按钮,点击后会回到主界面。
if request.method == "POST":
return redirect(location=url_for('mainpage'))
# 进入父模板的界面
return render_template("base.html")
接下来,我们将对父模板index.html
以及子模板inherit_test.html
进行比对,看看模板的继承究竟是怎么用的。
(1) 我们为title挖了个坑,这样父模板和子模板的标题可以不同;
父模板:
<title>
{% block title %} <!-- 需要被改变的部分都可以用block来做一个标记 -->
基页
{% endblock %}
</title>
子模板:
{% block title %}
新的一页
{% endblock %}
这里我们就能看到,子模板不再需要填写<title></title>
框架了,只需要把父模板的坑填好。如果不填的话,子模板对应的内容会和父模板一样。
(2) 样式
父模块中,我们将整个版面分成了三个部分:head
, middle
以及foot
。其中,head
和foot
我们都定死了:
head
:
<div id="head">
<ul>
<li>首页</li>
<li>第一排</li>
<li>第二排</li>
<li>第三排</li>
<li>第四排</li>
</ul>
</div>
foot
:
<div id="foot">
底部填充
</div>
样式style
中,有一部分也是固定的:
<style>
#head{
height: 50px;
background-color: bisque;
}
#head ul{
list-style: none;
height: 50px;
}
#head ul li{
float: left;
width: 100px;
text-align: center;
font-size: 18;
height: 50px;
line-height: 50px;
}
#foot{
height: 50px;
line-height: 50px;
}
</style>
但是和middle
相关的部分是变化的:
父模板:
{% block mycss %}
<style>
#middle {
height: 600px;
background-color: cadetblue;
}
</style>
{% endblock %}
子模板:
{% block mycss %}
<style>
#middle {
height: 600px;
background-color: blueviolet;
color: white;
font-weight: bold;
}
.div1{
width: 30%;
float: left;
border: 1px solid red;
height: 300px;
}
</style>
(3) middle
部分
父模板:
<div id="middle">
这一行在两个页面是一致的。<br>
{% block middle %}
基页的中间行
<form action="/base" method="post">
<p><button>从基页返回主页</button></p> <!-- 为这个按钮写一个脚本(点下它之后会发生什么) -->
</form>
{% endblock %}
</div>
这一行在两个页面是一致的。
这部分是固定的,但下面的部分,包括middle部分的样式都是可变的。在base
页面中,我们定义了一个按钮,点击后,可以直接回到主界面(因为重定向的原因)。
子模板:
{% block middle %}
继承页的中间行
<p><button id="buttonBase">一个按钮</button></p>
<form action="/inherit" method="post">
<p><button>从继承页返回主页</button></p>
</form>
<div class="div1"></div>
<div class="div1"></div>
<div class="div1"></div>
{% endblock %}
除了一个从继承页返回主页
的按钮外,这里我们还加了一个一个按钮
按钮,点击后会跳出弹窗,显示ok
。
对应的,脚本位置我们也增加了一个坑:
父模块:
{% block myjs %}
{% endblock %}
子模块:
{% block myjs %}
<script>
btn = document.getElementById('buttonBase')
btn.onclick = function(){
alert('ok')
}
</script>
{% endblock %}
我们看到,父模块里只是挖了坑,里面没有任何内容。这也是允许的。
3.5 引入样式表
通常,我们不会把脚本和样式都放在一个html文件里,而是单独作为各自的.css
与.js
文件放在static
目录下,文件结构可以是:
--statics
|--css
|--style.css
|--js
|--index.js
|--images
在这里,我们做一个简单的例子。
我们先给inherit_test.html
中的一个div1
加一个id
,如下所示:
{% block middle %}
继承页的中间行
<p><button id="buttonBase">一个按钮</button></p>
<form action="/inherit" method="post">
<p><button>从继承页返回主页</button></p>
</form>
<div class="div1" id="d1"></div>
<div class="div1"></div>
<div class="div1"></div>
{% endblock %}
在style.css
(static/css路径下)中,我们只是给id为d1的模块加了一个绿色的背景色。
#d1{
background-color: green;
}
在inherit_test.html
中,添加
<link rel="stylesheet" href="{{url_for('static',filename='css/style.css')}}">
3.6 引入图片
现在,我们试试将图片导入到inherit_test.html
页面中。图片保存于statics/images
路径下。
在inherit_test.html
中添加:
<img src="{{url_for('static',filename='images/view3.png')}}" >
4. 宏:Macro
把它看作jinjia2这个模板引擎的一个函数。这个函数可以返回一个html的字符串。
目的:为了避免重复定义相同代码,以代码复用。
4.1 结论
两种定义方式
- 在模板中直接定义
- 将所有宏提取到一个模板中,谁想用就直接导入
{% import 'macro/macro.html' as XXX %}
{{ XXX.form(...) }}
4.2 代码
我们在macro/
路径下新建macro1.html
,宏的定义写法如下:
{% macro form(action,value='登录',method='POST') %}
<form action="{{ action }}" method="{{ method }}">
<input type="text" placeholder="用户名" name="username">
<br>
<input type="password" placeholder="密码" name="password">
<br>
<input type="submit" placeholder="{{ value }}">
</form>
{% endmacro %}
如果我们不用宏,那么就是这么写:
<form action="/test7" method="post">
<p><input type="text" name="username" placeholder="请输入用户名"></p>
<p><input type="text" name="address" placeholder="请输入地址"></p>
<p><input type="submit" placeholder="提交"></p>
</form>
所以,宏就像是一个函数一样。我们如果要调用宏,在同一文件内:
{{ form('/') }}
如果我们要在另一个html文件中调用这个宏,我们可以先把这段宏代码放在一个独立的html文件中,比如/macro/macro.html
,然后在其他的html文件内导入:
{% import 'macro/macro.html' as macroTemp %}
{{ macroTemp.form('/inherit', value='注册') }}
5. 总结
变量:{{ 变量 }}
{{ url_for(‘static’,filename=’’) }}
{{ hongname(xxx) }} 宏的调用
块:{% %}
{% if 条件 %} … {% endif %}
{% for 条件 %} … {% endfor %}
{% block 条件 %} … {% endblock %}
{% macro 条件 %} … {% endmacro %}
{% include ‘’ %} 包含
{% import ‘’ %} 导入宏
{% extends %} 模板继承