概览 官方文档
Jinja 模板只是一个文本文件,可以 基于模板生成任何基于文本的格式(HTML、XML、CSV、LaTeX 等),一般用在前端的项目中,渲染 HTML 文件。
作为网络工程师,可以将其用来批量生成网络设备的配置。
或者其他需要批量生成文本的场景中。
模板包含变量 或表达式 ,这两者在模板求值的时候会被替换为值。模板中还有标签 ,控制模板的逻辑。模板语法的大量灵感来自于 Django 和 Python。
基本语法:
语句 {% ... %}
变量 {{ ... }}
注释 {# ... #}
常用的语句包括:for、if、set、include、block、filter 等
变量通过传递字典来进行使用,当使用 for 语句的时候,变量可以是列表。
基本用法 创建和渲染模板的最基本方法是通过 Template
,通过创建一个 Template
的实例, 会得到一个新的模板对象,模板对象有一个 render()
的方法,该方法在调用 dict 或 keywords 参数时填充模板。
1 2 3 4 5 6 7 from jinja2 import Templatetmpl = Template('hello {{ name}}' ) result = tmpl.render(name = '111' ) print (result)hello 111
一般用法 一般情况下,我们使用 *.j2
文件作为模板文件,用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from jinja2 import Template, FileSystemLoader, Environmentj2_loader = FileSystemLoader('./' ) env = Environment(loader=j2_loader) j2_tmpl = env.get_template('./jinja2.j2' ) result = j2_tmpl.render(name="xdai" ) print (result)
模板 jinja2.j2
的内容如下
1 2 This is a Jinja2 template file created by {{ name }}.
最终打印结果如下,传入的 name 被赋值给了模板文件,并输出了最终结果:
1 This is a Jinja2 template file created by xdai.
for 语句的使用 现在有如模板文件如下:
1 2 3 4 # cat jinja2_for.j2 {% for name in names %} Hello {{ name }}. {% endfor %}
通过传入一个列表来批量生成最终的结果。
1 2 3 4 5 6 7 8 from jinja2 import Template, FileSystemLoader, Environmentj2_loader = FileSystemLoader('./' ) env = Environment(loader=j2_loader) j2_tmpl = env.get_template('./jinja2_for.j2' ) names = ['Zhang San' , 'Li Si' , 'Wang Wu' ] result = j2_tmpl.render(names=names) print (result)
打印结果如下:
1 2 3 4 5 6 7 Hello Zhang San. Hello Li Si. Hello Wang Wu.
通过 for 循环,批量生成了 Hello。
还可以传入列表,列表的值是一个字典,然后通过 for 和字典的 .
或者 []
进行取值(和 Python 语法一致),将上面的模板文件改动如下:
1 2 3 4 # cat jinja2_for_with_dict.j2 {% for person in people %} My name is {{ person.name }}, and I am {{ person['age'] }} years old. {% endfor %}
然后,编写代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 from jinja2 import Template, FileSystemLoader, Environmentj2_loader = FileSystemLoader('./' ) env = Environment(loader=j2_loader) j2_tmpl = env.get_template('./jinja2_for_with_dict.j2' ) people = [ {'name' :'Zhang San' , 'age' : 18 }, {'name' :'Li Si' , 'age' : 20 }, {'name' :'Wang Wu' , 'age' : 22 }, ] result = j2_tmpl.render(names=names) print (result)
打印结果如下:
1 2 3 4 5 6 7 My name is Zhang San, and I am 18 years old. My name is Li Si, and I am 20 years old. My name is Wang Wu, and I am 22 years old.
if 语句的使用 if 语句用来判断,当条件成立时,对语句块文件的内容进行渲染,条件判定失败后则跳过该语句块。
将上文中的 for 循环模板进行修改如下,年龄大于 18 的进行自我介绍。
1 2 3 4 5 6 # cat jinja2_if.j2 {% for person in people %} {% if person.age > 18 %} My name is {{ person.name }}, and I am {{ person['age'] }} years old. {% endif %} {% endfor %}
代码与上面 for 语句的例子中相同,仅修改了加载模板的名字,打印结果如下:
1 2 3 4 5 6 7 8 My name is Li Si, and I am 20 years old. My name is Wang Wu, and I am 22 years old.
可以看到,age 小于 18 的,没有输出相应的文字。
Template Inheritance in Jinja(继承) 在为网络设备配置创建更大、功能更多的模板文件时,可以使用模板继承,将模板分解开来。 比如,创建 VLAN、interface 、OSPF 等各自独立的模板,这样灵活性更高,模板继承可以将这些独立的模板文件连接在一起。
使用 include
语句可以将一个模板完全插入到另一个模板中:
1 {% include 'vlans.j2' %}
另一种继承的方法是使用 bolck
语句,子模板引用父模板后,可以指定其中的某些部分,如果子模板中不存在,则使用父模板中的值。父模板也可以将 bolck
留空,子模板直接填充内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 This is father template. {% block test %} This is line can be replaced. {% endblock %} This is father template. {% extends 'father.j2' %} {% block test %} This is children template. {% endblock %} This is father template. This is children template. This is father template.
filter 语句的使用 filter 语句的作用就是对模板数据块的内容进行格式化修改。 例如:大小写转换、统计长度、计算绝对值等等,内置支持的过滤器可参见官网
来看个例子:
1 2 3 4 # cat jinja2_filter.j2 {% for person in people %} My name is {{ person.name | upper() }}, and I am {{ person['age'] }} years old. {% endfor %}
执行代码不再赘述,打印结果如下:
1 2 3 4 5 6 7 My name is ZHANG SAN, and I am 18 years old. My name is LI SI, and I am 20 years old. My name is WANG WU, and I am 22 years old.
Jinja2 Custom Filters 内置的 filter 可能会不满足自己的需要,这时就需要进行自定义了。 自定义筛选器只是常规 Python 函数,将筛选器的左侧作为第一个参数,并将参数作为额外的参数或关键字参数传递给筛选器。
可以将自己写的函数、导入的模块等更新到环境中,进而在 Jinja2 中使用。
Jinja2 的默认 filter 是一个字典,查看方式:Environment.filters
使用方法:通过更新 environment 中的 filters 字典,在模板环境中注册。
1 2 env = Environment(loader=FileSystemLoader('./templates/' )) env.filters['datetimeformat' ] = datetimeformat
在模板中使用:
1 2 3 {{ article.pub_date|datetimeformat }}
自定义过滤器示例 将 ipaddress.IPv4Interface
作为过滤器,来实现将传入的 IP 地址变量进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 def custom_ipaddr (ipaddress,operation ): from ipaddress import IPv4Interface ipadd = IPv4Interface(ipaddress) attr = getattr (ipadd,operation) return attr env = Environment(loader=FileSystemLoader('./roles/cmw7/templates/' )) env.filters['custom_ipaddr' ] = custom_ipaddr {{ 192.168 .1 .1 /24 | custom_ipaddr('netmask' ) }}
Macro(宏:相当于函数) 可以自己编写一个函数,然后在模板中调用
1 2 3 4 {% macro testfunc() %} test string {% endmacro %}
空白行的处理 通过上面的实践,你可能会发现,生成的结果中,有很多空行,默认情况下,jinja2 会给渲染后的结果加上空行。
如果需要去除模板中的空白,如 For 语句前后、变量表达式的开头或结尾添加减号 (-),就会删除这个语句块之前或之后的空格。
以上文中的 for 循环为例,修改模板如下:
1 2 3 4 # cat jinja2.j2 {% for name in names -%} Hello {{ name }}. {%- endfor %}
注意上面的 -
符号,放在 for 的结尾,是删除语句块之前的空行,放在 endfor 之前,是删除语句块之后的空行,如果前后的空行都删除了,则内容会在同一行,所以,上面模板渲染之后的输出为:
1 Hello Zhang San.Hello Li Si.Hello Wang Wu.
具体请根据自己的需求来进行空白行的控制。
实际应用 实际应用过程中,由于 jinja2 的模板需要传入字典、列表等,为了方便维护,我们可以使用单独的变量文件来存储这些东西,例如 YAML、JSON 等,一般使用 YAML ,因为简洁易读。
下面通过一个生成交换机配置的文件,来对 jinja2 模板语法进行综合使用。 模板文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # cat l2_interface.j2 {% for iface in l2_interface -%} # interface {{ iface.iface_name }} {% if iface.desc != None -%} description {{ iface.desc }} {% endif -%} {% if iface.link_type == 'trunk' -%} port link-type trunk undo port trunk permit vlan 1 port trunk permit vlan {{ iface.allow_vlan }} {% else -%} port link-type access port access vlan {{ iface.allow_vlan }} {% endif -%} {% endfor %}
变量文件如下:
1 2 3 4 5 6 7 8 9 10 11 --- l2_interface: - iface_name: g1/0/1 desc: jinja2_render_config link_type: access allow_vlan: 10 - iface_name: g1/0/2 desc: link_type: trunk allow_vlan: 10 20 30
代码如下:
1 2 3 4 5 6 7 8 9 10 11 import yamlfrom jinja2 import Template, FileSystemLoader, Environmentj2_loader = FileSystemLoader('./' ) env = Environment(loader=j2_loader) j2_tmpl = env.get_template('./l2_interfaces.j2' ) with open ('l2_interfaces.yaml' ) as f: vars = yaml.safe_load(f.read()) result = j2_tmpl.render(vars ) print (result)
最终生成了以下配置文件:
1 2 3 4 5 6 7 8 9 10 # interface g1/0/1 description jinja2_render_config port link-type access port access vlan 10 # interface g1/0/2 port link-type trunk undo port trunk permit vlan 1 port trunk permit vlan 10 20 30
下面是一个我自己写的案例,供参考。 Ansible Role:根据 Jinja2 生成 H3C Comware V7 配置
总结 在实际场景中,可以将固化配置做成模板文件,生成配置时,只要传入指定的参数,就可生成对应的配置,降低配置编写复杂度的同时,也降低了出错的概率。