模板-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_blockslstrip_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提供了两种形式来输出原始的字符,而不会被识别成模板语法:

  1. \{\{ '\{\{' \}\}

  2. 块转义:

    \{\% 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 \%\}
        &copy; 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