Flask:模版与静态资源

视图函数可以直接返回文本,但在正式的项目中很少这么做,因为大量的HTML代码与Python代码杂糅在一起影响逻辑代码的读写,并且不利于与前端开发者进行分工。这时就需要前端人员单独编写HTML文件并把可变的部分抽取出来,后台开发者读取HTML文件内容对可变部分进行数据填充后返回给浏览器。

模版的演变

前端与后台需要约定使用哪种方式对可变的部分进行抽取,下面通过一系列例子来演示从内置函数到开源库的演变

字符串格式化

前端开发者通过使用花括号({})来对可变的部分进行占位,后台开发者通过字符串对象的format方法对占位部分进行填充

1
2
s = "hello {}, my name is {}"
print(s.format("roiz", "harmel"))

打印结果:hello roiz, my name is harmel

有几个占位format方法就要传入几个字符串值,会根据传入实参的先后顺序进行填充

占位也可以加索引,索引号从0开始

1
2
s = "hello {1}, my name is {0}"
print(s.format("roiz", "harmel"))

打印结果:hello harmel, my name is roiz

会根据传入实参的先后顺序填充到相应索引位

缺点:如果少填充一个值则会抛出异常,占位部分不支持命名方式容易漏值

string.Template

在Python中自带了一个功能非常有限的模版实现,采用$加变量名的方式定义可变的部分

1
2
3
4
5
6
7
8
9
10
11
from string import Template

t = Template("hello $a, my name is $b")
# 定义了几个模版变量就必须传入对应的值
s1 = t.substitute(a="roiz", b="harmel")

# 没有传值的变量会原样输出
s2 = t.safe_substitute(a="roiz")

print(s1)
print(s2)

打印结果:
hello roiz, my name is harmel
hello roiz, my name is $b

缺点:功能有限不支持控制语句

Jinja2

Jinja2是Flask默认的模版引擎,在通过pip方式安装Flask时会一并安装,Jinja2可以在任何地方使用而不仅仅限于Flask

变量

在Jinja2中通过 的方式对可变部分进行占位,对于传入变量的值只要能够被str()转换成一个字符串即可正常输出,如果变量是个对象也可以采用连点的方式获取属性值和调用方法

1
2
3
4
5
6
7
8
from jinja2 import Template

t = Template("Hello {{a}}, my name is {{b}}")
s1 = t.render(a="roiz", b="harmel")
s1 = t.render(a="roiz")

print(s1)
print(s2)

打印结果:
hello roiz, my name is harmel
hello roiz, my name is

过滤器

在输出变量的同时开可以指定过滤器对其进行修改,语法格式:变量名 | 过滤器

1
2
3
4
5
6
from jinja2 import Template

t = Template("Hello {{a | title}}, my name is {{b | title}}")
s = t.render(a="roiz", b="harmel")

print(s)

打印结果:hello Roiz, my name is Harmel

Jinja2提供了大量的内置过滤器常用转换器有以下几个

过滤器名功能
safe对标签不进行转义
length返回字符串长度
trim去除首尾空格
upper转大写
lower转小写
filesizeformat对数值进行可读方式显示(KB、MB、G)

如果有特殊需求也可以自定义过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
from jinja2 import Environment

def filter(s):
return "~" + s + "~"

env = Environment()
env.filters['wrap'] = filter

# 通过环境上下文读取字符串加载正Template对象
t = env.from_string("Hello {{a | wrap}}, my name is {{b | wrap}}")
s = t.render(a="roiz", b="harmel")

print(s)

打印结果:Hello ~roiz~, my name is ~harmel~

注: 过滤器也可以带参数

过滤器还可以对一块文字进行过滤

1
2
{% filter 过滤器名 %}
{% endfilter %}

上面提到的方式都需要开发者自己去读取HTML文件内容后进行转换,Jinja2的Environment对象提供了 get_template 方法替我们加载HTML文件并返回Template对象,但在实际的开发中我们一般使用Flask替我们包装好的方法 render_template

模版文件:hello.html

1
Hello {{a}}, my name is {{b}}

主程序

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
return render_template("hello.html", a="roiz", b="harmel")

if __name__ == "__main__":
app.run(debug=True)

模版文件(hello.html)目前需要放在主程序同一目录下的 templates 文件夹下,下面的例子都将使用Flask包装的方式编写不在使用原始的Template对象

上面演示了原始的自定义过滤器,而Flask中注册过滤器更加简单

1
2
3
4
5
# 方式一
app.jinja_env.filters["过滤器名"] = 过滤器函数

# 方式二:在自定义过滤器函数上添加装饰器,底层还是使用的是方式一
@app.template_filter("过滤器名")

注释

在将模版渲染成字符串的时候会忽略掉注释部分

1
2
3
4
5
6
from jinja2 import Template

t = Template("{# hello #} world")
s = t.render()

print(s)

打印结果:world

设置变量

hello.html

1
2
3
{% set a = "roiz" %}
{% set b = "harmel" %}
Hello {{a}}, my name is {{b}}

字符串需要用引号包裹

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
return render_template("hello.html")

if __name__ == "__main__":
app.run(debug=True)

页面显示效果:Hello roiz, my name is harmel

下文不做说明的话都是以这个程序为基础做修改

注: 在模版还中可以直接使用如下几个变量:config、request、session

页面包含

header.html

1
this is header content

footer.html

1
this is footer content

hello.html

1
2
3
4
5
6
7
{% include "header.html" %}
<br>
{% set a = "roiz" %}
{% set b = "harmel" %}
Hello {{a}}, my name is {{b}}
<br>
{% include "footer.html" %}

页面显示效果:
this is header content
Hello roiz, my name is harmel
this is footer content

如果包含的页面不存在会抛出异常,include可以使用ignore missing标记,当包含的页面不存在时会忽略这条包含语句

1
2
3
4
5
6
7
8
9
{% include "xxxx.html" ignore missing %}

{% include "header.html" %}
<br>
{% set a = "roiz" %}
{% set b = "harmel" %}
Hello {{a}}, my name is {{b}}
<br>
{% include "footer.html" %}

宏类似于编程语言中的函数,用于把常用的逻辑抽取

1
2
3
4
5
6
7
{% macro say(name) %}
hello {{ name }}
{% endmacro %}

{% set a = "roiz" %}

{{ say(a) }}

页面显示效果:hello roiz

如果定义的宏在其他文件中就需要导入后使用

my_macro.html

1
2
3
{% macro say(name) %}
hello {{ name }}
{% endmacro %}

hello.html

1
2
3
4
5
{% import "my_macro.html" as m %}

{% set a = "roiz" %}

{{ m.say(a) }}

导入的宏文件必须有别名,显示的效果与上面一样

条件判断

1、判断变量是否存在

1
2
3
4
5
{% set a = "harmel" %}

{% if a %}
hello {{ a }}
{% endif %}

页面显示效果:hello harmel

注: 与Python中的if一样会认为0、””、None、[]、(,)为假

2、条件判断

1
2
3
4
5
{% set a = 10 %}

{% if a < 15 %}
{{ a }}
{% endif %}

页面显示效果:10

3、多条件判断

1
2
3
4
5
6
7
8
9
10
11
{% set score = 65 %}

{% if score >= 85 %}
A
{% elif score >= 70 %}
B
{% elif score >= 60%}
C
{% else %}
D
{% endif %}

页面显示效果:C

4、is判断

1
2
3
4
5
{% set a = 13 %}

{% if a is odd %}
odd
{% endif %}

注: odd是内置的判断函数如果为奇数返回真

页面显示效果为:odd

自定义is判断函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, render_template

app = Flask(__name__)

@app.template_test("empty")
def is_empty(arg):
# 如果为空返回True
return len(arg) == 0

@app.route("/")
def index():
return render_template("hello.html")

if __name__ == "__main__":
app.run(debug=True)

hello.html

1
2
3
4
5
{% set a = [] %}

{% if a is empty %}
empty
{% endif %}

页面显示效果为:empty

注: 装饰器修饰的方法必须返回一个布尔值

迭代

迭代集合

1
2
3
4
5
{% set array = [1,2,3] %}

{% for item in array %}
{{ item }}
{% endfor %}

页面显示效果:1 2 3

模版语言里也可以调用方法

1
2
3
4
5
{% set array = "1,2,3" %}

{% for item in array.split(",") %}
{{ item }}
{% endfor %}

页面显示效果:1 2 3

当集合为空时显示一条提示信息,不为空则迭代

1
2
3
4
5
6
7
8
9
{% set array = [] %}

{% if array %}
{% for item in array %}
{{ item }}
{% endfor %}
{% else %}
empty
{% endif %}

页面显示效果:empty,如果array = [1,2,3]则显示效果为:1,2,3

是不是觉得语句很长其实Jinja2早就考虑到了,简便写法为

1
2
3
4
5
6
7
{% set array = [] %}

{% for item in array %}
{{ item }}
{% else %}
empty
{% endfor %}

注: 千万别写成了Django里的{\% empty %}了,在Jinja2中是else

迭代时还可以使用loop变量该变量有first、last、index等常用属性

1
2
3
4
5
6
7
8
9
10
11
{% set array = ["A", "B", "C"] %}

{% for item in array %}
{% if loop.first %}
first:{{ item }} <br>
{% elif loop.last %}
last:{{ item }} <br>
{% else %}
{{ item }} <br>
{% endif%}
{% endfor %}

页面显示效果:
first:A
B
last:C

迭代时还可以使用判断条件进行过滤

1
2
3
4
5
{% set array = [1,2,3,4,5] %}

{% for item in array if item % 2 == 0 %}
{{ item }}
{% endfor %}

消息闪现

在视图函数中使用flash()方法来发送消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, flash, render_template

app = Flask(__name__)
# 必须配置安全密钥
app.config["SECRET_KEY"] = "abc"

# 另一种方式配置安全密钥的方式
# app.secret_key = "abc"

@app.route("/")
def index():
flash("Ha ~")
return render_template("flash.html")

if __name__ == "__main__":
app.run(debug=True)

在模版中采用如下方式调用

flash.html

1
2
3
{% for msg in get_flashed_messages() %}
{{ msg }}
{% endfor %}

页面显示效果:Ha ~

自定义函数

可以直接在模版中使用自定义的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, render_template

app = Flask(__name__)

# 此函数必须返回一个字典
@app.context_processor
def register():
def func(arg):
return "hello " + str(arg)
return dict(hello=func)

@app.route("/")
def index():
return render_template("hello.html")

if __name__ == "__main__":
app.run(debug=True)

hello.html

1
{{ hello("harmel") }}

页面显示效果:hello harmel

注: 不仅限于将函数暴露给模版使用,也可以暴露任何值

模版继承

模版继承有两个角色分别是母版和子版,母版提供模版骨架子版替换母版中的内容,合理使用模版继承能提高模版的重用

base.html

1
hello {% block name %} anonymous {% endblock %}

hello.html

1
2
{% extends "base.html" %}
{% block name %} harmel {% endblock %}

注: extends语句必须为子版的第一条语句

页面显示效果:hello harmel

如果子板想重用母版块的内容可以使用 super() 函数,该函数必须位于块内

hello.html

1
2
{% extends "base.html" %}
{% block name %} harmel, {{ super() }} {% endblock %}

页面显示效果:hello harmel, anonymous

如果想在块外引用块的内容可以使用如下的方式
base.html

1
hello {% block name %} anonymous {% endblock %}, {{ self.name() }}

注:只能在母版中使用,因为子版只做内容填充

页面显示效果:hello harmel anonymous

动态生成URL

在模版中如果写死URL假如URL路由地址变了则模版中的地址全部需要修改,一般采用如下方式进行反向绑定视图函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
return render_template("hello.html")

@app.route("/user/<int:uid>")
def user(uid):
return "User Home"

if __name__ == "__main__":
app.run(debug=True)

hello.html

1
{{ url_for("user", uid=12) }}

页面显示效果:/user/12,如果想要显示绝对路径可以传入_external=True

传入url_for()的参数不限于路由中的参数,还能传入任何参数,显示效果为附加一查询字符串

1
{{ url_for("user", uid=12, age=20) }}

页面显示效果:/user/12?age=20

注: 如果视图函数名改了也是要修改模版中的地址

原样输出

对一段代码不做解析进行原样输出

1
2
3
{% raw %}
my name is {{ name }}
{% endraw %}

被包裹的代码会被原样输出,不会对进行解析

模版文件位置

如果在主程序中构造Flask对象时传入的字符串值为__main__,则默认加载主程序所在目录下的 templates 文件夹,如果你的模版文件夹不叫这个名字可以在构造Flask对象的时候指定

1
app = Flask(__name__, template_folder="tmpl")

如果构造Flask对象时传入的字符串值不是__main__,则去字符串指定的文件夹下查找,该文件夹必须为一Python包,也就是说该文件夹下必须有一个 init.py 文件

静态资源

如果在主程序中构造Flask对象时传入的字符串值为__main__,则默认加载主程序所在目录下的 static 文件夹,如果你存放静态资源的文件夹不叫这个名字可以在构造Flask对象的时候指定

1
app = Flask(__name__, static_folder="common")

如果构造Flask对象时传入的字符串值不是__main__,则去字符串指定的文件夹下查找,该文件夹必须为一Python包,也就是说该文件夹下必须有一个 init.py 文件

模版中引入静态资源

存放静态资源的目录允许有层级,在引入的时候也要有层级关系

1
{{ url_for("static", filename="文件名") }}

  • 本文作者: Harmel
  • 本文链接: http://www.harmel.cn/2018/08/flask-template.html
  • 版权声明: 文章如无特别说明,则表明该文章为原创文章,如需要转载,请注明出处。
  • 本站说明: 本站使用阿里云服务器,如果您喜欢我的网站,欢迎收藏,能捐赠支持一下就再好不过了。