flask中jinja2模板引擎详解– 块 (Block)和宏 (Macro)

 

flask 考虑到模板代码的重用性,Jiaja2提供了块 (Block)和宏 (Macro)来提高代码的继承和复用性。其中块 (Block)的使用可以极大精简代码,可以通过继承(extend)扩展让大量代码重复使用,并在Block自由定制替换内容块;而宏 (Macro) 的使用更可以极大的提高模板的复用性,减少复杂度,类似于函数,可以传入参数。这篇我们就来学习下 块 (Block)和宏 (Macro)的用法。

块 (Block)

关于模板的继承,我们可以在子模板的顶部使用如”{% extend ‘base.html’ %}”语句来声明继承。而子模板中由”{% block block_name %}”和”{% endblock %}”所包括的语句块,将会替换父模板中同样由”{% block block_name %}”和”{% endblock %}”所包括的语句块。

这就是块的功能,模板语句的替换。这里要注意几个点:

  • 模板不支持多继承,也就是子模板中定义的块,不可能同时替换两个父模板的块。
  • 模板中不能定义同名的块,因为这样无法确认替换块的内容。

建议在”endblock”关键字后也加上块名,比如”{% endblock block_name %}”,增加可读性。

使用父模板块 (Block)的内容

如果子模板想使用父模板中的块里的内容,请使用{{ super() }}。请看下面例子:





    {% block head %}

    

    {% block title %}{% endblock %}

    {% endblock %}





    
    {% block body %}     {% endblock %}     

上面是父模板,我要在子模板里使用”head”块的css文件并增加新的文件,如下:

{% block title %}Block Sample{% endblock %}

{% block head %}

    {{ super() }}

    

{% endblock %}

父模板同子模板的”head”块中都有内容。程序运行后,父模板中的”head”块语句先被加载,而后加载子模板中的”head”块语句。

块内语句的作用域

默认情况下,块内语句是无法访问块外作用域中的变量。比如:

{% for item in range(5) %}
    
  • {% block loop_item %}{{ item }}{% endblock %}
  • {% endfor %}

    模板中定义”list”块并访问循环中的”item”变量:

    这个例子会输出空的 < li> 项,因为 item 在块中是不可用的。其原因是,如果 块被子模板替换,变量在其块中可能是未定义的或未被传递到上下文。

    从 Jinja 2.2 开始,你可以显式地指定在块中可用的变量,只需在块声明中添加 scoped 修饰,就把块设定到作用域中:

    {% for item in range(5) %}
        
  • {% block loop_item scoped %}{{ item }}{% endblock %}
  • {% endfor %}

    当覆盖一个块时,不需要提供 scoped 修饰。 

    创建宏

    凭借将反复出现的代码片段抽象成,我们可以实现DRY原则(Don't Repeat Yourself)。

    宏类似于函数。

    我们可以在下面的例子中实验一下:

    表单Form

    templates/_form.html

    {% macro render_input(name, type='text', value='',placeholder='') -%}
        
    {%- endmacro %}
    

    上面定义了一个宏,名称是render_input,这个宏有四个参数,分别是name、type、value、placeholder,下面调用它:

    templates/register.html

    {% from "_menu.html" import render_input %}
    ....
    
    {{ render_input('username',placeholder='请输入你的用户名') }}  //这里是用户名输入框
    {{ render_input('password',type='password')}}    //这里是密码框
    
    ...
    

    表单 error 

    templates/_errors.html

    {% macro render_field_errors(field) -%}
        
    {% if field.errors[0] %}

    {{ field.errors[0]}}

    {% endif %}
    {%- endmacro %}

    上面是表单错误信息的宏,调用时直接使用:

    templates/register.html

    {% from "_menu.html" import render_input %}
    ....
    
    {{ render_field_errors( 'username' ) }}
    
    ....
    

    接着是按钮的宏

    templates/_button.html

    {% macro render_button(name='',classes=()) -%}
        {{ name }}
    {%- endmacro %}
    

    class 的实现感觉非常巧妙。将 classes 设置为 tuple(元组),然后使用 String 的 join 方法,在每个class 属性之间添加空格。

    菜单

    菜单的宏代码:

    templates/_menu.html

    {% macro nav_link(endpoint, text) %}
    {% if request.endpoint.endswith(endpoint) %}
        
  • {{ text }}
  • {% else %}
  • {{ text }}
  • {% endif %} {% endmacro %}

    这个宏可以根据 URL 中的 endpoint 来 active 菜单中的当前项,感觉非常方便,而且将大段的代码,进行了缩减。

    如果没有宏,我们将不得不使用一大堆if/else语句来从每个链接中过滤出“active”链接。在使用的时候只需要调用:

    templates/base.html

    {% from "_menu.html" import nav_link with context %}
    
    
        
        {% block head %}
            我的应用
        {% endblock %}
        
        
            
        {% block body %}
        {% endblock %}
        
    

    注意 你可能注意到了我们在import语句中加入了with context。Jinja的上下文(context)通过render_template()函数传递的参数以及在我们的Python代码的Jinja环境上下文。这些变量能够被用于模板的渲染。

    一些变量是我们显式传递过去的,比如render_template("index.html", color="red"),但还有些变量和函数是Flask自动加入到上下文的,比如requestgsession。使用了{% from ... import ... with context %},我们告诉Jinja让所有的变量也在宏里可用。

    参见

    • 所有的全局变量都是由Flask传递给Jinja上下文的: http://flask.pocoo.org/docs/templating/#standard-context
    • 通过上下文处理器(context processors),我们可以增加传递给Jinja上下文的变量和函数: http://flask.pocoo.org/docs/templating/#context-processors

    分页导航

    templates/_pagination.html

    {% macro render_pagination(pagination, endpoint) %}
        
    {% endmacro %}
    

     pagination.iter_pages() 函数默认显示当前页的前五页和最后的后两页,中间的内容要以 ... 来代替。

    这个分页的代码,可以用在页面的不同位置,都能实现相同的效果,比如:

    {{ render_paginations('article_pagination') }}
    {{ render_paginations('comment_pagination') }}

    Caller () 的用法

    我们先来创建个宏”list_users”:

    templates/_list_user.html

    {% macro list_users(users) -%}
    
      
    
        
    
        {%- for user in users %}
    
          {{ caller() }}
    
        {%- endfor %}
    
      
    NameAction
    {{ user.name |e }}
    {%- endmacro %}

    宏的作用就是将用户列表显示在表格里,表格每一行用户名称后面调用了”{{ caller( ) }}”方法,这个有什么用呢?先别急,我们来写调用者的代码:

    templates/user.html

    {% from "_list_user.html" import list_users with context %}
    
    {% set users=[{'name':'Tom','gender':'M','age':20},
    
                  {'name':'John','gender':'M','age':18},
    
                  {'name':'Mary','gender':'F','age':24}]
    
    %}
    
    
    
    {% call list_users(users) %}
    
        
    
    {% endcall %}

    与上例不同,这里我们使用了”{% call %}”语句块来调用宏,语句块中包括了一段生成”Delete”按钮的代码。运行下试试,你会发现每个用户名后面都出现了”Delete”按钮,也就是”{{ caller( ) }}”部分被调用者”{% call %}”语句块内部的内容替代了。不明觉厉吧!其实吧,这个跟函数传个参数进去没啥大区别,个人觉得,主要是有些时候HTML语句太复杂(如上例),不方便写在调用参数上,所以就写在”{% call %}”语句块里了。

    Jinja2的宏不但能访问调用者语句块的内容,还能给调用者传递参数。嚯,这又是个什么鬼?我们来扩展下上面的例子。首先,我们将表格增加一列性别,并在宏里调用”caller()”方法时,传入一个变量”user.gender”:

    templates/_list_user.html

    {% macro list_users(users) -%}
      
        
        {%- for user in users %}
          {{ caller(user.gender) }}
        {%- endfor %}
      
    NameGenderAction
    {{ user.name |e }}
    {%- endmacro %}

    然后,我们修改下调用者语句块:

    templates/user.html

    {% from "_list_user.html" import list_users with context %}
    
    {% call(gender) list_users(users) %}
        
        {% if gender == 'M' %}
        
        {% else %}
        
        {% endif %}
        
        
    {% endcall %}
    

    大家注意到,我们在使用”{% call %}”语句时,将其改为了”{% call(gender) … %}”,这个括号中的”gender”就是用来接受宏里传来的”user.gender”变量。因此我们就可以在”{% call %}”语句中使用这个”gender”变量来判断用户性别。这样宏就成功地向调用者传递了参数。

    包含 (Include)

    这里我们再介绍一个Jinja2模板中代码重用的功能,就是包含 (Include),使用的方法就是”{% include %}”语句。其功能就是将另一个模板加载到当前模板中,并直接渲染在当前位置上。它同导入”import”不一样,”import”之后你还需要调用宏来渲染你的内容,”include”是直接将目标模板渲染出来。它同block块继承也不一样,它一次渲染整个模板文件内容,不分块。

    我们可以创建一个”footer.html”模板,并在”layout.html”中包含这个模板:

    
    
        ...
    
        {% include 'footer.html' %}
    
    

    当”include”的模板文件不存在时,程序会抛出异常。你可以加上”ignore missing”关键字,这样如果模板不存在,就会忽略这段”{% include %}”语句。

     {% include 'footer.html' ignore missing %}

    “{% include %}”语句还可以跟一个模板列表:

     {% include ['footer.html','bottom.html','end.html'] ignore missing %}

    上例中,程序会按顺序寻找模板文件,第一个被找到的模板即被加载,而其后的模板都会被忽略。如果都没找到,那整个语句都会被忽略。

    你可能感兴趣的:(flask)