模板-Jinja2
Table of Contents generated with DocToc
What
Jinja2是一个Python模板语言,类似于PHP将动态的编程语言嵌入到静态的文本文件中,达到动态的生成HTML的能力。实际上,按照个人理解,所谓模板语言类似于完形填空,一个固定的静态文本框架加上一些根据环境条件动态生成的内容,组成一个完整的html。
根据官方文档,以及自己的理解,整理了以下的使用方法。
PS. 因为我Liquid编译markdown时,把{和%识别成了特殊字符。
本文所有代码块中的转义符\
在实际使用时都不需要,都不需要,都不需要。
How
Jinja2可以搭配Django使用,基本的Django项目搭建参考Django官网。
如何设计一个模板
如何设计一个HTML模板,最最最基本的模板实例:
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation">
\{\% for item in navigation \%\}
<li><a href="\{\{ item.href \}\}">\{\{ item.caption \}\}</a></li>
\{\% endfor \%\}
</ul>
<h1>My Webpage</h1>
\{\{ a_variable \}\}
\{# a comment #\}
</body>
</html>
如上例子,第8~10行表示一个动态的代码逻辑,根据输入来决定生成什么。\{\{ \}\}
表示一个动态的表达,他无法控制html标签的生成与否。但能输出一个特定的数据,比如要输出data中的username属性,可以使用\{\{ data.username \}\}
。\{# #\}
表示其中的为注释,可以跨行,不会对最终输出产生什么影响。行内注释可以直接使用#
和python一样。##
比#
更强大,也是注释,而且在最后渲染时,##
注释后的东西全部会被忽略,即不会输出到最后的结果中。
变量
\{\{ data.username \}\}
和\{\{ data['username'] \}\}
还是有区别的,在python中,直接用dot是对属性的引用,而中括号引用是对dict中的键值对的引用。Jinja2对此作了一点“美化”,Jinja2中这两个语法互为补充,不管是用点还是中括号,既可以对属性进行引用,也可以对键值对进行引用,唯一不同是,点会先获取属性,如果没有对应属性,再获取键值对。中括号的方式反之。
过滤器(Filters)
与其叫过滤器,我更愿意叫它为一个pipeline。
\{\{ username|striptags|capitalize \}\}
,这条语句表示将username
的值传递给striptags
,它会过滤掉html标签,其输出再传递给capitalize
方法,它会将输入的首字母大写,其后小写,整个表达式的输出即最后一步capitalize
的输出。
username
为数据源,其后的每一个stage都用|
分隔。
更多内建的stage方法参考官网:Built-in Filters
测试器(Tests)
测试器其实就是一个判断语句。
\{\% if loop.index is divisibleby(3) \%\}
其中divisibleby()
就是一个测试器函数,更多测试器函数参考:Built-in Tests
控制非可见字符 (Whitespace Control)
默认情况下(trim_blocks
,lstrip_blocks
都开启),一个Jinja2的模板语句会在其后面带上一个换行符。所以,Jinja2在渲染模板的时候,会帮我们去掉这个单独的多余的换行符以及多余的空格。比如:
<div>\n
\{\% if True \%\}\n
yay\n
\{\% endif \%\}\n
</div>\n
实际上:
<div>\n
\n
yay\n
\n
</div>\n
显然,这里多了两个换行符和其前面的空格,在最终渲染完成后,Jinja2会将其删除掉:
<div>\n
yay\n
</div>\n
而其他的换行符会保持不变。
Jinja2同时提供了一个语法糖:
\{\%- -\%\}
\{\%+ +\%\}
前者表示在渲染时,把此Block前面和后面的不可见字符都删除掉,而后者表示保留此Block前后的不可见字符。
比如:如果seq=[1,2,3,4,5,6]
\{\% for item in seq -\%\}
\{\{ item \}\}
\{\%- endfor \%\}
则渲染完成后其输出为:
123456
转义
Jinja2提供了两种形式来输出原始的字符,而不会被识别成模板语法:
-
\{\{ '\{\{' \}\}
-
块转义:
\{\% raw \%\} asdfasdfasdfasdfasdf\{\}\{\}\{\}\{\}\{\}\{\} \{\% endraw \%\}
这两个表达式中间的所有字符都会不会被Jinja2解析,而是按原样输出。
如何复用一个模板
模板继承
定义一个父模板:
<!DOCTYPE html>
<html lang="en">
<head>
\{\% block head \%\}
<link rel="stylesheet" href="style.css" />
<title>\{\% block title \%\}\{\% endblock \%\} - My Webpage</title>
\{\% endblock \%\}
</head>
<body>
<div id="content">\{\% block content \%\}\{\% endblock \%\}</div>
<div id="footer">
\{\% block footer \%\}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
\{\% endblock \%\}
</div>
</body>
</html>
继承父模板:
\{\% extends "base.html" \%\}
\{\% block title \%\}Index\{\% endblock \%\}
\{\% block head \%\}
\{\{ super() \}\}
<style type="text/css">
.important \{ color: #336699; \}
</style>
\{\% endblock \%\}
\{\% block content \%\}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
\{\% endblock \%\}
第一行声明继承自父模板 base.html,第二行表示名为title的Block会在父模板中被值Index
填充。第3~8行,首先会保留父模块的在名为head的Block中的内容,同时还会将css属性加入到此模块的最后。第9~14行,将会把子模板content block中定义的内容,填充到父模板同名的block中
模板引用
模板引用相较于模板继承更加直观,且容易理解。
比如在head.html
中定义了一个block:
<head>
<title>this is header</title>
</head>
当设计一个博客页面的时候,就可以直接引用这个header.html:
\{\% include 'header.html' \%\}
<body>
...
</body>
导入宏(Macro)
所谓宏,其实是一个抽象出来的模板方法,比如有一个form表单,这个表单可以单独用一个Block来定义,但是表单里的属性名对于不同页面可能不太一样,此时用Block来定义就不太方便了。可以用Macro来定义:
\{\% macro input(name, value='', type='text') -\%\}
<input type="\{\{ type \}\}" value="\{\{ value|e \}\}" name="\{\{ name \}\}">
\{\%- endmacro \%\}
假设这个定义在components.macro中(用macro而不用html后缀,是想把它和普通的html模板区分开,没有其他作用)。可以这样引用这个宏:
\{\% from 'components.macro' import input as input_field \%\}
<dl>
<dt>Username</dt>
<dd>\{\{ input_field('username') \}\}</dd>
<dt>Password</dt>
<dd>\{\{ input_field('password', type='password') \}\}</dd>
</dl>
very easy.
REFERENCES