参考文章:[Python web开发实战]
模板
在之前的章节中,视图函数直接返回文本,而在实际生产环境中其实很少这样用,因为实际的页面大多是带有样式和复杂逻辑的 HTML 代码,这可以让浏览器渲染出非常漂亮和复杂的效果。页面的内容应该是可重用的,而且需要执行更高级的功能。首先看一下 Python 自带的模板 string.Template:
1 | In [1]: from string import Template |
自带的模板提供的功能大抵如此,支持很有限;不能写控制语句,无法继承重用。这对于 web 开发来说远远不够,需要使用第三方的模板系统。目前市面上有非常多的模板系统,其中最知名的就是 Jinja2 和 Mako。
Jinja2
Jinja 是日本寺庙的意思,并且寺庙的英文 temple 和 template 的发音类似。Jinja2 是 Flask 默认的仿 Django 模板的一个模板引擎,由 Flask 的作者开发。它速度快,被广泛使用,并且提供了可选的沙箱模板来保证执行环境的安全。它有如下优点:
- 当 HTML 设计者和后端 Python 开发工作分离
- 减少使用 Python 的复杂程度,页面逻辑应该独立于业务逻辑,这样才能开发出易于维护的程序;
- 模板非常灵活,快速和安全,对于设计者和开发者会更友好;
- 提供了控制语句,继承等高级功能,减少开发的复杂度。
Jinja2 是 Flask 的一个依赖,因为我们之前安装了 Flask,所以 Jinja2 也随之安装了。否则可以单独安装:
1 | pip install Jinja2 |
Jinja2 从 2.7 开始已经依赖 MarkupSafe 了,MarkupSafe 的 C 实现要快的多,使用 pip 安装 Jinja2 时会自动安装它。如果不方便升级到新版本的 Jinja2,但当前版本大于 2.5.1,则可以手动安装 MarkupSafe。
API 的基本使用方式
Jinja2 通过 Template 类创建并渲染模板:
1 | In [9]: from jinja2 import Template |
是不是和 string.Template 做的事情很像呢?上面的代码片段背后的逻辑大致是这样的:
1 | from jinja2 import Environment |
Environment 的实例用于存储配置和全局对象,然后从文件系统或其他位置加载模板:
1 | > echo "Hello {{ name }}" > templates/chapter3/section2/jinja2/hello.html |
通过 Environment 创建了一个模板环境,模板加载器(loader)会在 templates 文件夹下寻找模板。因为这里是测试,所以只是用到 app.py 这个空文件作为包名。由于模板文件在模板目录的子目录下,也可以这样获取:
1 | env = Environment(loader=PackageLoader('app', 'templates')) |
使用模板加载器的另一个明显的好处就是可以支持模板继承。
Jinja2 的基本语法
模板仅仅是文本文件,它可以使用任何基于文本的格式(HTML,XML,CSV,LaTeX等),它并没有特定的扩展名,通常使用 .html 作为后缀名。模板包含 “变量” 或 “表达式”,这两者在模板求值的时候会被替换为值。模板中还有标签和控制语句。
下面是一个简单的模板(simple.html)
1 |
|
我们来解析以下这个模板的语法:
- 第一行,声明文档类型是 HTML5;
- 第 7,9,10 行,这是三种分隔符,每种分隔符都包含开始标记和结束标记;
- {# .... #} 模板注释。它不会出现在渲染的页面里;
- {% .... %} 用于执行诸如 for 循环或赋值的语句;
- {{ .... }} 用于把表达式的结果输出到模板上。
- 第 9 行,出现了 “for” 循环这种控制结构。语法是
{% for X in Y %} ... {% endfor %}
,控制语句都需要以 endxxx 作为结束。 - 第 10 行,应用把变量传递到模板,可以使用点 (.) 来访问变量的属性,也可以使用中括号语法([])。下面这两行的效果几乎是一样的:
1
2{{ item.href }}
{{ item['href'] }} - 第 14 行,trim 是一个过滤器,在模板中通过管道符号(|)把变量和过滤器分开。也可以使用多个过滤器,如
{{ title | trim | striptags }}
,striptags 也是一个过滤器,Jinja2 内置了非常多的过滤器,全部过滤器可以在http://bit.ly/29RZ1fk
找到,一定要熟悉这些过滤器,他们大多都很常用。
模板继承
合理使用模板继承,让模板能重用,能提高工作效率和代码质量。
首先定义一个基础的 “骨架” 模板(base.html)
1 |
|
这个模板有如下细节:
- ... 是一个代码块,可以在子模板重载。
- head 的代码块有默认内容,而 content 和 footer 都是没有内容的,这三个块需要在子模板中被重载,就用这个基类模板的定义显示默认内容。
接着看子模板(index.html)
1 | {% extends "base.html" %} |
子模板有如下细节:
- index.html 继承了 base.html 里面的内容。
extends
标签应该在模板中一开始就使用。 - 标题被种子啊,换成了 “Index”。
- head 块被重载,但是首先使用了 super(), 表示先使用 base.html 的 head 块的内容,再基于此添加 CSS 样式。
- content 块被重载,其中在块的结束标签中加入了名称,这样可以改善模板的可读性。
- footer 没有被重载,什么都不显示。
如果你想多次使用一个块,可以使用特殊的 “self” 变量并条用与块同名的函数:
1 | <title>{% block title %}{% endblock %}</title> |
宏
宏类似常规编程语言中的函数。它用于把常用行为抽象成可重用的函数:
1 | In [12]: Template(""" |
可以像函数一样调用宏
赋值
在代码块中,你也可以为变量赋值。赋值使用 set 标签,并且可以为多个变量赋值
1 | In [13]: print(Template(""" |
include
include 语句用于包含一个模板,渲染时会在 include 语句的对应位置添加被包含的模板内容:
1 | {% include 'header.html' %} |
include 可以使用 “ignore missing” 标记,如果模板不存在,Jinja 会忽略这条语句:
1 | {% include 'sidebar.html' ignore missing %} |
import
Jinja2 支持在不同的模板中导入宏并继续使用,与 Python 中的 import 语句类似。有两种方式来导入模板:可以把整个模板导入到一个变量(import xx) 或从其中导入特定的宏 (from xx import yy)。
现在有一个宏模板 (macro.html)
1 | {% macro hello(name) %} |
引用并调用宏的方法如下(hello_macro.html)
1 | {% import 'macro.html' as macro %} |
看一下 渲染效果
1 | In [14]: from jinja2 import FileSystemLoader, Environment |