• jinja2 简单整理


    Jinja2

    本文全文都来自:http://docs.jinkan.org/docs/jinja2/index.html

    简介

    jinja2 是一个模仿 Django 模板语言而重新开发的模板引擎,因为Django模板引擎限制比较多,Django的开发者认为模板语言就应该干模板该做的事情,后端的事情放在后端处理,不应该在前端模板中写太多编程语言和处理逻辑,因此限制比较多。所以如果你比较熟悉Django模板,那么jinja2你也很快就能了解。

    安装

    pip install Jinja2
    

    基本使用

    from jinja2 import Template
    
    # 直接使用字符串,创建一个模板。模板包含了一个变量 {{ name }}
    temp = Template("<p>{{ name }}</p>")
    
    # 给模板传递变量,并渲染. 可以传递关键字参数,或者字典
    content = temp.render(name="Hello")
    # content = temp.render({"name": "Hello"})
    
    print(content)
    

    "{{ }}" 是用来在模板中声明变量的。模板自带了一个 render() 函数,可以给模板传递变量(context,也叫上下文变量),然后渲染它的内容。

    Template 对象

    模板对象, 就是我们上面用到的:

    from jinja2 import Template
    
    temp = Template("<p>{{ name }}</p>")
    content = temp.render(name="Hello")
    
    print(content)
    

    大模板文件,可以使用生成器,来减少内存压力:

    from jinja2 import Template
    
    temp = Template("{{ name }}")
    gen = temp.generate(name="Wang")  # 返回的是生成器
    print(gen)
    for g in gen:
    	print(g)  # 渲染后的内容
    

    常见的属性和方法:

    • globals

      该模板的全局变量字典。修改这个字典是不安全的,因为它可能与其它模板或 加载这个模板的环境共享。

    • name

      模板的加载名。如果模板从字符串加载,这个值为 None 。

    • filename

      模板在文件系统上的文件名,如果没有从文件系统加载,这个值为 None 。

    • render([context])

      渲染模板

    • generate([context])

      render 类似。如果一个模板文件很大,我们可以使用这个函数,来返回一个生成器缓解内存压力。然后迭代这个生成器,来获取渲染后的内容。

    • stream([context])

      generate() 一样,只不过返回 TemplateStream.

    TemplateStream 对象

    当我们对 Template 对象使用 .stream() ,就可以获取一个模板流。它有两个方法:

    • disable_buffering()

      禁用输出时缓存

    • dump(fp, encoding=None, errors='strict')

      将整个数据流保存到文件中:

      Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')

    Environment 对象

    除了上面我们介绍的直接使用 Template 对象,我们还可以使用 Environment 对象,来保存一些配置,譬如:

    from jinja2 import Environment, PackageLoader, Template
    
    # 声明一个 package 加载器,会自动去 temp 这个 python包下的 templates 文件夹找所有的文件,即:./temp/templates/*.*
    loader = PackageLoader("temp", "templates")
    env = Environment(loader=loader)  # 生成环境
    
    template = env.get_template("test.html")  # 加载某个文件,文件后缀随意,因为它只是一个文件,会从:./temp/templates/ 路径下查找这个 test.html
    
    print(template.render(name="ahha"))
    

    Environemt 的好处是,可以保存一些配置,以后所有使用这个环境所获取的模板,都会统一使用这个配置。

    我们可以通过 .get_template() 来获取某个模板,然后通过 .render(context) 来渲染模板。

    参数:

    • block_start_string
      • 声明 block 时的起始字符串,默认: '{%'.
    • block_end_string
      • 声明 block 时的结束字符串,默认: '%}'.
    • variable_start_string
      • 声明 变量 时的起始字符串,默认: '{{'.
    • variable_end_string
      • 声明 变量 时的结尾字符串,默认: '}}'.
    • comment_start_string
      • 声明 注释 时的起始字符串,默认: '{#'.
    • comment_end_string
      • 注释的结尾字符串: '#}'.
    • line_statement_prefix
      • 行语句的起始字符, 以此字符开头的行,会被当场语句执行 行语句.
    • line_comment_prefix
      • 注释的起始字符,如果以此字符开头,会被当成注释(上面我们说过默认的注释是{# #}): See also 行语句.
    • newline_sequence
      • 为序列开启新行。必须是 '\r', '\n' or '\r\n'. 默认: '\n'
    • extensions
      • 扩展插件
    • autoescape
      • 如果设置成 True,会自动转义一些特殊字符
    • loader
      • 模板加载器
    • auto_reload
      • 如果模板更改,自动重新加载模板(为了性能,最好设置成 False)

    Environment 对象的一些属性:

    • filters

      该环境的过滤器字典。只要没有加载过模板,添加新过滤器或删除旧的都是 安全的。自定义过滤器见 自定义过滤器 。有效的过滤器名称见 标识符的说明

    • tests

      该环境的测试函数字典。只要没有加载过模板,修改这个字典都是安全的。 自定义测试见 see 自定义测试 。有效的测试名见 标识符的说明

    • globals

      一个全局变量字典。这些变量在模板中总是可用。只要没有加载过模板,修 改这个字典都是安全的。更多细节见 全局命名空间 。有效的 对象名见 标识符的说明

    • add_extension(extension)

      给环境添加一个扩展

    • from_string(source, globals=None, template_class=None)

      从字符串加载一个模板。

    • get_or_select_template(template_name_or_list, parent=None, globals=None)

      如果给了一个可迭代的模板列表,会使用 select_template() ,否则会使用 get_template()

    • get_template(name, parent=None, globals=None)

      从加载器加载模板。如果 parent 参数不为空,则会拼接 parent 文件夹来获取模板的真实路径。

    • join_path(template, parent)

      连接模板和 parent路径

    • list_templates(extensions=None, filter_func=None)

      返回所有的模板列表。

    • select_template(names, parent=None, globals=None)

      get_template() 很像,但是会尝试多次获取模板.如果找不到模板会抛出 TemplatesNotFound

    Loader 加载器

    在环境对象这一小节中,我们用到了加载器,下面有几种不同的加载器:

    FileSystemLoader(pathes, encoding='utf-8')

    文件系统加载器,可以直接使用路径,或者路径列表作为参数,来加载这些路径下的所有模板文件:

    loader =  FileSystemLoader('./temp/templates')
    loader =  FileSystemLoader(['./temp/templates', './temp/others'])
    

    PackageLoader(package_name, package_path='templates', encoding='utf-8')

    python包加载器,会从python的包(带有__init__.py)中的 templates 文件夹下加载所有的模板:

    loader = PackageLoader('package1', 'templates')
    

    DictLoader(mapping)

    字典加载器,可以使用一个字典对象加载模板,字典的键是模板名,值是模板的文本字符串:

    from jinja2 import Environment, DictLoader
    
    loader = DictLoader({'index.html': 'source {{ name }} here'})
    env = Environment(loader=loader)
    template = env.get_template("index.html")
    
    print(template.render(name="TEST"))
    

    PrefixLoader(mapping, delimiter='/')

    一个前缀加载器,接收一个字典,字典的键是前缀,字典的值是一个加载器,之后就可以使用 前缀+delimiter+模板名 来加载模板:

    from jinja2 import Environment, PackageLoader, PrefixLoader
    
    
    loader = PrefixLoader({
    	'app1': PackageLoader('temp', "templates")
    }, delimiter="&")
    
    env = Environment(loader=loader)
    
    # 直接使用 app1 + delimiter + 模板名 就可以找到模板
    template = env.get_template("app1&test.html")
    
    print(template.render(name="TEST"))
    

    ChoiceLoader(loaders)

    一个可选加载器,接收一个加载器列表。如果第一个加载器找不到相应的模板,则会从第二个加载器开始找,并以此类推 ...

    from jinja2 import Environment, ChoiceLoader, FileSystemLoader
    
    loader = ChoiceLoader([
        FileSystemLoader('./'),  # 这个路径下没有模板
        FileSystemLoader('./temp/templates')  # 这个路径有模板:test.html
     ])
    
    env = Environment(loader=loader)
    
    template = env.get_template("test.html")  # 依然能找到
    
    print(template.render(name="TEST"))
    
    

    转义

    为了安全起见,所有用户输入的文本,都应该进行转义,因为用户可能输入不安全的 html 字符,从而进行 跨站脚本 攻击(Cross Site Scripting)

    from markupsafe import Markup
    
    s = "<p>Hello world!</p>"   # html 字符串
    m = Markup(s)               # markup 对象
    
    t = m.striptags()  # 清除标签,只剩下文本
    e = Markup.escape(s)  # 转义特殊字符
    o = e.unescape()  # 特殊字符重新转义回文本
    print(
    	m,  # <p>Hello world!</p>
    	t,  # Hello world!
    	e,  # &lt;p&gt;Hello world!&lt;/p&gt;
    	o,  # <p>Hello world!</p>
    
    	sep='\n'
    )
    

    过滤器

    过滤器就是python函数,只不过它可以用特殊的方式,在模板中使用,渲染模板的时候,会自动执行这个函数。

    过滤器的语法糖是:|

    譬如:

    <p>
        {{ 40|addFilter(30) }}
    </p>
    

    假设我们有一个叫做 addFilter 的特殊过滤器函数,上面的代码会调用 addFilter(40, 30) 然后将返回值渲染到页面上。

    from jinja2 import Environment
    
    
    # 一个普通函数
    def addFilter(x, y):
    	return x + y
    
    
    env = Environment()
    env.filters['addFilter'] = addFilter  # 添加一个过滤器
    
    con = env.from_string("""
    {{ 40|addFilter(30) }}
    """).render()
    print(con)
    

    测试

    所谓测试,其实就是判断语句,比如 python:

    x = 2
    if x == 2:
        return True
    else:
        return False
    

    jinja2 示例:

    from jinja2 import Environment, FileSystemLoader, Template
    from jinja2.nodes import EvalContext
    
    # 一个普通的函数
    def is_odd(n):
    	if n % 2 == 0:
    		return False
    	else:
    		return True
    
    
    env = Environment()
    # 给环境添加一个自定义的测试
    env.tests["odd"] = is_odd
    
    # 从字符串加载一个模板
    temp = env.from_string("""
    	<p>
    		{% if 3 is odd %}
    			<span>3 is odd</span>
    		{% else %}
    			<span>3 is not odd</span>
    		{% endif %}
    	</p>
    """)
    
    # 渲染模板,返回内容
    content = temp.render()
    print(content)
    

    模板语法

    注释

    在模板中,注释使用 {# ... #} 表示:

    <p>
        {# this is comment,
            and this is comment too.
        #}
    </p>
    

    变量

    变量在模板中的语法,用 {{}} 括起来:

    <p>
        {{ name }}  <!-- name 就是一个变量 -->
        {{ obj.name }}  <!-- 提取 obj 对象的属性 -->
        {{ obj["name"] }}  <!-- 和 obj.name 等效 -->
    </p>
    

    针对上述模板,我们后台如下:

    from jinja2 import Template
    
    class Obj:
    	name = "wang"
    
    temp = Template("""<p>
        {{ name }}  <!-- name 就是一个变量 -->
        {{ obj.name }}  <!-- 提取 obj 对象的属性 -->
        {{ obj["name"] }}  <!-- 和 obj.name 等效 -->
    </p>""")
    
    gen = temp.render(obj=Obj(), name="Fake")
    print(gen)
    

    可以看出,我们可以像是使用普通的 python 语法一样,在模板中提取属性或者字典的值

    消除空白

    jinja2 会严格按照模板渲染,也就是说,如果你的模板中写入了空格,或者在标签之间换行了,渲染的内容也会原封不动的换行:

    譬如:

    from jinja2 import Template
    
    class Obj:
    	name = "wang"
    
    temp = Template("""<p>         {# <p>后面有个换行符 #}
        {{ name }} {{ obj.name }}  {# 这两个变量会在一行  #}
        {{ obj["name"] }}          {# 后面也有换行符 #}
    </p>""")
    
    gen = temp.render(obj=Obj(), name="Fake")
    print(gen)
    

    会渲染成:

    <p>
        Fake wang
        wang
    </p>
    

    如果你想将三个变量和标签都显示在一行,只能这样:

    temp = Template("""<p>{{ name }}{{ obj.name }}{{ obj["name"] }}</p>""")
    

    会渲染成:

    <p>Fakewangwang</p>
    

    如果我们想要在模板中好看(模板中换行),但是实际渲染的效果要在一行,可以使用 - 符号。

    譬如:

    temp = Template("""<p>
    	{{- name -}}
     	{{- obj.name -}} 
    	{{- obj["name"] -}}
    </p>""")
    

    会渲染成:

    <p>Fakewangwang</p>
    

    要点1:- 可以不成对出现

    要点2:-{{}} 之间没有空格

    要点3:{{- 代表消除变量之前的空白符,-}} 代表消除变量之后的空白符。

    要点4:- 不仅可以用在 {{ .. }} 上,也可以用在 {% .. %}

    转义自身语法

    如果你想要转义 {{ 本身,可以使用:

    {{ '{{' }}
    

    对于较大的段落,可以使用 raw 来将里面的内容全部当作原生字符

    {% raw %}
        <ul>
        {% for item in seq %}
            <li>{{ item }}</li>
        {% endfor %}
        </ul>
    {% endraw %}
    

    行语句

    我们之前曾经提到过行语句。其实就是自定义一个符号,然后在模板中,所有以这个符号开头的字符串,都会被当作语句来执行,譬如:

    from jinja2 import Template
    
    
    s = """
    <p>
    	# for i in [
    		'a', 
    		'b',
    		'c'
    	]:
    		{{ i }}
    	# endfor
    </p>
    """
    temp = Template(s, line_statement_prefix="#")  # 以 # 作为语句定义符号
    
    gen = temp.render()
    print(gen)
    

    上面的以'#'开头的行,会作为语句执行,并且语句结尾可以加冒号,并且如果遇到[],()等,可以换行

    模板 block

    编程语言有 继承 的概念,模板也可以有。我们可以写一个基本模板,然后让子模板继承这个模板

    基本模板: mother.html

    {% block title %}           {# 声明一个名为 title 的block #}
        <p>This is title</p>
        {% block content %}     {# title 内部嵌套了一个名为 content 的block #}
        {% endblock %}
    {% endblock %}
    
    {% block foot %}
        <span>This is foot</span>
    {% endblock %}
    

    上面我们编写了一个母版,它里面定义了很多的 block, 每个 block 都有自己的名字(block的名字不能重复): {% block blok_name %}...{% endblock %} ,在 block 中,我们可以写入一些 html 代码,让子模板继承。

    各个 block 之间是可以嵌套的

    注意每个 block 要有一个 {% endblock %}

    子模板:son.html

    {% extends "mother.html" %}             {# 继承母版 #}
    
    {% block content %}                     {# 重写某个block #}
        <span>This is content, and the mother.html doesn't have this.</span>
    {% endblock %}
    
    {% block foot %}
        {{ super() }}                       {# 继承母版中的 foot block 的内容 #}
        <span>New foot content</span>
    {% endblock %}
    

    {% extend %} 非常关键:它告诉模板要继承另一个模板。并且这个标签要放在模板的最上面。

    当然,继承的标签可以写路径: {% extends "layout/default.html" %}

    如果子模板没有重写母版中的某个block,则会默认使用母版中的block。

    命名 block 的结束标签

    针对一个block,我们还可以在 endblock 时写上它的名字,当然像上面的例子一样不写也行。

    {% block sidebar %}
    {% endblock sidebar %}
    

    块作用域

    一个 block 的内容,无法和block外部的内容互动,它有自己的作用域。譬如,你想在一个 for 循环中循环某个block,而block却无法获取for循环的作用域:

    {% extends "mother.html" %}             {# 继承母版 #}
    
    {% for i in [1,2,3] %}
        {% block foot scoped %}  			{# 后面加了一个 scoped, 就可以获取 for 循环中的变量了 #}
            {{ i }}
        {% endblock %}
    {% endfor %}
    

    转义字符串

    from jinja2 import Template
    
    
    temp = Template("""
    {{ value|safe }}    {# safe 过滤器,不转义字符 :<script>test</script> #}
    {{ value|e }}		{# e 过滤器,转义字符 :&lt;script&gt;test&lt;/script&gt; #}
    """)
    
    x = temp.render(value="<script>test</script>")
    print(x)
    

    当然,你也可以自动转义:

    {% autoescape true %}
    	自动转义在这块文本中是开启的。
    {% endautoescape %}
    
    {% autoescape false %}
    	自动转义在这块文本中是关闭的。
    {% endautoescape %}
    

    控制结构

    For

    for 可以用来遍历序列,如列表,字典等。

    {% for item in items %}
    	{{ item }}
    {% endfor %}
    
    
    {% for key, value in my_dict.items() %}
    	{{ key }}
    {% endfor %}
    
    
    {% for i in [0, 1, 2] if not i %}
    	{{ i }}
    {% else %}							{# else 会在 for 循环没有成功执行的情况下执行 #}
    	<span>List is empty</span>
    {% endfor %}
    

    查看循环的索引(循环到第几个元素了)

    from jinja2 import Template
    
    
    temp = Template("""
    {% for key, value in my_dict.items() %}
    	{{ key }}
    	{{ loop.index }}    {# loop 是一个特殊对象,可以获取当前循环的索引位置 #}
    {% endfor %}
    """)
    
    x = temp.render(my_dict={"a":"b", "b":"a"})
    print(x)
    

    loop 的几个特殊属性:

    变量 描述
    loop.index 当前循环迭代的次数(从 1 开始)
    loop.index0 当前循环迭代的次数(从 0 开始)
    loop.revindex 到循环结束需要迭代的次数(从 1 开始)
    loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
    loop.first 如果是第一次迭代,为 True 。
    loop.last 如果是最后一次迭代,为 True 。
    loop.length 序列中的项目数。
    loop.cycle 在一串序列间期取值的辅助函数。见下面的解释。
    from jinja2 import Template
    
    
    temp = Template("""
    {% for i in [1,2,3] %}
    	{{ loop.cycle('A', "C", "B") }}  {# loop.cycle会循环执行里面的A-C-B #}
    {% endfor %}
    """)
    
    x = temp.render()
    print(x)
    

    If

    和python中的if一样:

    {% if x %}
    	...
    {% elif y %}
    	...
    {% else %}
    	...
    {% endif %}
    

    跳出循环

    continue, break

    import jinja2.ext
    from jinja2 import Template
    
    
    temp = Template("""
    {% for i in [1,2,3] %}
    	{% if i == 1 %}
    		{{ i }}
    		{% continue %}
    	{% else %}
    		{% break %}
    	{% endif %}
    {% endfor %}
    """, extensions=[jinja2.ext.loopcontrols])  # 要额外加载一个扩展,才能使用 continue 和 break
    
    x = temp.render()
    print(x)
    

    宏类似于函数。我们可以定义一个宏,然后定义宏的内容。以后我们可以像调用函数一样调用宏。

    {# 声明了一个名为 input 的宏,它还带有几个参数 #}
    {% macro input(name, value='', type='text', size=20) -%}
        <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}">
    {%- endmacro %}
    
    {# 调用宏,并传参 #}
    <p>{{ input('username') }}</p>
    <p>{{ input('password', type='password') }}</p>
    

    include

    include 可以直接将另一个模板包含进当前模板,相当于将另一个模板直接嵌套进来。

    {% include 'header.html' %}
    
    {% include "sidebar.html" ignore missing %}  			{# ignore missing:如果找不到模板,可以忽略 #}
    
    {% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}  {# 可以使用列表 #}
    
    {% include "sidebar.html" ignore missing without context %}            {# without context 可以不携带上下文 #}
    
    {% include "sidebar.html" ignore missing with context %}            {# with context 可以携带上下文 #}
    

    什么是上下文:

    上下文其实就是模板中定义的变量,我们渲染时会将上下文传递给模板:template.render(context) ,而我们嵌套其他模板时,也可以将它们中的上下文包含进来,这样在当前模板中也可以使用被嵌套模板中的上下文。

    导入

    假设现有:forms.html

    {% macro input(name, value='', type='text') -%}
        <input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
    {%- endmacro %}
    
    {%- macro textarea(name, value='', rows=10, cols=40) -%}
        <textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols
            }}">{{ value|e }}</textarea>
    {%- endmacro %}
    

    我们可以像导入模块一样导入它:

    {% import 'forms.html' as forms %}   {# 导入 #}
    {% from 'forms.html' import input as input_field, textarea %}  {# 也可使用 from .. import .. as . #}
    <dl>
        <dt>Username</dt>
        <dd>{{ forms.input('username') }}</dd>
        <dt>Password</dt>
        <dd>{{ forms.input('password', type='password') }}</dd>
    </dl>
    <p>{{ forms.textarea('comment') }}</p>
    

    还可以导入时带入上下文:

    {% from 'forms.html' import input with context %}
    

    表达式

    在模板中,可以正常使用python中常见的表达式:

    数学计算:

    +
    -
    *
    /
    //
    %
    

    字面量:

    dict
    list
    tuple
    str
    
    true
    false
    

    比较运算:

    ==
    >=
    <=
    !=
    >
    <
    

    逻辑运算:

    and
    or
    not
    
    
    is
    in
    |    # 过滤器
    ()   # 调用函数
    ./[] # 用来获取对象的属性
    

    内置过滤器和测试

    见:http://docs.jinkan.org/docs/jinja2/templates.html#id11

  • 相关阅读:
    sort函数详解
    C++重载运算符的规则详解
    poj3061-subsequence
    员工管理系统 :练习
    Jdbc 模拟登陆系统的练习
    有关String 的常用方法
    浅谈希尔排序-----摘录
    简单选择排序
    typedef 和define 的区别
    写博客的理由
  • 原文地址:https://www.cnblogs.com/wztshine/p/16054582.html
Copyright © 2020-2023  润新知