Django的模板引擎中,功能最强大因此也最复杂的部分,就是模板继承。它允许你构建一个模板基础“骨架”,包含网站中所有的通用元素,然后定义各种**blocks**,以便子模板可以重写。
通过一个例子最容易理解模板继承:
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
~~~
这个模板文件,我们可以称之为**base.html**, 定义了一个简单的HTML文档骨架,可用于2列的页面布局。子模板的任务就是用内容填充空的块状区域【empty blocks】。
在这个例子中,**block**标记定义了3个区域,可供子模板填充。所有的**block**标记所做的事情,就是告诉模板引擎,子模板可以重写模板的这些部分。
一个子模板看上去大概像这样:
~~~
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
~~~
这里的关键是**extends**标记。它告诉模板引擎,这个模板继承于另外一个模板。当模板系统要计算这个模板最终输出时,它首先定位到父模板,在这里,就是**"base.html"**。
此时,模板引擎会注意到**base.html**中的3个**block**标记,然后使用子模板的内容替换这3个区域。基于上面**blog_entries**的值,最终的输出大致像这样:
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>My amazing blog</title>
</head>
<body>
<div id="sidebar">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
</div>
<div id="content">
<h2>Entry one</h2>
<p>This is my first entry.</p>
<h2>Entry two</h2>
<p>This is my second entry.</p>
</div>
</body>
</html>
~~~
要注意由于子模板并没有定义**sidebar**区块,父模板的值被续用。父模板中包裹在**{% block %}**标记的内容,总是作为最后的备选。
你可以使用任意多的继承层级。一种常用的模板继承方式是如下的3级步骤:
* 创建一个**base.html**模板文件,囊括了网站的主体外观。
* 针对网站中每一个模块,创建一个**base_SECTIONNAME.html**模板文件。比如,**base_news.html**,**base_sports.html**。这些模板全都继承自**base.html**,并且包含模块特定的样式和设计。
* 为每种类型的页面创建单独的模板,比如一则新闻,或者一篇博客。这些模板继承自对应的功能模块模板。
这种方式可以最大化代码复用,并且很容易对共享内容区域添加新元素,比如网站模块导航。
以下是处理继承时的一些注意事项:
* 如果你在一个模板中使用**{% extends %}**,那它必须是模板中的第一个标记。否则模板继承将失效。
* 在基础模板中**{% block %}**标记越多越好。记住,子模板不需要定义所有的父区块,所以你可以对一些区块使用合理的默认值填充,然后只需要定义那些你确实需要的区块。钩子多总比钩子少好。
* 如果你发现自己正在一组模板中做重复的事情,那可能就意味着,你需要在父模板中,将这部分内容移至**{% block %}**中。
* 如果你需要从父模板中获取区块内容,**{{ block.super }}**变量可以完成这件事情。当你不想完全覆盖,而只想在父区块中增加内容时,这个会非常有用。使用**{{ block.super }}**插入的数据不会被自动转义(详见[HTML自动转义](https://docs.djangoproject.com/en/1.10/ref/templates/language/#automatic-html-escaping)),因为如果有必要,它在父模板中就已经被转义过了。
* 为了额外的可读性,你可以可选的在**{% endblock %}**标记中给定一个名字。比如:
~~~
{% block content %}
...
{% endblock content %}
~~~
在较大的模板文件中,这个技巧可以帮助你识别到底是哪个**{% block %}**标记结束。
最后,记住你不能在同样的模板文件中,定义同名的多个**block**标记。之所以有这个限制,是因为一个**block**标记是双向的影响。就是说,一个block标记的作用,不仅仅是[向子级]提供可填充区域 -- 它还在父端定义了填充的内容。如果在一个模板中有2个同名的**block**标记,那么父模板就不知道该使用哪一个区块的内容。