在前一章中,你可能已經注意到我們在例子視圖中返回文本的方式有點特別。 也就是說,HTML被直接硬編碼在 Python 代碼之中。
~~~
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
~~~
盡管這種技術便于解釋視圖是如何工作的,但直接將HTML硬編碼到你的視圖里卻并不是一個好主意。 讓我們來看一下為什么:
* 對頁面設計進行的任何改變都必須對 Python 代碼進行相應的修改。 站點設計的修改往往比底層 Python 代碼的修改要頻繁得多,因此如果可以在不進行 Python 代碼修改的情況下變更設計,那將會方便得多。
* Python 代碼編寫和 HTML 設計是兩項不同的工作,大多數專業的網站開發環境都將他們分配給不同的人員(甚至不同部門)來完成。 設計者和HTML/CSS的編碼人員不應該被要求去編輯Python的代碼來完成他們的工作。
* 程序員編寫 Python代碼和設計人員制作模板兩項工作同時進行的效率是最高的,遠勝于讓一個人等待另一個人完成對某個既包含 Python又包含 HTML 的文件的編輯工作。
基于這些原因,將頁面的設計和Python的代碼分離開會更干凈簡潔更容易維護。 我們可以使用 Django的?_模板系統_?(Template System)來實現這種模式,這就是本章要具體討論的問題。
## 模板系統基本知識
模板是一個文本,用于分離文檔的表現形式和內容。 模板定義了占位符以及各種用于規范文檔該如何顯示的各部分基本邏輯(模板標簽)。 模板通常用于產生HTML,但是Django的模板也能產生任何基于文本格式的文檔。
讓我們從一個簡單的例子模板開始。 該模板描述了一個向某個與公司簽單人員致謝 HTML 頁面。 可將其視為一個格式信函:
~~~
<html>
<head><title>Ordering notice</title></head>
<body>
<h1>Ordering notice</h1>
<p>Dear {{ person_name }},</p>
<p>Thanks for placing an order from {{ company }}. It's scheduled to
ship on {{ ship_date|date:"F j, Y" }}.</p>
<p>Here are the items you've ordered:</p>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if ordered_warranty %}
<p>Your warranty information will be included in the packaging.</p>
{% else %}
<p>You didn't order a warranty, so you're on your own when
the products inevitably stop working.</p>
{% endif %}
<p>Sincerely,<br />{{ company }}</p>
</body>
</html>
~~~
該模板是一段添加了些許變量和模板標簽的基礎 HTML 。 讓我們逐步分析一下:
> 用兩個大括號括起來的文字(例如?{{?person_name?}}?)稱為?_變量(variable)_?。這意味著在此處插入指定變量的值。 如何指定變量的值呢? 稍后就會說明。
>
> 被大括號和百分號包圍的文本(例如?{%?if?ordered_warranty?%}?)是?_模板標簽(template tag)_?。標簽(tag)定義比較明確,即: 僅通知模板系統完成某些工作的標簽。
>
> 這個例子中的模板包含一個for標簽(?{%?for?item?in?item_list?%}?)和一個if?標簽({%?if?ordered_warranty?%}?)
>
> for標簽類似Python的for語句,可讓你循環訪問序列里的每一個項目。?if?標簽,正如你所料,是用來執行邏輯判斷的。 在這里,tag標簽檢查ordered_warranty值是否為True。如果是,模板系統將顯示{% if ordered_warranty %}和{% else %}之間的內容;否則將顯示{% else %}和{% endif %}之間的內容。{% else %}是可選的。
>
> 最后,這個模板的第二段中有一個關于_filter_過濾器的例子,它是一種最便捷的轉換變量輸出格式的方式。 如這個例子中的{{ship_date|date:”F j, Y” }},我們將變量ship_date傳遞給date過濾器,同時指定參數”F j,Y”。date過濾器根據參數進行格式輸出。 過濾器是用管道符(|)來調用的,具體可以參見Unix管道符。
Django 模板含有很多內置的tags和filters,我們將陸續進行學習. 附錄F列出了很多的tags和filters的列表,熟悉這些列表對你來說是個好建議. 你依然可以利用它創建自己的tag和filters。這些我們在第9章會講到。
## 如何使用模板系統
讓我們深入研究模板系統,你將會明白它是如何工作的。但我們暫不打算將它與先前創建的視圖結合在一起,因為我們現在的目的是了解它是如何獨立工作的。 。 (換言之, 通常你會將模板和視圖一起使用,但是我們只是想突出模板系統是一個Python庫,你可以在任何地方使用它,而不僅僅是在Django視圖中。)
在Python代碼中使用Django模板的最基本方式如下:
1. 可以用原始的模板代碼字符串創建一個?Template?對象, Django同樣支持用指定模板文件路徑的方式來創建?Template?對象;
1. 調用模板對象的render方法,并且傳入一套變量context。它將返回一個基于模板的展現字符串,模板中的變量和標簽會被context值替換。
代碼如下:
~~~
>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Adrian'})
>>> print t.render(c)
My name is Adrian.
>>> c = template.Context({'name': 'Fred'})
>>> print t.render(c)
My name is Fred.
~~~
以下部分逐步的詳細介紹
### 創建模板對象
創建一個?Template?對象最簡單的方法就是直接實例化它。?Template?類就在?django.template?模塊中,構造函數接受一個參數,原始模板代碼。 讓我們深入挖掘一下 Python的解釋器看看它是怎么工作的。
轉到project目錄(在第二章由?django-admin.py?startproject?命令創建), 輸入命令python?manage.py?shell?啟動交互界面。
一個特殊的Python提示符
如果你曾經使用過Python,你一定好奇,為什么我們運行python?manage.py?shell而不是python。這兩個命令都會啟動交互解釋器,但是manage.py?shell命令有一個重要的不同: 在啟動解釋器之前,它告訴Django使用哪個設置文件。 Django框架的大部分子系統,包括模板系統,都依賴于配置文件;如果Django不知道使用哪個配置文件,這些系統將不能工作。
如果你想知道,這里將向你解釋它背后是如何工作的。 Django搜索DJANGO_SETTINGS_MODULE環境變量,它被設置在settings.py中。例如,假設mysite在你的Python搜索路徑中,那么DJANGO_SETTINGS_MODULE應該被設置為:’mysite.settings’。
當你運行命令:python manage.py shell,它將自動幫你處理DJANGO_SETTINGS_MODULE。 在當前的這些示例中,我們鼓勵你使用`` python manage.py shell``這個方法,這樣可以免去你大費周章地去配置那些你不熟悉的環境變量。
隨著你越來越熟悉Django,你可能會偏向于廢棄使用`` manage.py shell`` ,而是在你的配置文件.bash_profile中手動添加?DJANGO_SETTINGS_MODULE這個環境變量。
讓我們來了解一些模板系統的基本知識:
~~~
>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print t
~~~
如果你跟我們一起做,你將會看到下面的內容:
0xb7d5f24c?每次都會不一樣,這沒什么關系;這只是Python運行時?Template?對象的ID。
當你創建一個?Template?對象,模板系統在內部編譯這個模板到內部格式,并做優化,做好 渲染的準備。 如果你的模板語法有錯誤,那么在調用?Template()?時就會拋出?TemplateSyntaxError?異常:
~~~
>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
...
django.template.TemplateSyntaxError: Invalid block tag: 'notatag'
~~~
這里,塊標簽(block tag)指向的是`` {% notatag %}``,塊標簽與模板標簽是同義的。
系統會在下面的情形拋出?TemplateSyntaxError?異常:
* 無效的tags
* 標簽的參數無效
* 無效的過濾器
* 過濾器的參數無效
* 無效的模板語法
* 未封閉的塊標簽 (針對需要封閉的塊標簽)
### 模板渲染
一旦你創建一個?Template?對象,你可以用?_context_?來傳遞數據給它。 一個context是一系列變量和它們值的集合。
context在Django里表現為?Context?類,在?django.template?模塊里。 她的構造函數帶有一個可選的參數: 一個字典映射變量和它們的值。 調用?Template?對象 的?render()?方法并傳遞context來填充模板:
~~~
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
u'My name is Stephane.'
~~~
我們必須指出的一點是,t.render(c)返回的值是一個Unicode對象,不是普通的Python字符串。 你可以通過字符串前的u來區分。 在框架中,Django會一直使用Unicode對象而不是普通的字符串。 如果你明白這樣做給你帶來了多大便利的話,盡可能地感激Django在幕后有條不紊地為你所做這這么多工作吧。 如果不明白你從中獲益了什么,別擔心。你只需要知道Django對Unicode的支持,將讓你的應用程序輕松地處理各式各樣的字符集,而不僅僅是基本的A-Z英文字符。
字典和Contexts
Python的字典數據類型就是關鍵字和它們值的一個映射。?Context?和字典很類似,?Context?還提供更多的功能,請看第九章。
變量名必須由英文字符開始 (A-Z或a-z)并可以包含數字字符、下劃線和小數點。 (小數點在這里有特別的用途,稍后我們會講到)變量是大小寫敏感的。
下面是編寫模板并渲染的示例:
~~~
>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p>Thanks for placing an order from {{ company }}. It's scheduled to
... ship on {{ ship_date|date:"F j, Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the packaging.</p>
... {% else %}
... <p>You didn't order a warranty, so you're on your own when
... the products inevitably stop working.</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'John Smith',
... 'company': 'Outdoor Equipment',
... 'ship_date': datetime.date(2009, 4, 2),
... 'ordered_warranty': False})
>>> t.render(c)
u"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor
Equipment. It's scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You
didn't order a warranty, so you're on your own when\nthe products
inevitably stop working.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment
</p>"
~~~
讓我們逐步來分析下這段代碼:
> 首先我們導入 (import)類?Template?和?Context?,它們都在模塊?django.template?里。
>
> 我們把模板原始文本保存到變量?raw_template?。注意到我們使用了三個引號來 標識這些文本,因為這樣可以包含多行。
>
> 接下來,我們創建了一個模板對象?t?,把?raw_template?作為?Template?類構造函數的參數。
>
> 我們從Python的標準庫導入?datetime?模塊,以后我們將會使用它。
>
> 然后,我們創建一個?Context?對象,?c?。?Context?構造的參數是Python 字典數據類型。 在這里,我們指定參數?person_name?的值是?'John?Smith'?, 參數company 的值為 ‘Outdoor Equipment’ ,等等。
>
> 最后,我們在模板對象上調用?render()?方法,傳遞 context參數給它。 這是返回渲染后的模板的方法,它會替換模板變量為真實的值和執行塊標簽。
>
> 注意,warranty paragraph顯示是因為?ordered_warranty?的值為?True?. 注意時間的顯示,?April?2,?2009, 它是按?'F?j,?Y'?格式顯示的。
>
> 如果你是Python初學者,你可能在想為什么輸出里有回車換行的字符('\n'?)而不是 顯示回車換行? 因為這是Python交互解釋器的緣故: 調用?t.render(c)?返回字符串, 解釋器缺省顯示這些字符串的?_真實內容呈現_?,而不是打印這個變量的值。 要顯示換行而不是?'\n'?,使用?print?語句:?print?t.render(c)?。
這就是使用Django模板系統的基本規則: 寫模板,創建?Template?對象,創建?Context?, 調用?render()?方法。
### 同一模板,多個上下文
一旦有了?模板?對象,你就可以通過它渲染多個context, 例如:
~~~
>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print t.render(Context({'name': 'John'}))
Hello, John
>>> print t.render(Context({'name': 'Julie'}))
Hello, Julie
>>> print t.render(Context({'name': 'Pat'}))
Hello, Pat
~~~
無論何時我們都可以像這樣使用同一模板源渲染多個context,只進行?一次模板創建然后多次調用render()方法渲染會更為高效:
~~~
# Bad
for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print t.render(Context({'name': name}))
# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print t.render(Context({'name': name}))
~~~
Django 模板解析非常快捷。 大部分的解析工作都是在后臺通過對簡短正則表達式一次性調用來完成。 這和基于 XML 的模板引擎形成鮮明對比,那些引擎承擔了 XML 解析器的開銷,且往往比 Django 模板渲染引擎要慢上幾個數量級。
### 深度變量的查找
在到目前為止的例子中,我們通過 context 傳遞的簡單參數值主要是字符串,還有一個?datetime.date?范例。 然而,模板系統能夠非常簡潔地處理更加復雜的數據結構,例如list、dictionary和自定義的對象。
在 Django 模板中遍歷復雜數據結構的關鍵是句點字符 (.)。
最好是用幾個例子來說明一下。 比如,假設你要向模板傳遞一個 Python 字典。 要通過字典鍵訪問該字典的值,可使用一個句點:
~~~
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally is 43 years old.'
~~~
同樣,也可以通過句點來訪問對象的屬性。 比方說, Python 的?datetime.date?對象有?year?、?month?和?day幾個屬性,你同樣可以在模板中使用句點來訪問這些屬性:
~~~
>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(1993, 5, 2)
>>> d.year
1993
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
u'The month is 5 and the year is 1993.'
~~~
這個例子使用了一個自定義的類,演示了通過實例變量加一點(dots)來訪問它的屬性,這個方法適用于任意的對象。
~~~
>>> from django.template import Template, Context
>>> class Person(object):
... def __init__(self, first_name, last_name):
... self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
u'Hello, John Smith.'
~~~
點語法也可以用來引用對象的* 方法*。 例如,每個 Python 字符串都有?upper()?和?isdigit()?方法,你在模板中可以使用同樣的句點語法來調用它們:
~~~
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
u'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
u'123 -- 123 -- True'
~~~
注意這里調用方法時并* 沒有* 使用圓括號 而且也無法給該方法傳遞參數;你只能調用不需參數的方法。 (我們將在本章稍后部分解釋該設計觀。)
最后,句點也可用于訪問列表索引,例如:
~~~
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
u'Item 2 is carrots.'
~~~
不允許使用負數列表索引。 像?{{?items.-1?}}?這樣的模板變量將會引發`` TemplateSyntaxError``
Python 列表類型
一點提示: Python的列表是從0開始索引。 第一項的索引是0,第二項的是1,依此類推。
句點查找規則可概括為: 當模板系統在變量名中遇到點時,按照以下順序嘗試進行查找:
* 字典類型查找 (比如?foo["bar"]?)
* 屬性查找 (比如?foo.bar?)
* 方法調用 (比如?foo.bar()?)
* 列表類型索引查找 (比如?foo[bar]?)
系統使用找到的第一個有效類型。 這是一種短路邏輯。
句點查找可以多級深度嵌套。 例如在下面這個例子中?{{person.name.upper}}?會轉換成字典類型查找(person['name']?) 然后是方法調用(?upper()?):
~~~
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'SALLY is 43 years old.'
~~~
#### 方法調用行為
方法調用比其他類型的查找略為復雜一點。 以下是一些注意事項:
> 在方法查找過程中,如果某方法拋出一個異常,除非該異常有一個?silent_variable_failure?屬性并且值為?True?,否則的話它將被傳播。如果異常被傳播,模板里的指定變量會被置為空字符串,比如:
~~~
>>> t = Template("My name is {{ person.first_name }}.")
>>> class PersonClass3:
... def first_name(self):
... raise AssertionError, "foo"
>>> p = PersonClass3()
>>> t.render(Context({"person": p}))
Traceback (most recent call last):
...
AssertionError: foo
>>> class SilentAssertionError(AssertionError):
... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
u'My name is .'
~~~
> 僅在方法無需傳入參數時,其調用才有效。 否則,系統將會轉移到下一個查找類型(列表索引查找)。
>
> 顯然,有些方法是有副作用的,好的情況下允許模板系統訪問它們可能只是干件蠢事,壞的情況下甚至會引發安全漏洞。
>
> 例如,你的一個?BankAccount?對象有一個?delete()?方法。 如果某個模板中包含了像{{?account.delete?}}這樣的標簽,其中`` account`` 又是BankAccount?的一個實例,請注意在這個模板載入時,account對象將被刪除。
>
> 要防止這樣的事情發生,必須設置該方法的?alters_data?函數屬性:
~~~
def delete(self):
# Delete the account
delete.alters_data = True
~~~
> 模板系統不會執行任何以該方式進行標記的方法。 接上面的例子,如果模板文件里包含了{{?account.delete?}}?,對象又具有?delete()方法,而且delete()?有alters_data=True這個屬性,那么在模板載入時,?delete()方法將不會被執行。 它將靜靜地錯誤退出。
#### 如何處理無效變量
默認情況下,如果一個變量不存在,模板系統會把它展示為空字符串,不做任何事情來表示失敗。 例如:
~~~
>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
u'Your name is .'
>>> t.render(Context({'var': 'hello'}))
u'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
u'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
u'Your name is .'
~~~
系統靜悄悄地表示失敗,而不是引發一個異常,因為這通常是人為錯誤造成的。 這種情況下,因為變量名有錯誤的狀況或名稱, 所有的查詢都會失敗。 現實世界中,對于一個web站點來說,如果僅僅因為一個小的模板語法錯誤而造成無法訪問,這是不可接受的。
### 玩一玩上下文(context)對象
多數時間,你可以通過傳遞一個完全填充(full populated)的字典給?Context()?來初始化?上下文(Context)?。 但是初始化以后,你也可以使用標準的Python字典語法(syntax)向``上下文(Context)`` 對象添加或者刪除條目:
~~~
>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
Traceback (most recent call last):
...
KeyError: 'foo'
>>> c['newvariable'] = 'hello'
>>> c['newvariable']
'hello'
~~~
## 基本的模板標簽和過濾器
像我們以前提到過的,模板系統帶有內置的標簽和過濾器。 下面的章節提供了一個多數通用標簽和過濾器的簡要說明。
### 標簽
#### if/else
{%?if?%}?標簽檢查(evaluate)一個變量,如果這個變量為真(即,變量存在,非空,不是布爾值假),系統會顯示在?{%?if?%}?和?{%?endif?%}?之間的任何內容,例如:
~~~
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% endif %}
~~~
{%?else?%}?標簽是可選的:
~~~
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}
~~~
Python 的“真值”
在Python和Django模板系統中,以下這些對象相當于布爾值的False
* 空列表([]?)
* 空元組(()?)
* 空字典({}?)
* 空字符串(''?)
* 零值(0?)
* 特殊對象None
* 對象False(很明顯)
* 提示:你也可以在自定義的對象里定義他們的布爾值屬性(這個是python的高級用法)。
除以上幾點以外的所有東西都視為`` True``
{%?if?%}?標簽接受?and?,?or?或者?not?關鍵字來對多個變量做判斷 ,或者對變量取反(?not?),例如: 例如:
~~~
{% if athlete_list and coach_list %}
Both athletes and coaches are available.
{% endif %}
{% if not athlete_list %}
There are no athletes.
{% endif %}
{% if athlete_list or coach_list %}
There are some athletes or some coaches.
{% endif %}
{% if not athlete_list or coach_list %}
There are no athletes or there are some coaches.
{% endif %}
{% if athlete_list and not coach_list %}
There are some athletes and absolutely no coaches.
{% endif %}
~~~
{%?if?%}?標簽不允許在同一個標簽中同時使用?and?和?or?,因為邏輯上可能模糊的,例如,如下示例是錯誤的: 比如這樣的代碼是不合法的:
~~~
{% if athlete_list and coach_list or cheerleader_list %}
~~~
系統不支持用圓括號來組合比較操作。 如果你確實需要用到圓括號來組合表達你的邏輯式,考慮將它移到模板之外處理,然后以模板變量的形式傳入結果吧。 或者,僅僅用嵌套的{%?if?%}標簽替換吧,就像這樣:
~~~
{% if athlete_list %}
{% if coach_list or cheerleader_list %}
We have athletes, and either coaches or cheerleaders!
{% endif %}
{% endif %}
~~~
多次使用同一個邏輯操作符是沒有問題的,但是我們不能把不同的操作符組合起來。 例如,這是合法的:
~~~
{% if athlete_list or coach_list or parent_list or teacher_list %}
~~~
并沒有?{%?elif?%}?標簽, 請使用嵌套的`` {% if %}`` 標簽來達成同樣的效果:
~~~
{% if athlete_list %}
Here are the athletes: {{ athlete_list }}.
{% else %}
No athletes are available.
{% if coach_list %}
Here are the coaches: {{ coach_list }}.
{% endif %}
{% endif %}
~~~
一定要用?{%?endif?%}?關閉每一個?{%?if?%}?標簽。
#### for
{%?for?%}?允許我們在一個序列上迭代。 與Python的?for?語句的情形類似,循環語法是?for?X?in?Y?,Y是要迭代的序列而X是在每一個特定的循環中使用的變量名稱。 每一次循環中,模板系統會渲染在?{%?for?%}?和{%?endfor?%}?之間的所有內容。
例如,給定一個運動員列表?athlete_list?變量,我們可以使用下面的代碼來顯示這個列表:
~~~
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
~~~
給標簽增加一個?reversed?使得該列表被反向迭代:
~~~
{% for athlete in athlete_list reversed %}
...
{% endfor %}
~~~
可以嵌套使用?{%?for?%}?標簽:
~~~
{% for athlete in athlete_list %}
<h1>{{ athlete.name }}</h1>
<ul>
{% for sport in athlete.sports_played %}
<li>{{ sport }}</li>
{% endfor %}
</ul>
{% endfor %}
~~~
在執行循環之前先檢測列表的大小是一個通常的做法,當列表為空時輸出一些特別的提示。
~~~
{% if athlete_list %}
{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% endfor %}
{% else %}
<p>There are no athletes. Only computer programmers.</p>
{% endif %}
~~~
因為這種做法十分常見,所以`` for`` 標簽支持一個可選的`` {% empty %}`` 分句,通過它我們可以定義當列表為空時的輸出內容 下面的例子與之前那個等價:
~~~
{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% empty %}
<p>There are no athletes. Only computer programmers.</p>
{% endfor %}
~~~
Django不支持退出循環操作。 如果我們想退出循環,可以改變正在迭代的變量,讓其僅僅包含需要迭代的項目。 同理,Django也不支持continue語句,我們無法讓當前迭代操作跳回到循環頭部。 (請參看本章稍后的理念和限制小節,了解下決定這個設計的背后原因)
在每個`` {% for %}``循環里有一個稱為`` forloop`` 的模板變量。這個變量有一些提示循環進度信息的屬性。
> forloop.counter?總是一個表示當前循環的執行次數的整數計數器。 這個計數器是從1開始的,所以在第一次循環時?forloop.counter?將會被設置為1。
~~~
{% for item in todo_list %}
<p>{{ forloop.counter }}: {{ item }}</p>
{% endfor %}
~~~
> forloop.counter0?類似于?forloop.counter?,但是它是從0計數的。 第一次執行循環時這個變量會被設置為0。
>
> forloop.revcounter?是表示循環中剩余項的整型變量。 在循環初次執行時?forloop.revcounter?將被設置為序列中項的總數。 最后一次循環執行中,這個變量將被置1。
>
> forloop.revcounter0?類似于?forloop.revcounter?,但它以0做為結束索引。 在第一次執行循環時,該變量會被置為序列的項的個數減1。
>
> forloop.first?是一個布爾值,如果該迭代是第一次執行,那么它被置為[``](http://docs.30c.org/djangobook2/chapter04/index.html#id12)`` 在下面的情形中這個變量是很有用的:
>
>
>
> System Message: WARNING/2 (, line 1071);?_[backlink](http://docs.30c.org/djangobook2/chapter04/index.html#id13)_
>
> Inline literal start-string without end-string.
>
>
~~~
{% for object in objects %}
{% if forloop.first %}<li class="first">{% else %}<li>{% endif %}
{{ object }}
</li>
{% endfor %}r %}
~~~
> forloop.last?是一個布爾值;在最后一次執行循環時被置為True。 一個常見的用法是在一系列的鏈接之間放置管道符(|)
~~~
{% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %}
~~~
> 上面的模板可能會產生如下的結果:
~~~
Link1 | Link2 | Link3 | Link4
~~~
> 另一個常見的用途是為列表的每個單詞的加上逗號。
~~~
Favorite places:
{% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}
~~~
> forloop.parentloop?是一個指向當前循環的上一級循環的?forloop?對象的引用(在嵌套循環的情況下)。 例子在此:
~~~
{% for country in countries %}
<table>
{% for city in country.city_list %}
<tr>
<td>Country #{{ forloop.parentloop.counter }}</td>
<td>City #{{ forloop.counter }}</td>
<td>{{ city }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
~~~
forloop?變量僅僅能夠在循環中使用。 在模板解析器碰到{%?endfor?%}標簽后,forloop就不可訪問了。
Context和forloop變量
在一個?{%?for?%}?塊中,已存在的變量會被移除,以避免?forloop?變量被覆蓋。 Django會把這個變量移動到forloop.parentloop?中。通常我們不用擔心這個問題,但是一旦我們在模板中定義了?forloop?這個變量(當然我們反對這樣做),在?{%?for?%}?塊中它會在?forloop.parentloop?被重新命名。
#### ifequal/ifnotequal
Django模板系統壓根兒就沒想過實現一個全功能的編程語言,所以它不允許我們在模板中執行Python的語句(還是那句話,要了解更多請參看理念和限制小節)。 但是比較兩個變量的值并且顯示一些結果實在是個太常見的需求了,所以Django提供了?{%?ifequal?%}?標簽供我們使用。
{%?ifequal?%}?標簽比較兩個值,當他們相等時,顯示在?{%?ifequal?%}?和?{%?endifequal?%}?之中所有的值。
下面的例子比較兩個模板變量?user?和?currentuser?:
~~~
{% ifequal user currentuser %}
<h1>Welcome!</h1>
{% endifequal %}
~~~
參數可以是硬編碼的字符串,隨便用單引號或者雙引號引起來,所以下列代碼都是正確的:
~~~
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% endifequal %}
{% ifequal section "community" %}
<h1>Community</h1>
{% endifequal %}
~~~
和?{%?if?%}?類似,?{%?ifequal?%}?支持可選的?{%?else%}?標簽:
~~~
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% else %}
<h1>No News Here</h1>
{% endifequal %}
~~~
只有模板變量,字符串,整數和小數可以作為?{%?ifequal?%}?標簽的參數。下面是合法參數的例子:
~~~
{% ifequal variable 1 %}
{% ifequal variable 1.23 %}
{% ifequal variable 'foo' %}
{% ifequal variable "foo" %}
~~~
其他任何類型,例如Python的字典類型、列表類型、布爾類型,不能用在?{%?ifequal?%}?中。 下面是些錯誤的例子:
~~~
{% ifequal variable True %}
{% ifequal variable [1, 2, 3] %}
{% ifequal variable {'key': 'value'} %}
~~~
如果你需要判斷變量是真還是假,請使用?{%?if?%}?來替代?{%?ifequal?%}?。
#### 注釋
就像HTML或者Python,Django模板語言同樣提供代碼注釋。 注釋使用?{#?#}?:
~~~
{# This is a comment #}
~~~
注釋的內容不會在模板渲染時輸出。
用這種語法的注釋不能跨越多行。 這個限制是為了提高模板解析的性能。 在下面這個模板中,輸出結果和模板本身是 完全一樣的(也就是說,注釋標簽并沒有被解析為注釋):
~~~
This is a {# this is not
a comment #}
test.
~~~
如果要實現多行注釋,可以使用`` {% comment %}`` 模板標簽,就像這樣:
~~~
{% comment %}
This is a
multi-line comment.
{% endcomment %}
~~~
### 過濾器
就象本章前面提到的一樣,模板過濾器是在變量被顯示前修改它的值的一個簡單方法。 過濾器使用管道字符,如下所示:
~~~
{{ name|lower }}
~~~
顯示的內容是變量?{{?name?}}?被過濾器?lower?處理后的結果,它功能是轉換文本為小寫。
過濾管道可以被* 套接* ,既是說,一個過濾器管道的輸出又可以作為下一個管道的輸入,如此下去。 下面的例子實現查找列表的第一個元素并將其轉化為大寫。
~~~
{{ my_list|first|upper }}
~~~
有些過濾器有參數。 過濾器的參數跟隨冒號之后并且總是以雙引號包含。 例如:
~~~
{{ bio|truncatewords:"30" }}
~~~
這個將顯示變量?bio?的前30個詞。
以下幾個是最為重要的過濾器的一部分。 附錄F包含其余的過濾器。
> addslashes?: 添加反斜杠到任何反斜杠、單引號或者雙引號前面。 這在處理包含JavaScript的文本時是非常有用的。
>
> date?: 按指定的格式字符串參數格式化?date?或者?datetime?對象, 范例:
~~~
{{ pub_date|date:"F j, Y" }}
~~~
> 格式參數的定義在附錄F中。
>
> length?: 返回變量的長度。 對于列表,這個參數將返回列表元素的個數。 對于字符串,這個參數將返回字符串中字符的個數。 你可以對列表或者字符串,或者任何知道怎么測定長度的Python 對象使用這個方法(也就是說,有?__len__()?方法的對象)。
## 理念與局限
現在你已經對Django的模板語言有一些認識了,我們將指出一些特意設置的限制和為什么要這樣做 背后的一些設計哲學。
相對與其他的網絡應用的組件,模板的語法很具主觀性,因此可供程序員的選擇方案也很廣泛。 事實上,Python有成十上百的 開放源碼的模板語言實現。 每個實現都是因為開發者認為現存的模板語言不夠用。 (事實上,對一個 Python開發者來說,寫一個自己的模板語言就象是某種“成人禮”一樣! 如果你還沒有完成一個自己的 模板語言,好好考慮寫一個,這是一個非常有趣的鍛煉。 )
明白了這個,你也許有興趣知道事實上Django并不強制要求你必須使用它的模板語言。 因為Django 雖然被設計成一個FULL-Stack的Web框架,它提供了開發者所必需的所有組件,而且在大多數情況 使用Django模板系統會比其他的Python模板庫要?_更方便_?一點,但是并不是嚴格要求你必須使用 它。 你將在后續的“視圖中應用模板”這一章節中看到,你還可以非常容易地在Django中使用其他的模板語言。
雖然如此,很明顯,我們對Django模板語言的工作方式有著強烈的偏愛。 這個模板語言來源于World Online的開發經驗和Django創造者們集體智慧的結晶。 下面是關于它的一些設計哲學理念:
> _業務邏輯應該和表現邏輯相對分開_?。我們將模板系統視為控制表現及表現相關邏輯的工具,僅此而已。 模板系統不應提供超出此基本目標的功能。
>
> 出于這個原因,在 Django 模板中是不可能直接調用 Python 代碼的。 所有的編程工作基本上都被局限于模板標簽的能力范圍。 當然,?_是_?有可能寫出自定義的模板標簽來完成任意工作,但這些“超范圍”的 Django 模板標簽有意地不允許執行任何 Python 代碼。
>
> _語法不應受到 HTML/XML 的束縛_?。盡管 Django 模板系統主要用于生成 HTML,它還是被有意地設計為可生成非 HTML 格式,如純文本。 一些其它的模板語言是基于 XML 的,將所有的模板邏輯置于 XML 標簽與屬性之中,而 Django 有意地避開了這種限制。 強制要求使用有效 XML 編寫模板將會引發大量的人為錯誤和難以理解的錯誤信息,而且使用 XML 引擎解析模板也會導致令人無法容忍的模板處理開銷。
>
> _假定設計師精通 HTML 編碼_?。模板系統的設計意圖并不是為了讓模板一定能夠很好地顯示在 Dreamweaver 這樣的所見即所得編輯器中。 這種限制過于苛刻,而且會使得語法不能像目前這樣的完美。 Django 要求模板創作人員對直接編輯 HTML 非常熟悉。
>
> _假定設計師不是 Python 程序員_?。模板系統開發人員認為:模板通常由設計師而非程序員來編寫,因此不應被假定擁有Python開發知識。
>
> 當然,系統同樣也特意地提供了對那些?_由_?Python 程序員進行模板制作的小型團隊的支持。 它提供了一種工作模式,允許通過編寫原生 Python 代碼進行系統語法拓展。 (詳見第十章)
>
> _目標并不是要發明一種編程語言_?。目標是恰到好處地提供如分支和循環這一類編程式功能,這是進行與表現相關判斷的基礎。
## 在視圖中使用模板
在學習了模板系統的基礎之后,現在讓我們使用相關知識來創建視圖。 重新打開我們在前一章在?mysite.views中創建的?current_datetime?視圖。 以下是其內容:
~~~
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
~~~
讓我們用 Django 模板系統來修改該視圖。 第一步,你可能已經想到了要做下面這樣的修改:
~~~
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = Template("<html><body>It is now {{ current_date }}.</body></html>")
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
~~~
沒錯,它確實使用了模板系統,但是并沒有解決我們在本章開頭所指出的問題。 也就是說,模板仍然嵌入在Python代碼里,并未真正的實現數據與表現的分離。 讓我們將模板置于一個?_單獨的文件_?中,并且讓視圖加載該文件來解決此問題。
你可能首先考慮把模板保存在文件系統的某個位置并用 Python 內建的文件操作函數來讀取文件內容。 假設文件保存在?/home/djangouser/templates/mytemplate.html?中的話,代碼就會像下面這樣:
~~~
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
# Simple way of using templates from the filesystem.
# This is BAD because it doesn't account for missing files!
fp = open('/home/djangouser/templates/mytemplate.html')
t = Template(fp.read())
fp.close()
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
~~~
然而,基于以下幾個原因,該方法還算不上簡潔:
* 它沒有對文件丟失的情況做出處理。 如果文件?mytemplate.html?不存在或者不可讀,?open()?函數調用將會引發?IOError?異常。
* 這里對模板文件的位置進行了硬編碼。 如果你在每個視圖函數都用該技術,就要不斷復制這些模板的位置。 更不用說還要帶來大量的輸入工作!
* 它包含了大量令人生厭的重復代碼。 與其在每次加載模板時都調用?open()?、?fp.read()?和?fp.close()?,還不如做出更佳選擇。
為了解決這些問題,我們采用了?_模板自加載_?跟?_模板目錄_?的技巧.
## 模板加載
為了減少模板加載調用過程及模板本身的冗余代碼,Django 提供了一種使用方便且功能強大的 API ,用于從磁盤中加載模板,
要使用此模板加載API,首先你必須將模板的保存位置告訴框架。 設置的保存文件就是我們前一章節講述ROOT_URLCONF配置的時候提到的?settings.py。
如果你是一步步跟隨我們學習過來的,馬上打開你的settings.py配置文件,找到TEMPLATE_DIRS這項設置吧。 它的默認設置是一個空元組(tuple),加上一些自動生成的注釋。
~~~
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
~~~
該設置告訴 Django 的模板加載機制在哪里查找模板。 選擇一個目錄用于存放模板并將其添加到TEMPLATE_DIRS?中:
~~~
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
~~~
下面是一些注意事項:
> 你可以任意指定想要的目錄,只要運行 Web 服務器的用戶可以讀取該目錄的子目錄和模板文件。 如果實在想不出合適的位置來放置模板,我們建議在 Django 項目中創建一個?templates?目錄(也就是說,如果你一直都按本書的范例操作的話,在第二章創建的?mysite?目錄中)。
>
> 如果你的?TEMPLATE_DIRS只包含一個目錄,別忘了在該目錄后加上個逗號。
>
> Bad:
~~~
# Missing comma!
TEMPLATE_DIRS = (
'/home/django/mysite/templates'
)
> Good:
# Comma correctly in place.
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
~~~
> Python 要求單元素元組中必須使用逗號,以此消除與圓括號表達式之間的歧義。 這是新手常犯的錯誤。
>
> 如果使用的是 Windows 平臺,請包含驅動器符號并使用Unix風格的斜杠(/)而不是反斜杠(),就像下面這樣:
~~~
TEMPLATE_DIRS = (
'C:/www/django/templates',
)
~~~
> 最省事的方式是使用絕對路徑(即從文件系統根目錄開始的目錄路徑)。 如果想要更靈活一點并減少一些負面干擾,可利用 Django 配置文件就是 Python 代碼這一點來動態構建?TEMPLATE_DIRS?的內容,如: 例如:
~~~
import os.path
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)
~~~
> 這個例子使用了神奇的 Python 內部變量?__file__?,該變量被自動設置為代碼所在的 Python 模塊文件名。 `` os.path.dirname(__file__)`` 將會獲取自身所在的文件,即settings.py?所在的目錄,然后由os.path.join?這個方法將這目錄與?templates?進行連接。如果在windows下,它會智能地選擇正確的后向斜杠”“進行連接,而不是前向斜杠”/”。
>
> 在這里我們面對的是動態語言python代碼,我需要提醒你的是,不要在你的設置文件里寫入錯誤的代碼,這很重要。 如果你在這里引入了語法錯誤,或運行錯誤,你的Django-powered站點將很可能就要被崩潰掉。
完成?TEMPLATE_DIRS?設置后,下一步就是修改視圖代碼,讓它使用 Django 模板加載功能而不是對模板路徑硬編碼。 返回?current_datetime?視圖,進行如下修改:
~~~
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = get_template('current_datetime.html')
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
~~~
此范例中,我們使用了函數?django.template.loader.get_template()?,而不是手動從文件系統加載模板。 該get_template()?函數以模板名稱為參數,在文件系統中找出模塊的位置,打開文件并返回一個編譯好的Template?對象。
在這個例子里,我們選擇的模板文件是current_datetime.html,但這個與.html后綴沒有直接的聯系。 你可以選擇任意后綴的任意文件,只要是符合邏輯的都行。甚至選擇沒有后綴的文件也不會有問題。
要確定某個模板文件在你的系統里的位置,?get_template()方法會自動為你連接已經設置的?TEMPLATE_DIRS目錄和你傳入該法的模板名稱參數。比如,你的?TEMPLATE_DIRS目錄設置為?'/home/django/mysite/templates',上面的?get_template()調用就會為你找到?/home/django/mysite/templates/current_datetime.html?這樣一個位置。
如果?get_template()?找不到給定名稱的模板,將會引發一個?TemplateDoesNotExist?異常。 要了解究竟會發生什么,讓我們按照第三章內容,在 Django 項目目錄中運行?python?manage.py?runserver?命令,再次啟動Django開發服務器。 接著,告訴你的瀏覽器,使其定位到指定頁面以激活current_datetime視圖(如http://127.0.0.1:8000/time/?)。假設你的?DEBUG項設置為?True,而你有沒有建立current_datetime.html?這個模板文件,你會看到Django的錯誤提示網頁,告訴你發生了?TemplateDoesNotExist?錯誤。

圖 4-1: 模板文件無法找到時,將會發送提示錯誤的網頁給用戶。
該頁面與我們在第三章解釋過的錯誤頁面相似,只不過多了一塊調試信息區: 模板加載器事后檢查區。 該區域顯示 Django 要加載哪個模板、每次嘗試出錯的原因(如:文件不存在等)。 當你嘗試調試模板加載錯誤時,這些信息會非常有幫助。
接下來,在模板目錄中創建包括以下模板代碼?current_datetime.html?文件:
~~~
<html><body>It is now {{ current_date }}.</body></html>
~~~
在網頁瀏覽器中刷新該頁,你將會看到完整解析后的頁面。
### render_to_response()
我們已經告訴你如何載入一個模板文件,然后用?Context渲染它,最后返回這個處理好的HttpResponse對象給用戶。 我們已經優化了方案,使用?get_template()?方法代替繁雜的用代碼來處理模板及其路徑的工作。 但這仍然需要一定量的時間來敲出這些簡化的代碼。 這是一個普遍存在的重復苦力勞動。Django為此提供了一個捷徑,讓你一次性地載入某個模板文件,渲染它,然后將此作為?HttpResponse返回。
該捷徑就是位于?django.shortcuts?模塊中名為?render_to_response()?的函數。大多數情況下,你會使用[``](http://docs.30c.org/djangobook2/chapter04/index.html#id19)\?``[``](http://docs.30c.org/djangobook2/chapter04/index.html#id21)[``](http://docs.30c.org/djangobook2/chapter04/index.html#id23)對象,除非你的老板以代碼行數來衡量你的工作。
System Message: WARNING/2 (, line 1736);?_[backlink](http://docs.30c.org/djangobook2/chapter04/index.html#id20)_
Inline literal start-string without end-string.
System Message: WARNING/2 (, line 1736);?_[backlink](http://docs.30c.org/djangobook2/chapter04/index.html#id22)_
Inline literal start-string without end-string.
System Message: WARNING/2 (, line 1736);?_[backlink](http://docs.30c.org/djangobook2/chapter04/index.html#id24)_
Inline literal start-string without end-string.
下面就是使用?render_to_response()?重新編寫過的?current_datetime?范例。
~~~
from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': now})
~~~
大變樣了! 讓我們逐句看看代碼發生的變化:
* 我們不再需要導入?get_template?、?Template?、?Context?和?HttpResponse?。相反,我們導入django.shortcuts.render_to_response?。?import?datetime?繼續保留.
* 在?current_datetime?函數中,我們仍然進行?now?計算,但模板加載、上下文創建、模板解析和HttpResponse?創建工作均在對?render_to_response()?的調用中完成了。 由于?render_to_response()?返回?HttpResponse?對象,因此我們僅需在視圖中?return?該值。
render_to_response()?的第一個參數必須是要使用的模板名稱。 如果要給定第二個參數,那么該參數必須是為該模板創建?Context?時所使用的字典。 如果不提供第二個參數,?render_to_response()?使用一個空字典。
### locals() 技巧
思考一下我們對?current_datetime?的最后一次賦值:
~~~
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': now})
~~~
很多時候,就像在這個范例中那樣,你發現自己一直在計算某個變量,保存結果到變量中(比如前面代碼中的 now ),然后將這些變量發送給模板。 尤其喜歡偷懶的程序員應該注意到了,不斷地為臨時變量_和_臨時模板命名有那么一點點多余。 不僅多余,而且需要額外的輸入。
如果你是個喜歡偷懶的程序員并想讓代碼看起來更加簡明,可以利用 Python 的內建函數?locals()?。它返回的字典對所有局部變量的名稱與值進行映射。 因此,前面的視圖可以重寫成下面這個樣子:
~~~
def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
~~~
在此,我們沒有像之前那樣手工指定 context 字典,而是傳入了?locals()?的值,它囊括了函數執行到該時間點時所定義的一切變量。 因此,我們將?now?變量重命名為?current_date?,因為那才是模板所預期的變量名稱。 在本例中,?locals()?并沒有帶來多?_大_?的改進,但是如果有多個模板變量要界定而你又想偷懶,這種技術可以減少一些鍵盤輸入。
使用?locals()?時要注意是它將包括?_所有_?的局部變量,它們可能比你想讓模板訪問的要多。 在前例中,locals()?還包含了?request?。對此如何取舍取決你的應用程序。
### get_template()中使用子目錄
把所有的模板都存放在一個目錄下可能會讓事情變得難以掌控。 你可能會考慮把模板存放在你模板目錄的子目錄中,這非常好。 事實上,我們推薦這樣做;一些Django的高級特性(例如將在第十一章講到的通用視圖系統)的缺省約定就是期望使用這種模板布局。
把模板存放于模板目錄的子目錄中是件很輕松的事情。 只需在調用?get_template()?時,把子目錄名和一條斜杠添加到模板名稱之前,如:
~~~
t = get_template('dateapp/current_datetime.html')
~~~
由于?render_to_response()?只是對?get_template()?的簡單封裝, 你可以對?render_to_response()?的第一個參數做相同處理。
~~~
return render_to_response('dateapp/current_datetime.html', {'current_date': now})
~~~
對子目錄樹的深度沒有限制,你想要多少層都可以。 只要你喜歡,用多少層的子目錄都無所謂。
> 注意
> Windows用戶必須使用斜杠而不是反斜杠。?get_template()?假定的是 Unix 風格的文件名符號約定。
### include?模板標簽
在講解了模板加載機制之后,我們再介紹一個利用該機制的內建模板標簽:?{%?include?%}?。該標簽允許在(模板中)包含其它的模板的內容。 標簽的參數是所要包含的模板名稱,可以是一個變量,也可以是用單/雙引號硬編碼的字符串。 每當在多個模板中出現相同的代碼時,就應該考慮是否要使用?{%?include?%}?來減少重復。
下面這兩個例子都包含了?nav.html?模板。這兩個例子是等價的,它們證明單/雙引號都是允許的。
~~~
{% include 'nav.html' %}
{% include "nav.html" %}
~~~
下面的例子包含了?includes/nav.html?模板的內容:
~~~
{% include 'includes/nav.html' %}
~~~
下面的例子包含了以變量?template_name?的值為名稱的模板內容:
~~~
{% include template_name %}
~~~
和在?get_template()?中一樣, 對模板的文件名進行判斷時會在所調取的模板名稱之前加上來自?TEMPLATE_DIRS的模板目錄。
所包含的模板執行時的 context 和包含它們的模板是一樣的。 舉例說,考慮下面兩個模板文件:
~~~
# mypage.html
<html>
<body>
{% include "includes/nav.html" %}
<h1>{{ title }}</h1>
</body>
</html>
# includes/nav.html
<div id="nav">
You are in: {{ current_section }}
</div>
~~~
如果你用一個包含?current_section的上下文去渲染?mypage.html這個模板文件,這個變量將存在于它所包含(include)的模板里,就像你想象的那樣。
如果{%?include?%}標簽指定的模板沒找到,Django將會在下面兩個處理方法中選擇一個:
* 如果?DEBUG?設置為?True?,你將會在 Django 錯誤信息頁面看到?TemplateDoesNotExist?異常。
* 如果?DEBUG?設置為?False?,該標簽不會引發錯誤信息,在標簽位置不顯示任何東西。
## 模板繼承
到目前為止,我們的模板范例都只是些零星的 HTML 片段,但在實際應用中,你將用 Django 模板系統來創建整個 HTML 頁面。 這就帶來一個常見的 Web 開發問題: 在整個網站中,如何減少共用頁面區域(比如站點導航)所引起的重復和冗余代碼?
解決該問題的傳統做法是使用?_服務器端的 includes_?,你可以在 HTML 頁面中使用該指令將一個網頁嵌入到另一個中。 事實上, Django 通過剛才講述的?{%?include?%}?支持了這種方法。 但是用 Django 解決此類問題的首選方法是使用更加優雅的策略——?_模板繼承_?。
本質上來說,模板繼承就是先構造一個基礎框架模板,而后在其子模板中對它所包含站點公用部分和定義塊進行重載。
讓我們通過修改?current_datetime.html?文件,為?current_datetime?創建一個更加完整的模板來體會一下這種做法:
~~~
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>The current time</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
<p>It is now {{ current_date }}.</p>
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
~~~
這看起來很棒,但如果我們要為第三章的?hours_ahead?視圖創建另一個模板會發生什么事情呢?
~~~
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>Future time</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
~~~
很明顯,我們剛才重復了大量的 HTML 代碼。 想象一下,如果有一個更典型的網站,它有導航條、樣式表,可能還有一些 JavaScript 代碼,事情必將以向每個模板填充各種冗余的 HTML 而告終。
解決這個問題的服務器端 include 方案是找出兩個模板中的共同部分,將其保存為不同的模板片段,然后在每個模板中進行 include。 也許你會把模板頭部的一些代碼保存為?header.html?文件:
~~~
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
~~~
你可能會把底部保存到文件?footer.html?:
~~~
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
~~~
對基于 include 的策略,頭部和底部的包含很簡單。 麻煩的是中間部分。 在此范例中,每個頁面都有一個My?helpful?timestamp?site?標題,但是這個標題不能放在?header.html?中,因為每個頁面的?是不同的。 如果我們將??包含在頭部,我們就不得不包含??,但這樣又不允許在每個頁面對它進行定制。 何去何從呢?
Django 的模板繼承系統解決了這些問題。 你可以將其視為服務器端 include 的逆向思維版本。 你可以對那些_不同_?的代碼段進行定義,而不是?_共同_?代碼段。
第一步是定義?_基礎模板_?, 該框架之后將由?_子模板_?所繼承。 以下是我們目前所講述范例的基礎模板:
~~~
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
{% block content %}{% endblock %}
{% block footer %}
<hr>
<p>Thanks for visiting my site.</p>
{% endblock %}
</body>
</html>
~~~
這個叫做?base.html?的模板定義了一個簡單的 HTML 框架文檔,我們將在本站點的所有頁面中使用。 子模板的作用就是重載、添加或保留那些塊的內容。 (如果你一直按順序學習到這里,保存這個文件到你的template目錄下,命名為?base.html?.)
我們使用一個以前已經見過的模板標簽:?{%?block?%}?。 所有的?{%?block?%}?標簽告訴模板引擎,子模板可以重載這些部分。 每個{%?block?%}標簽所要做的是告訴模板引擎,該模板下的這一塊內容將有可能被子模板覆蓋。
現在我們已經有了一個基本模板,我們可以修改?current_datetime.html?模板來 使用它:
~~~
{% extends "base.html" %}
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
~~~
再為?hours_ahead?視圖創建一個模板,看起來是這樣的:
~~~
{% extends "base.html" %}
{% block title %}Future time{% endblock %}
{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}
~~~
看起來很漂亮是不是? 每個模板只包含對自己而言?_獨一無二_?的代碼。 無需多余的部分。 如果想進行站點級的設計修改,僅需修改?base.html?,所有其它模板會立即反映出所作修改。
以下是其工作方式。 在加載?current_datetime.html?模板時,模板引擎發現了?{%?extends?%}?標簽, 注意到該模板是一個子模板。 模板引擎立即裝載其父模板,即本例中的?base.html?。
此時,模板引擎注意到?base.html?中的三個?{%?block?%}?標簽,并用子模板的內容替換這些 block 。因此,引擎將會使用我們在?{?block?title?%}?中定義的標題,對?{%?block?content?%}?也是如此。 所以,網頁標題一塊將由?{%?block?title?%}替換,同樣地,網頁的內容一塊將由?{%?block?content?%}替換。
注意由于子模板并沒有定義?footer?塊,模板系統將使用在父模板中定義的值。 父模板?{%?block?%}?標簽中的內容總是被當作一條退路。
繼承并不會影響到模板的上下文。 換句話說,任何處在繼承樹上的模板都可以訪問到你傳到模板中的每一個模板變量。
你可以根據需要使用任意多的繼承次數。 使用繼承的一種常見方式是下面的三層法:
1. 創建?base.html?模板,在其中定義站點的主要外觀感受。 這些都是不常修改甚至從不修改的部分。
1. 為網站的每個區域創建?base_SECTION.html?模板(例如,?base_photos.html?和?base_forum.html?)。這些模板對?base.html?進行拓展,并包含區域特定的風格與設計。
1. 為每種類型的頁面創建獨立的模板,例如論壇頁面或者圖片庫。 這些模板拓展相應的區域模板。
這個方法可最大限度地重用代碼,并使得向公共區域(如區域級的導航)添加內容成為一件輕松的工作。
以下是使用模板繼承的一些訣竅:
* 如果在模板中使用?{%?extends?%}?,必須保證其為模板中的第一個模板標記。 否則,模板繼承將不起作用。
* 一般來說,基礎模板中的?{%?block?%}?標簽越多越好。 記住,子模板不必定義父模板中所有的代碼塊,因此你可以用合理的缺省值對一些代碼塊進行填充,然后只對子模板所需的代碼塊進行(重)定義。 俗話說,鉤子越多越好。
* 如果發覺自己在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的某個?{%?block?%}?中。
* 如果你需要訪問父模板中的塊的內容,使用?{{?block.super?}}這個標簽吧,這一個魔法變量將會表現出父模板中的內容。 如果只想在上級代碼塊基礎上添加內容,而不是全部重載,該變量就顯得非常有用了。
* 不允許在同一個模板中定義多個同名的?{%?block?%}?。 存在這樣的限制是因為block 標簽的工作方式是雙向的。 也就是說,block 標簽不僅挖了一個要填的坑,也定義了在_父_模板中這個坑所填充的內容。如果模板中出現了兩個相同名稱的?_{% block %}_?標簽,父模板將無從得知要使用哪個塊的內容。
* {%?extends?%}?對所傳入模板名稱使用的加載方法和?get_template()?相同。 也就是說,會將模板名稱被添加到?TEMPLATE_DIRS?設置之后。
* 多數情況下,?{%?extends?%}?的參數應該是字符串,但是如果直到運行時方能確定父模板名,這個參數也可以是個變量。 這使得你能夠實現一些很酷的動態功能。
## 下一章
你現在已經掌握了模板系統的基本知識。 接下來呢?
時下大多數網站都是?_數據庫驅動_?的:網站的內容都是存儲在關系型數據庫中。 這使得數據和邏輯能夠徹底地分開(視圖和模板也以同樣方式對邏輯和顯示進行了分隔。)
下一章將講述如何與數據庫打交道。