模板结构组织
除了使用函数、过滤器等工具控制模板的输出外,jinja2还提供了一些工具来在宏观上组织模板内容。
局部模板
在Web程序中,我们通常会为每一类页面编写一个独立的模板。比如主页模板、用户资料也模板、设置页模板等。这些模板可以直接在视图函数中渲染并作为HTML响应主题。除了这类模板,我们还会用到另一类非独立模板,这类模板通常被称为局部模板或此模板,因为它们仅包含部分代码,所以我们不会在视图函数中直接渲染它,而是插入到其他独立模板中。
当某个视图用来处理ajax请求时,返回的数据不需要包含完整的HTML结构,这时就可以返回渲染后的局部模板。
当多个独立的模板中都会使用同一块HTML代码时,我们可以把这部分代码抽离出来,存储到局部模板中。这样一方面可以避免重复,另一方面也可以方便统一管理。比如,多个页面中都要在页面顶部显示一个提示条,这个横幅可以定义在局部模板_banner.html中。
我们使用include标签来插入一个局部模板,这会把局部模板的全部内容插在使用include标签的位置。
比如在其他模板中,我们可以在任意位置使用下面的代码插入_banner.html的内容:
{% include ‘_banner.html’ %}
为了和普通模板区分开,局部模板的命名通常以一个下划线开始。
宏
宏(macro)是jinja2提供的一个非常有用的特性,它类似python中的函数。使用宏可以把一部分模板代码封装到宏里,使用传递的参数来构建内容,最后返回构建后的内容。在功能上,它和局部模板类似,都是为了方便代码块的重用。
为了便于管理,可以把宏存储在单独的文件中,这个文件通常命名为macros.html或_macros.html。在创建宏时,使用macro和endmacro标签声明宏的开始和结束。在开始标签中定义宏的名称和接收的参数
例子:
{% macro qux(amount=1) %}
{% if amount == 1 %}
I am qux.
{% elif amount > 1 %}
We are quxs.
{% endmacro %}
使用时,需要向从python模块中导入函数一样使用import语句导入它,然后作为函数调用,传入必要的参数,例如:
{% from 'macros.html' import qux %}
...
{{ qux(amount=5) }}
另外,在使用宏时我们需要注意上下文问题,在jinja2中,出于性能的考虑,并且为了让一切保持显示,默认情况下包含(include)一个局部模板会传递当前上下文到局部模板中,但导入(import)不会。具体来说,当我们使用render_template()函数渲染一个foo.html模板时,这个foo.html的模板上下文中包含下列对象:
1 flask使用内置的模板上下文处理函数提供的g、session、config、request。
2 扩展使用内置的模板上下文处理函数提供的变量。
3 自定义模板上下文处理传入的变量
4 使用render_template()函数传入的变量
5 jinja2和flask内置及自定义全局对象。
6 jinja2内置及自定义过滤器
7 jinja2内置及自定义测试器
使用include标签插入的局部模板(比如_banner.html)同样可以使用上述上下文中的变量和函数。而导入另一个并非直接渲染的模板(比如macro.html)时,这个模板进包含下列这些对象:
1 jinja2和flask内置的全局函数和自定义全局函数
2 jinja2内置及自定义过滤器
3 jinja2内置及自定义测试器
因此,如果我们想在导入的宏中使用第一个列表中的2、3、4项,就需要在导入时显示地使用with context声明传入当前模板的上下文:
{% from “macros.html” import foo with context %}
虽然flask使用内置的模板上下文处理函数传入session、g、request和config,但它同时也使用app.jinja_env.globals字典将这几个变量设置为全局变量,所以我们仍然可以在不显示声明传入上下文的情况下,直接在导入的宏中使用它们。
模板继承
jinja2的模板继承允许你定义一个基模板,把网页上的导航栏、页脚等通用内容放在基模板中,而每一个继承基模板的子模板在被渲染时都会自动包含这些部分。使用这种方式可以避免在多个模板中编写重复的代码。
编写基模板
基模板存储了程序页面的固定部分,通常被命名为base.html或layout.html。实例程序中的基模板base.html中包含了一个基本的HTML结构,我们还添加了一个简单的导航条和页脚
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="utf-8">
<title>{% block title %}Template - HelloFlask{% endblock %}</title>
{% block styles %}{% endblock %}
{% endblock %}
</head>
<body>
<nav>
<ul><li><a href="{{ url_for('index') }}">Home</a></li></ul>
</nav>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
...
{% endblock %}
</footer>
{% block sripts %}{% endblock %}
</body>
</html>
当子模板继承基模板后,子模板会自动包含基模板的内容和结构。为了能够让子模板方便地覆盖或插入内容到基模板中,我们需要在基模板中定义块(block),在子模板中可以通过定义同名的块来执行继承操作。
块的开始和结束分别使用block和endblock标签声明,而且快之间可以嵌套。在这个基模板中,我们创建了6个块:head、title、styles、content、footer和scripts,分别用来划分不同的代码。其中,head块表示<head>标签的内容,title表示<title>标签的内容,content块表示页面主题内容,footer表示页脚部分,styles块和scripts块分别用来包含CSS文件和javaScript文件引用链接或页内的CSS和javaScript代码。
这里的块名称可以随意指定,而且并不是必须的。你可以按照需要设置块,如果只需要让字模板添加主体内容,那么仅定义一个content块就足够了。
以content块为例,模板继承示意图如下:
为了避免块的混乱,块的结束标签可以指明块名,同时要确保前后名称一致。
如:
{% block body %}
…
{% endblock body %}
编写子模板
因为基模板中定义了HTML的基本结构,而且包含了页脚等固定信息,在子模板中我们不再需要定义这些内容,只需要对特定的块进行修改。这时我们可以修改前面创建的电影清单模板watchlist.html和主页模板index.html,将这些子模板的通用部分合并到基模板中,并在子模板中定义块来组织内容,以便在渲染时将块中的内容插入到基模板的对应位置。
以index.html为例,修改后的index.html子模板如下:
{% extends 'base.html' %}
{% from 'macros.html' import qux %}
{% block content %}
{% set name='baz' %}
<h1>Template</h1>
<ul>
<li><a href="{{ url_for('watchlist') }}">Watchlist</a></li>
<li>Filter:{{ foo|musical }}</li>
<li>Global:{{ bar() }}</li>
<li>Test:{% if name is baz %}I am baz.{% endif %}</li>
<li>Macro: {{ qux(amount=5) }}</li>
</ul>
{% endblock %}
我们使用extends标签声明扩展基模板,它告诉模板引擎当前模板继承base.html
extends必须是字模板的第一个标签。
我们在基模板中定义了四个块,在子模板中,我们可以对父模板中的块进行两种操作:
1 覆盖内容
当在字模板里创建同名的块时,会使用子块的内容覆盖父块的内容。比如在字模板index.html中定义了title块,内容为Home,这会把块中的内容填充到基模板里的title块的位置,最终渲染为<title>Home</title>,content块的效果同理。
2 追加内容
如果要向基模板中的块追加内容,需要使用jinja2提供的super()函数进行声明,这会向父块添加内容。比如,下面例子中向基模板中的styles块追加了一行<style>样式定义:
{% block styles %}
{{ super() }}
<style>
.foo{
color:red;
}
</style>
{% endblock %}
当字模板被渲染时,它会继承基模板的所有内容,然后根据我们定义的块进行覆盖或追加操作,渲染子模板index.html的结果:
相当于把base.html的内容先拷贝过来,然后根据子模板中的内容进行定型,比如覆盖了什么块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Template - HelloFlask}</title>
</head>
<body>
<nav>
<ul><li><a href="/">Home</a></li></ul>
</nav>
<main>
<h1>Template</h1>
<ul>
<li><a href="/watchlist">Watchlist</a></li>
<li>Filter:I am foo.♫</li>
<li>Global:I am bar.</li>
<li>Test:I am baz.</li>
<li>Macro: We are quxs.</li>
</ul>
</main>
<footer>
...
</footer>
</body>
</html>