在动态Web程序中,视图函数返回的HTML数据往往需要根据相应的变量(比如查询参数)动态生成。当HTML代码保存到单独的文件中时,我们没法再使用字符串格式化或拼接字符串的方式来在HTML代码
中插入变量,这时我们需要使用模板引擎(template engine)。借助模板引擎,我们可以在HTML文件中使用特殊的语法来标记出变量,这类包含固定内容和动态部分的可重用文件称为模板(template)。
3.1 模板基本用法
3.1.1创建模板
先创建虚拟数据:
user = {
'username': 'Grey Li',
'bio': 'A boy who loves movies and music.',
}
movies = [
{'name': 'My Neighbor Totoro', 'year': '1988'},
{'name': 'Three Colours trilogy', 'year': '1993'},
{'name': 'Forrest Gump', 'year': '1994'},
{'name': 'Perfect Blue', 'year': '1997'},
{'name': 'The Matrix', 'year': '1999'},
{'name': 'Memento', 'year': '2000'},
{'name': 'The Bucket list', 'year': '2007'},
{'name': 'Black Swan', 'year': '2010'},
{'name': 'Gone Girl', 'year': '2014'},
{'name': 'CoCo', 'year': '2017'},
]
然后在templates目录下新建html模板文件,使用Jinja2语法操作变量:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ user.username }}'s Watchlist</title>
</head>
<body>
<a href="{{ url_for('index') }}">← Return</a>
<h2>{{ user.username }}</h2>
{% if user.bio %}
<i>{{ user.bio }}</i>
{% else %}
<i>This user has not provided a bio.</i>
{% endif %}
{# 下面是电影清单(这是注释) #}
<h5>{{ user.username }}'s Watchlist ({{ movies|length }}):</h5>
<ul>
{% for movie in movies %}
<li>{{ movie.name }} - {{ movie.year }}</li>
{% endfor %}
</ul>
</body>
</html>
在Jinja2中常见的三种界定符:
- 语句
如 if判断、for循环等:{% … %}
- 表达式
如字符串、变量、函数调用等:{{ … }}
- 注释
{# … #}
Jinja2还支持“.”获取变量属性,如user.username 等同于 user[‘username’]
3.1.2 模板语法
Jinja2允许你在模板中使用大部分Python对象,比如字符串、列
表、字典、元组、整型、浮点型、布尔值。它支持基本的运算符号
(+、-、*、/等)、比较符号(比如==、!=等)、逻辑符号(and、
or、not和括号)以及in、is、None和布尔值(True、False)。
在Jinja2里,语句使用{% … %}标识,在语句结束地方,必须添加结束标签:
{% if user.bio %}
<i>{{ user.bio }}</i>
{% else %}
<i>This user has not provided a bio.</i>
{% endif %}
和Python一样,for语句用来迭代一个序列:
<ul>
{% for movie in movies %}
<li>{{ movie.name }} - {{ movie.year }}</li>
{% endfor %}
</ul>
常用Jinja2 for循环特殊变量:
3.1.3 渲染模板
使用Flask提供的render_template()渲染函数,如下代码所示:
from flask import Flask, render_template
...
@app.route('/watchlist')
def watchlist():
return render_template('watchlist.html', user=user, movies=movies)
Flask会在程序根目录下的templates文件夹里寻找模板文件,所以这里传入的文件路径是相对于templates根目录的。除了模板文件路径,我们还以关键字参数的形式传入了模板中使
用的变量值,以user为例:左边的user表示传入模板的变量名称,右边的user则是要传入的对象。
3.1.4 过滤器
过滤器和变量用一个竖线(管道符号)隔开,需要参数的过滤器可以像函数一样使用括号传递。下面是一个对name变量使用title过滤器的例子:
{{ name|title }}
另一种用法是将过滤器作用于一部分模板数据,使用filter标签和endfilter标签声明开始和结束。比如,下面使用upper过滤器将一段文字转换为大写:
{% filter upper %}
This text becomes uppercase.
{% endfilter %}
常用内置过滤器:
自定义过滤器:
使用app.template_filter() 装饰器可以注册自定义过滤器,如一下代码所示:
from flask import Markup
@app.template_filter()
def musical(s):
return s + Markup(' ♫')
使用方式和其他过滤器相同
{{ name|musical }}
3.2 模板结构组织
3.2.1 局部模板
使用include标签插入一个局部模板文件,这会把局部模板全部内容插在使用include标签的位置。如在其他模板中,如下:
{% include '_banner.html' %}
局部模板命名通常以“_”下换线开始。
3.2.2 宏
为了方便管理,将宏存放单独的文件夹中,文件通常以macros.html或_macors.html命名。
使用 macro 和 endmacro 标签声明宏的开始和结束。
简单实例:
{% macro qux(amount=1) %}
{% if amount == 1 %}
I am qux.
{% elif amount > 1 %}
We are quxs.
{% endif %}
{% endmacro %}
使用时,像Python中导入函数一样,使用import语句导入,然后作为函数调用,传入参数。如下所示:
{% from 'macros.html' import qux %}
...
{{ qux(amount=5) }}
3.2.3 模板继承
-
编写基模板
通常命名为base.html或者layout.html,定义基模板,实例如下:<!DOCTYPE html> <html> <head> {% block head %} <meta charset="utf-8"> <title>{% block title %}Template - HelloFlask{% endblock %}</title> {% block styles %}{% endblock %} {% endblock %} </head> <body> <nav> <ul><li><a href="{{ url_for('index') }}">Home</a></li></ul> </nav> <main> {% block content %}{% endblock %} </main> <footer> {% block footer %} ... {% endblock %} </footer> {% block scripts %}{% endblock %} </body> </html>
模板继承示意图:
-
编写子模板
使用extends标签声明扩展基模板,它告诉模板引擎当前模板继承base.html。{% extends 'base.html' %} {% from 'macros.html' import qux %} {% block content %} {% set name='baz' %} <h1>Template</h1> <ul> <li><a href="{{ url_for('watchlist') }}">Watchlist</a></li> <li>Filter: {{ foo|musical }}</li> <li>Global: {{ bar() }}</li> <li>Test: {% if name is baz %}I am baz.{% endif %}</li> <li>Macro: {{ qux(amount=5) }}</li> </ul> {% endblock %}
extends 必须是子模板的第一个标签。
(1) 覆盖内容
当在子模板里创建同名的块时,会使用子块的内容覆盖父块的内容。比如我们在子模板index.html中定义了title块,内容为Home,这会把块中的内容填充到基模板里的title块的位置
(2) 追加内容
如果想要向基模板中的块追加内容,需要使用Jinja2提供的super()函数进行声明,这会向父块添加内容。比如,下面的示例向基模板中的styles块追加了一行< style >样式定义:{% block styles %} {{ super() }} <style> .foo { color: red; } </style> {% endblock %}
3.3 模板进阶
-
使用宏加载静态资源
{% macro static_file(type, filename_or_url, local=True) %} {% if local %} {% set filename_or_url = url_for('static', filename=filename_or_url) %} {% endif %} {% if type == 'css' %} <link rel="stylesheet" href="{{ filename_or_url }}" type="text/css"> {% elif type == 'js' %} <script type="text/javascript" src="{{ filename_or_url }}"></script> {% elif type == 'icon' %} <link rel="icon" href="{{ filename_or_url }}"> {% endif %} {% endmacro %}
在模板中导入宏后,只需在调用时传入静态资源的类别和文件路径就会获得完整的资源加载语句。使用它加载CSS文件的示例如下:
static_file('css', 'css/bootstrap.min.css')
使用它也可以从CDN加载资源,只需要将关键字参数local设为False,然后传入资源的URL即可:
static_file('css', 'https://maxcdn.../css/bootstrap.min.css', local=False)
-
消息闪现
flash() 函数,可以用来“闪现”需要显示给用户的消息,实际上,flash()函数发送的消息会存储在session中,需要在模板中使用全局函数 get_flashed_messages() 获取消息并显示出来。
在app.py文件中,使用flash() 函数“闪现”消息:from flask import Flask, render_template, flash app = Flask(__name__) app.secret_key = 'secret string' @app.route('/flash') def just_flash(): flash('I am flash, who is looking for me?') return redirect(url_for('index'))
在模板文件中使用 get_flashed_messages() 函数渲染消息:
<main> {% for message in get_flashed_messages() %} <div class="alert">{{ message }}</div> {% endfor %} {% block content %}{% endblock %} </main>
-
自定义错误页面
在模板文件夹templates里为错误页面创建了一个errors子文件夹,并在其中为最常见的404和500错误创建了模板文件,表示404页面的404.html模板内容如代码:{% extends 'base.html' %} {% block title %}404 - Page Not Found{% endblock %} {% block content %} <h1>Page Not Found</h1> <p>You are lost...</p> {% endblock %}
错误处理函数需要附加app.errorhandler() 装饰器,并传入错误状态码作为参数。错误处理函数本身则需要接收异常类作为参数,并在返回值中注明对应的HTTP状态码。当发生错误时,对应的错误处理函数会被调用,它的返回值会作为错误响应的主体。
from flask import Flask, render_template ... @app.errorhandler(404) def page_not_found(e): return render_template('errors/404.html'), 404
安利一门Python超级好课!
扫码下单输优惠码【csdnfxzs】再减5元,比官网还便宜!