模板的作用是内容注入、页面继承与包含,Jinja2 模板引擎为此实现了一整套完善的解决方案。
在使用模板时,所有静态资源应放置在 static 资源目录下,模板页面应放置在 templates 模板目录下。
1.使用模板
首先,在 templates 模板目录下创建一个模板页面文件 value.html
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注入变量</title>
</head>
<body>
测试内容
</body>
</html>
在视图函数中,调用模板渲染函数便可生成图片。
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/value')
def value(username=None):
return render_template('value.html')
if __name__ == "__main__":
app.run()
2.基本语法
以下是 3 种基本语法,在接下来的代码中将会经常出现。
{% ... %}
为控制语句,常用于实现结构控制,定义模板、变量等。{{ ... }}
为表达式语句,常用于输出变量,调用宏观指令、对象函数等。{# ... #}
为注释语句,用于添加代码注解。
3.注入变量
如果只是加载模板,那么网页呈现出的效果其实与静态页面无异。要实现动态网页,需要根据不同的情况显示不同的内容,这个过程便是注入变量。
注入变量的过程通常分两步,首先,在视图函数中注入变量。
@app.route('/value')
@app.route('/value/<string:username>')
def value(username=None):
return render_template('value.html', username=username)
其次,如果需要将变量显示到页面中,则需要在模板文件中使用表达式语句输出变量,示例代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注入变量</title>
</head>
<body>
{# 在表达式语句中,可使用 or 为变量添加默认值 #}
<h1>{{ username or '游客'}}</h1>
<h2> 欢迎使用!!! </h2>
</body>
</html>
4.生成链接
在 Flask 构建的网络中,每一个功能都对应一个视图函数。如果需要将不同的页面联系起来,便需要使用链接标签。在通常情况下,链接标签中所对应的链接可以在模板中使用函数 url_for()
生成。
<!-- 生成静态文件链接 -->
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css)}}">
<!-- 生成路由链接 -->
<a href="{{ url_for('value') }}">
注入变量演示
</a>
<!-- 生成路由链接(带参数) -->
<a href="{{ url_for('value', username='admin) }}">
注入变量演示(username=admin)
</a>
5.控制结构
下面是一个文章列表的视图函数,参数 num
用于控制文章列表(模拟)的文章数量,在生成文章列表之后,将其注入模板中进行显示。
@app.route('/control')
@app.route('/control/<int:number>')
def control(num=0):
# 此处使用 list 模拟文章列表
articles = []
for i in range(1, num+1):
articles.append({
'title' : '文章%d标题' % i,
'content' : ('文章%d内容' % i) * i,
})
return render_template('control.html', num=num, articles=articles)
文章列表页模板(control.html)的代码如下,这里给出了 if
和 for
语句的基本使用方式的简单例子,我们会发现这些语句与 Python 的控制语句相似,同时又类似与 HTML 标签,需要使用 {% end*** %}
语句,对前面的控制语句进行闭合。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>控制结构</title>
</head>
<body>
<h1>文章列表</h1>
{% if num <=0 %}
<h2> 文章数量为 0,没有内容可展示 </h2>
{% else %}
{% for article in articles %}
<h2>{{ article.title }}</h2>
<p>{{ article.content }}</p>
{% endfor %}
{% endif %}
</body>
</html>
6.模板的包含与继承
- 模板可以用于解决公共内容的冗余问题。例如,将公共部分的内容分离到一个单独的文件,再将模板加载到需要使用的页面中。如果按这种模式进行开发,在修改公共部分内容时,仅需要修改公共部分内容所处的文件,大大缩短了修改各个页面的时间。
- 在大多数的情况下,模板包含可以实现大部分功能,但是以模板包含的方式进行网站开发,仍然需要在每一个页面中添加包含代码。
- 而当包含页面的代码也是公共部分时,模板包含便不再适用,此时应引入新的方式进行开发——模板继承。
基准页extends_base.html :{% block %}
语句用于定义可被继承页面修改的区块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
{% block title %}
{# 网页标题 #}
{% endblock %}
</title>
{% block head %}
{# 网页所引用的样式等静态资源 #}
{% endblock %}
</head>
<body>
{% block content %}
{# 网页内容 #}
{% endblock %}
{% block script %}
{# 网页所引用的 JavaScript 资源 #}
{% endblock %}
</body>
</html>
前端基准页 extends_bootstrap.html
由于前端统一使用 Bootstrap 框架进行开发,所以需要建立一个前端网页的基准页,以自动引入相关静态资源及加载导航栏。
{% extends 'extends_base.html' %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}">
{% endblock %}
{% block script %}
<script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap.min.js') }}"></script>
{% endblock %}
{% block content %}
{% include 'include_nav.html' %}
<div class="container">
{% block inner_content %}
{# 模板页面的正文内容 #}
{% endblock %}
</div>
{% endblock %}
包含导航栏模板的完整页面 extends.html
{% extends 'extends_bootstrap.html' %}
{% block title %}模板的继承、包含{% endblock %}
{% block inner_content %}
<h1>模板的继承</h1>
<p>可以使复杂的机构变简单。以免在多个页面中引用静态资源。</p>
<h1>模板的包含</h1>
<p>可以使复杂的部分单独拆分到某一个文件。</p>
{% endblock %}
{% block script %}
{# 继承模板内容并添加新内容 #}
{{ super() }}
<script>
// 以下代码用于添加文字动画
let items = $('h1.p');
items.hide();
items.fadeIn(1000);
</script>
{% endblock %}
添加视图函数
@app.route('/extends')
def extends():
return render_template('extends.html')
7.宏指令
模板的包含、继承也只是从已经编好的模板中加载相应的内容,并将模板内容覆盖到当前页面下。在常见的应用中,会有包含相同文章项目的页面,例如网站首页中和文章列表页面中包含相同的文章项目,此时,文章项目的显示代码就需要在两个页面中重复编写,这种方式操作起来较为繁琐,消耗的资源也较多。此时的文章项目属于公共部分,但在需要多次生成文章项目的情况下,包含和继承便不再适合,宏指令便是此问题的解决方案。
宏命令并不复杂,类似于 Python 中的函数,拥有参数,可以被调用,用于生成网页内容。
视图函数
@app.route('/macro')
@app.route('/macro/<int:num>')
def macro(num=0):
# 此处使用 list 模拟文章列表
articles = []
for i in range(1, num+1):
articles.append({'title' : '文章%d标题' % i,
'content' : ('文章%d内容' % i) * i,
})
return render_template('macro.html',
num = num,
num_prev = num - 1,
num_next = num + 1,
articles = articles)
模板文件 macro.html
{% extends 'extends_bootstrap.html' %}
{# 宏指令可以定义在单独的文件中,然后被其他文件引入 #}
{% import 'macro_define.html' as macro %}
{% block title %}
宏指令
{% endblock %}
{% block inner_content %}
{# 宏指令的使用方法类似于函数 #}
{{ macro.article_list(articles, num) }}
{% if num > 0 %}
<a href="{{ url_for('macro', num=num_prev) }}" class="btnbtn-primary">-1</a>
{% endif %}
<a href="{{ url_for('macro', num=num_next) }}"class="btn btn-primary">+1</a>
{% endblock %}
宏指令模板文件 macro_define.html
{% macro article_item(title, content) %}
<h2>{{ title }}</h2>
<p>{{ content }}</p>
{% endmacro %}
{% macro article_list(articles, article_num) %}
{% if article_num <= 0 %}
<h2> 文章数量为0,没有内容可展示。</h2>
{% else %}
{% for article in articles %}
{{ article_item(article.title, article.content) }}
{% endfor %}
{% endif %}
{% endmacro %}
8.注册全局对象
在某些特殊的模板中,可以通过直接调用函数来获取数据。但在被继承的模板中,如果需要调用某个固定的函数,通常需要在每一个使用被继承模板的视图函数中注入相应的函数(变量),但这显然不是推荐的操作方法。这时,需要注册全局对象。
模板文件 global.html
{% extends 'extends_bootstrap.html' %}
{% block title %}
注册全局对象
{% endblock %}
{% block inner_content %}
{# 此处将数字列表输出到页面中 #}
{{ global_test(10) }}
{% endblock %}
全局对象使用示例
# 注册全局对象的类型不限,可以是任何类型(函数亦可)
def range_list(x):
return list(range(x))
# 将函数注册到全局对象中
app.add_template_global(range_list, 'global_test')
@app.route('/global')
def global_():
return render_template('global.html')
9.变量过滤器
变量过滤器用于对注入变量进行简单处理。在模板页面注入变量时,只需要在变量后面添加 |
即可调用过滤器方法。
常见的过滤器基本用法
{# 字符串的首字母大写,其他小写 #}
<p>{{ 'test' | capitalize }}</p>
{# 字符串格式化 #}
<p>{{ '你好 %s ,这里是 %s 。' | format(request.remote_addr, request.path) }}</p>
{# 字符串转小写 #}
<p>{{ 'TESTTEST' | lower }}</p>
{# 字符串转大写 #}
<p>{{ 'testtest' | upper }}</p>
{# 字符串替换 #}
<p>{{ 'Hello Test' | replace('Test', 'World') }}</p>
{# 翻转字符串 #}
<p>{{ '?uoy era woH' | reverse }}</p>
{# 清除字符串首尾多余的空格 #}
<p>{{ ' 这 是 一 段 测 试 内 容 ' | trim }}</p>
{# 截断字符串 #}
<p>{{ '这 是 一 段 测 试 内 容' | truncate(8) }}</p>
{# 计算字符串中单词的数量 #}
<p>{{ 'How are you?' | wordcount }}</p>
{# 四舍五入 #}
<p>{{ 1.3 | round }}</p>
<p>{{ 1.5 | round }}</p>
{# 为未定义的变量提供默认值 #}
<p>{{ undefined | default('默认值') }}</p>
{# 保留HTML 实体,不对内容进行转义 #}
<p>{{ '<script>alert("这里是safe 变量过滤器演示,请不要在不安全的数据中使用,否则用户将会受到攻击;就如这个弹框(恶意代码)。")</script>' | safe }}</p>
{# 对HTML 实体字符进行转义 #}
<p>{{ '<script>alert("test")</script>' | escape }}</p>
{# 清除字符串中包含的HTML 标签 #}
<p>{{ '<script>alert("test")</script>' | striptags }}</p>
{# 将对象转换为JSON 格式,通常在<script> 标签对中使用 #}
<script>
let data = {{ {'name': request.remote_addr} | tojson }};
alert("你好呀!" + data["name"]);
</script>
{# 对内容进行URL 编码 #}
<a href="?keyword={{ '这 是 一 段 测 试 内 容' | urlencode }}"> 链接</a>
自定义过滤器
# 自定义一个转换时间的过滤器
def convert_time(t):
return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
# 注册模板变量过滤器
app.add_template_filter(convert_time)