• 模板(template):在html文件中使用特殊的语法来标记出变量,这类包含固定和鼎泰部分的可重用的文件成为模板;
  • 模板引擎(template engine):读取并支持模板中特殊语法标记,根据传入变量输出最终内容
  • 渲染(rendering):上述过程成为渲染

模板基本用法

定界符

1 语句:如if、for等
{% … %}

2 表达式:如字符串、变量、函数调用等
{{ … }}

3 注释
{# … #}

模板语法

  1. {% for x in lists %}
  2. ...
  3. {% endfor %}

渲染模板

  • 使用Flask提供的渲染函数 render_template()
    • reder_template()函数中,首先传入模板文件名作为参数,(传入文件路径时相对于templates根目录,因为Flask会在程序根目录下的templates文件夹寻找模板文件)
    • 以关键字参数的形式传入模板中使用的变量
  1. form flask import render_template
  2. @app.route('/watchlist')
  3. def watchlist():
  4. ...
  5. return render_template("watchlist.html", user=user, movies=movies)

模板辅助工具

  • 辅助工具可以方便的控制模板的输出

上下文

模板上下文包括了很多变量,包括在模板渲染时手动传入变量、Flask默认传入的变量、在模板中定义的变量

在模板中定义变量
1 使用set标签在模板中定义变量

  1. {% set navigation=[('/','Home'), ('/about','about')] %}

2 使用set将一部分模板数据定义为变量

  1. {% set navigation %}
  2. #html内容
  3. {% endset %}

上下文变量
1 内置上下文变量

  • Flask在模板上下文提供了一些内置变量,可以在模板中直接使用
变量 说明
config 当前的配置对象
request 当前的请求对象,在已激活的请求环境下可用
session 当前的会话对象,在已激活的请求环境下可用
g 与请求绑定的全局变量,在已激活的请求环境下可用

2 自定义上下文

  • 如果多个模板都需要使用同一个变量,更好的办法是设置一个模板全局变量
    • Flask提供了一个app.context_processor装饰器,可以用来注册模板上下文处理函数,帮我们完成统一传入变量的工作。
    • 模板上下文函数需要返回一个包含变量键值对的字典。
    • 在调用render_template()函数渲染任意模板时,所有使用app.context_processoer装饰器注册的 模板上下文处理函数(包括Flask内置的)都会被执行。 这些函数的返回值可用被添加到模板中。
  1. @app.context_processor
  2. def inject():
  3. ...
  4. return {'foo':foo}
  5. #处理使用app.context_processor装饰器,也可用将其作为方法调用
  6. app.context_processor(lambda: {'foo':'haha'})

全局对象

  • 全局对象:在所有模板中都可用使用的对象

1 内置全局函数
jinja2在模板中默认提供了一些全局函数

函数 说明
range()
lipsum(n=5, html=True, min=20, max=100) 生成随机文本(lorem lipsum)
dict()

Flask在模板中提供的全局函数

函数 说明
url_for()
get_flash_messages()
  1. <a href="<url_for('index')>">点击</a>

2 自定义全局函数

  • 使用app.context_processor注册模板上下文处理函数来传入函数
  • 使用app.template_global装饰器直接将函数注册为模板全局函数:默认使用函数名称传入模板,也可用在装饰器内使用name参数指定一个名称。
  1. @app.template_global
  2. def bar():
  3. return 'i am bar'

过滤器

  • 在jinja2中, 过滤(filter)是可以用来修改和过滤变量值的特殊函数。
    • 过滤器和变量用一个竖线(管道符)隔开,需要参数的过滤器可以像函数一样使用括号传递。
  1. {{ name|title }} #title过滤器,将name标题值标题化
  2. {{ movie| length }} #length,获取变量长度
  3. #将过滤器作用于部分模板数据, 使用filter和endfilter标签声明开始结束
  4. {% filter upper %}
  5. This text becomes uppercase
  6. {% endfilter %}

内置过滤器

过滤器 说明
default(value, default_value=u”, boolead=False) 设置默认值,默认值作为参数传入,别名d
escape(s) 转义HTML文本,别名e
first(seq) 返回序列的第一个元素
last(seq)
length(object) 返回变量的长度
random(seq) 返回序列中的随机元素
safe(value) 将变量标记为安全,避免转义
trim(value) 清除变量值前后的空格
max(value, case_sensitive=False, attribute=None) 返回序列中的最大值
min
unique(value, case_sensitive=False, attribute=None) 返回序列中不重复的值
striptags(value) 清除变量内的HTML标签
urliz(value, trim_url_limit=None, nofollow=False, terget=None, rel=None) 将URL文本转换为可单击的HTML连接
wordcount(s) 计算单词数量
tojson(value,indent=None) 将变量转换为JSON格式
turncate(s, length=255, killwords=False, end=’…’, leeway=None) 结算字符串,常用于显示文章摘要,length设置截断的长度,killwords设置是否截断单词,end设置结尾的符号.
  • 列表中过滤器函数的第一个参数表示被过滤的变量值或字符串,即竖线符号左侧的值,其他参数可以通过添加括号传入
  • 过滤器可以叠加使用
  1. {{ name|default('Unknown')|title }}
  2. {{ name| length }}
  3. #默认jinja2会对模板内的变量进行转义, 如果想避免转义可使用safe过滤器
  4. {{ text|safe }}

自定义过滤器

  • 通过app.template_filter()装饰器可以注册自定义过滤器,
    • 默认使用函数名为过滤器名称,可以传入name参数设置。
    • 过滤器函数接收被处理的值为参数,返回处理后的值。
  • 也可以使用app.add_template_filter()方法注册自定义过滤器,传入函数对象和可选自定义名称
  1. from flask import Markup
  2. @app.template.filter()
  3. def musical(s):
  4. return s + Makrup('&#9835;')

测试器

  • 用来测试变量或表达式,返回布尔值(True、False)的特殊函数
  1. number测试判断变量是否是数字, 使用is连接变量和测试器
  2. {% if age is number %}
  3. {{ age*365}}
  4. {{ else }}
  5. 无效的数字
  6. {{ endif }}

内置测试器

测试器 说明
callable 判断对象是否可被调用
defined/ undefined 判断变量是否已定义
none 判断变量是否为None
number/string 判断变量是否是数字/字符串
sequence 判断变量是否是是序列,如字符串、列表、元组
iterable/mapping 判断变量是否可迭代/匹配对象(如字典)
sameas(value, other) 判断变量与other是否指向相同的内存地址
  • 在使用测试器时, is左侧是测试器接收的第一个参数, 其他参数通过括号传入, 也可以在右侧使用空格连接。
  1. {% if age is sameas(bar) %}
  2. 等同于
  3. {% if age is sameas bar %}

自定义测试器

  • 可以使用Flask提供的app.template_test()装饰器注册自定义测试器
    • 测试器函数接收需要被测试的值为参数,返回布尔值
  • 也可以使用app.add_template_test()方法注册测试器,传入函数对象和可选择的自定义名称
  1. @app.temlpate_test
  2. def baz(n):
  3. if n == 'baz':
  4. return True
  5. return False

模板环境变量

  • 在jinja2中,渲染行为由jinja2.Enviroment类控制,所有配置选项、上下文变量、全局函数、过滤器、测试器都存储在Enviroment示例上。
  • 当与Flask结合后,并不单独创建Environment对象,而是使用Flask创建的Environment对象,它存储在app.jinja_env属性上。
    • 可以使用app.jinja_env更改jinja2设置。
    • 模板环境中的全局函数、过滤器、测试器分别存储在Environment对象的globals、filters和tests属性中,这三个属性都属于字典对象;除了是装饰器和方法注册自定义函数,还可以直接操作这三个字典来添加相应的属性或变量,通过向对应的字典属性中添加一个键值对实现,在模板中使用的变量名称作为键,对应的函数对象或变量作为值。
  1. #1 添加自定义全局对象
  2. def bar():
  3. return 't am bar'
  4. app.jinja_env.globals['bar'] = bar
  5. # 2 自定义过滤器
  6. def smiling(s):
  7. return s+ ':)'
  8. app.jinja_env.filters['smiling'] = smiling
  9. # 3 添加自定义测试器
  10. ...
  11. app.jinja_env.tests['baz'] = baz

模板结构组织

通过组织模板内容,减少页面重复编写。(don’t repeat yourself)

局部模板

  • 局部模板仅包含部分代码,所以不会在视图函数中直接渲染它,而是插入到其他独立模板中。
  • 当多个独立模块中使用同一块HTML代码时,我们就可以把这部分代码抽离出来,存储到局部模板中。 即可以避免重复、也方便同一管理。 比如头部部分代码可以定义在局部模板_header.html中。 为了和普通模板区分开,局部模板命名一般以一个下划线开始。
  • 使用include插入一个局部模板,这回把局部模板全部内容插在使用include标签的位置。
  1. {% include '_header.html' %}

  • 宏(macro)是jinja2提供的类,似python中的函数;创建宏时,使用macro和endmacro标签声明宏的开始和结束,开始标签中定义宏的名称和接收的参数。
  • 使用宏可以把一部分模板代码封装到宏里,使用传递的参数来构建内容,最后返回构建的内容。
  • 便于管理,通常把宏存储在单独的文件中,通常命名为macros.html或_macros.html

macros.html 定义

  1. {% macro qux(amount=1) %}
  2. {% if amount==1 %}
  3. i am qux
  4. {% elif amount>1 %}
  5. we are quxs
  6. {% endif %}
  7. {% endmacro %}

使用宏

  1. {% from 'macros.html' import qux %}
  2. {% qux(amount=5) %}
  • 在jinja2中,处于性能考虑,默认情况下包含(include)一个局部模板会传递当前上下文到局部模板中;但导入(import)却不会。
    • 导入(一般为宏)只会包含jinja2和flask内置的全局函数和自定义全局函数/过滤器/测试器。不包含render_template()函数传入的变量、自定义模板上下文处理器传入的变量、扩展使用内置的模板上下文处理函数提供的变量。 因此如果在导入中需要使用这些变量,就需要在导入时显示的使用with context声明 传入当前模板的上下文
  1. {% from "macros.html" import qux with context %}

模板继承

  • jinja2支持定义一个基模板,把页面上的导航栏、页面等内容放在基模板中,而每个继承基模板的子模板都会在渲染时自动包含这些内容,在每个子模板中中只用重新定义不同内容的地方, 这样避免在多个模板中编写重复的内容。
  • 基模板存储页面的固定部分,通常命名为base.html或layout.html。 包含一个完整的html结构。
  • 子模板继承基模板后,会自动包含基模板的内容和结构。为了能够让子模板方便的覆盖或插入内容,需要在基模板中定义块(block),在子模板中可以通过定义同名的块来执行继承操作。
    • 块的开始和结束分别使用block和endblock标签声明,开始block中定义块名称; 块之间可以嵌套。

编写基模板

。。。

编写子模板

  • 因为基模板已经定义了HTML的基本结构,所以在子模板中不需要再定义这些内容,只需要继承后针对特定块进行修改。
  1. {% extends 'base.html' %} #使用extends标签声明扩展基模板, 且extends必须时子模块的第一个标签
  2. {% from 'macros.html' import qux %}
  3. {% block title %} 这个页面的标题{% endblock %} #1. 覆盖内容:同名块会覆盖block内容
  4. {% block styles%}
  5. {{ super() }} # 2 追加内容:使用jinja2提供的super()函数进行声明。
  6. ...
  7. {% endblock %}

进阶

空白控制

  • 在渲染实际输出的HTML文件中,模板中jinja2语句、表达式、注释会保留移除后的空行。
  1. 可以通过在定界符内存添加减号,实现在渲染后移除空格
  1. {% if True -%}
  2. hello
  3. {%- endif %}
  1. 还可以使用模板环境对象提供的trim_blocks(控制删除jinja2语句后的第一个空行)和lstrip_blocks(控制删除jinja语句所在行之前的空格和制表符)属性设置
  1. app.jinja_env.trim_blocks= True
  2. app.jinja_env.strip_blocks= True

加载静态文件

  • 在Flask程序中, 默认将静态文件存储在与主脚本(包含实例程序的脚本)同级目录的static文件夹中。
    • 如果想修改静态文件文件夹,可以在实例化Flask类时,使用static_folder参数指定。 静态文件的URL路径中的static也会随着文件夹名称变化,在实例化Flask类时使用static_url_path参数可以自定义静态文件的URL路径。
  • 在HTML中引用静态文件,需要使用url_for()函数获取静态文件的url。Flask内置了获取静态文件的视图函数,端点值为static,默认url规则为/static/, 变量filename是相对于static文件夹根目录的文件路径。

添加Favion

  • favicon.ico文件指的是Favicon/浏览器标签图标。
  1. <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">

使用CSS框架

  • 这里演示的使用bootstrap框架; 另外bootstrap所以来的jQuery和Popper.js需要单独下载。按照jQuery、Popper.js、Bootstrap顺序引入。
  • 通过css和js资源引入在基模板完成
  1. {% block styles %}
  2. <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
  3. {% endblock %}
  4. {% block scripts %}
  5. <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>
  6. <script src="{{ url_for('static', filename='js/popper.min.js') }}"></script>
  7. <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
  8. {% endblock %}

使用宏加载静态资源

  • 为了方便加载静态资源,可以创建一个专门加载静态资源的宏。

template.macros.html

  1. {% macro static_file(type, filename_or_url, local=True) %}
  2. {% if local %}
  3. {% set filename_or_url=url_for('static', filaneme=filename_or_url) %}
  4. {% endif %}
  5. {% if type='css' %}
  6. <link rel="stylesheet" href="{{ filename_or_url }}" type="text/css">
  7. {% elif type='js' %}
  8. <script type="text/javascript" src="filename_or_url"></script>
  9. {% elif type='icon' %}
  10. <link rel="icon" type="image/x-icon" href="{{ filename_or_url }}">
  11. {% endif %}
  12. {% endmacro %}

在模板中导入宏后调用使用
base.html

  1. {% from 'macros.html' import static_file %}
  2. static_file('js', 'js/jquery.min.js')
  3. 或从cdn加载
  4. static_file('js', 'www.cdn.XXX/js/bootstrap.min.js', local=False)

消息闪现

  • Flask提供一个flash()函数,用来“闪现”需要显示给用户的消息。 在视图函数中调用flash()函数实现.
    • 在视图函数中使用flash()发送的消息会存在session中(使用session为程序设置密钥:app.secret_key或SECRET_KEY环境变量)
    • 在模板中使用全局函数get_flashed_messages()函数获取消息并显示出来。

在视图函数闪现消息

  1. @app.route('/login')
  2. def login():
  3. ...
  4. flash('登陆成功')
  5. return ...

在模板渲染消息(一般可以定义在基模板中)

  1. <main>
  2. {% for message in get_flashed_messages() %}
  3. <div class="alert">{{ message }}</div>
  4. {% endfor %}
  5. </main>

自定义错误页

  • 错误处理函数和视图函数类型,返回值作为响应的主体。
  • 错误处理函数需要附加app.errorhander()装饰器,并传入错误码作为参数。 错误处理函数本身则需要接收异常类作为参数,并在返回值注明对应的HTTP状态码。
  1. from flask import Flask, render_template
  2. @app.errorhander(404)
  3. def page_not_found():
  4. return render_template('error/404.html'), 404

js和css中的jinja2

  • 有时候需要在javascript或css代码中使用jinja2提供的变量值或控制语句,如通过传入theme_color变量来设置页面的主图色彩。
    • ps:只有通过render_template()传入的模板文件才会被渲染,如果把jinja2代码单独写在js和css文件中,他们包含的jinja2代码永远不会被执行。因此需要通过下列方式。

1 行内/嵌入式js/css

  • 如果要在js和css文件中使用jinja2代码,就需要在html中使用定义这部分代码
  • 这种方式会使得html中存在大量js和css代码使变得难以维护,不推荐。 因此可以尽量将要使用的jinja2变量值在html模板中定义为js变量。

2 定义为js/css变量

  • 需要在js中获取的变量,通过html元素的data-属性存储。然后在js中使用DOM元素的dataset属性获取data-属性值(如element.dataset.username)或使用getAttribute()方法(如element.getAttribute(‘data-username’) 或在使用jQuery时使用jQuery对象调用data方法获取(如$element.data(‘username’))。
    • 在html中data-*被成为自定义数据属性,可以用它存储自定义数据供js获取。
  • 对于需要全局使用的数据,可以在页面中使用嵌入式js定义变量
  1. <script type="text/javascript">
  2. var foo='{{ foo }}'
  3. </script>
  • 当在js中插入了太多jinja2语法时,或许应该考虑将程序转变为Web API,可以专心使用js来编写客户端。
  • css同理,在html中将变量定义为css变量, 然后在css文件中使用var()函数并传入变量名即可获取对应的变量值。
  1. <style>
  2. :root {
  3. --theme-clolr: {{ theme_color }};
  4. --backgroud-url : {{ url_for('static', filename='my.jpg') }}
  5. }
  6. {/style}
  1. #foo {
  2. color: var(--theme-color);
  3. }
  4. #bar {
  5. background: var(--background-url);
  6. }