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**標記,那么父模板就不知道該使用哪一個區塊的內容。