在前一章中,你可能已經(jīng)注意到我們在例子視圖中返回文本的方式有點特別。 也就是說,HTML被直接硬編碼在 Python 代碼之中。
def current_datetime(request):
now = datetime.datetime.now()
html = "It is now %s." % now
return HttpResponse(html)
盡管這種技術(shù)便于解釋視圖是如何工作的,但直接將HTML硬編碼到你的視圖里卻并不是一個好主意。 讓我們來看一下為什么:
對頁面設(shè)計進(jìn)行的任何改變都必須對 Python 代碼進(jìn)行相應(yīng)的修改。 站點設(shè)計的修改往往比底層 Python 代碼的修改要頻繁得多,因此如果可以在不進(jìn)行 Python 代碼修改的情況下變更設(shè)計,那將會方便得多。
Python 代碼編寫和 HTML 設(shè)計是兩項不同的工作,大多數(shù)專業(yè)的網(wǎng)站開發(fā)環(huán)境都將他們分配給不同的人員(甚至不同部門)來完成。 設(shè)計者和HTML/CSS的編碼人員不應(yīng)該被要求去編輯Python的代碼來完成他們的工作。
基于這些原因,將頁面的設(shè)計和Python的代碼分離開會更干凈簡潔更容易維護(hù)。 我們可以使用 Django的 模板系統(tǒng) (Template System)來實現(xiàn)這種模式,這就是本章要具體討論的問題。
模板是一個文本,用于分離文檔的表現(xiàn)形式和內(nèi)容。 模板定義了占位符以及各種用于規(guī)范文檔該如何顯示的各部分基本邏輯(模板標(biāo)簽)。 模板通常用于產(chǎn)生HTML,但是Django的模板也能產(chǎn)生任何基于文本格式的文檔。
讓我們從一個簡單的例子模板開始。 該模板描述了一個向某個與公司簽單人員致謝 HTML 頁面。 可將其視為一個格式信函:
<html>
<head><title>Ordering noticetitle>head>
<body>
<h1>Ordering noticeh1>
<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>
該模板是一段添加了些許變量和模板標(biāo)簽的基礎(chǔ) HTML 。 讓我們逐步分析一下:
用兩個大括號括起來的文字(例如 {{ person_name }} )稱為 變量(variable) 。這意味著在此處插入指定變量的值。 如何指定變量的值呢? 稍后就會說明。
被大括號和百分號包圍的文本(例如 {% if ordered_warranty %} )是 模板標(biāo)簽(template tag) 。標(biāo)簽(tag)定義比較明確,即: 僅通知模板系統(tǒng)完成某些工作的標(biāo)簽。
這個例子中的模板包含一個for標(biāo)簽( {% for item in item_list %} )和一個if 標(biāo)簽({% if ordered_warranty %} )
for標(biāo)簽類似Python的for語句,可讓你循環(huán)訪問序列里的每一個項目。 if 標(biāo)簽,正如你所料,是用來執(zhí)行邏輯判斷的。 在這里,tag標(biāo)簽檢查ordered_warranty值是否為True。如果是,模板系統(tǒng)將顯示{% if ordered_warranty %}和{% else %}之間的內(nèi)容;否則將顯示{% else %}和{% endif %}之間的內(nèi)容。{% else %}是可選的。
最后,這個模板的第二段中有一個關(guān)于_filter_過濾器的例子,它是一種最便捷的轉(zhuǎn)換變量輸出格式的方式。 如這個例子中的{{ship_date|date:”F j, Y” }},我們將變量ship_date傳遞給date過濾器,同時指定參數(shù)”F j,Y”。date過濾器根據(jù)參數(shù)進(jìn)行格式輸出。 過濾器是用管道符(|)來調(diào)用的,具體可以參見Unix管道符。
Django 模板含有很多內(nèi)置的tags和filters,我們將陸續(xù)進(jìn)行學(xué)習(xí). 附錄F列出了很多的tags和filters的列表,熟悉這些列表對你來說是個好建議. 你依然可以利用它創(chuàng)建自己的tag和filters。這些我們在第9章會講到。
讓我們深入研究模板系統(tǒng),你將會明白它是如何工作的。但我們暫不打算將它與先前創(chuàng)建的視圖結(jié)合在一起,因為我們現(xiàn)在的目的是了解它是如何獨立工作的。 。 (換言之, 通常你會將模板和視圖一起使用,但是我們只是想突出模板系統(tǒng)是一個Python庫,你可以在任何地方使用它,而不僅僅是在Django視圖中。)
在Python代碼中使用Django模板的最基本方式如下:
可以用原始的模板代碼字符串創(chuàng)建一個 Template 對象, Django同樣支持用指定模板文件路徑的方式來創(chuàng)建 Template 對象;
代碼如下:
>>> 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.
以下部分逐步的詳細(xì)介紹
創(chuàng)建一個 Template 對象最簡單的方法就是直接實例化它。 Template 類就在 django.template 模塊中,構(gòu)造函數(shù)接受一個參數(shù),原始模板代碼。 讓我們深入挖掘一下 Python的解釋器看看它是怎么工作的。
轉(zhuǎn)到project目錄(在第二章由 django-admin.py startproject 命令創(chuàng)建), 輸入命令python manage.py shell 啟動交互界面。
一個特殊的Python提示符
如果你曾經(jīng)使用過Python,你一定好奇,為什么我們運行python manage.py shell而不是python。這兩個命令都會啟動交互解釋器,但是manage.py shell命令有一個重要的不同: 在啟動解釋器之前,它告訴Django使用哪個設(shè)置文件。 Django框架的大部分子系統(tǒng),包括模板系統(tǒng),都依賴于配置文件;如果Django不知道使用哪個配置文件,這些系統(tǒng)將不能工作。
如果你想知道,這里將向你解釋它背后是如何工作的。 Django搜索DJANGO_SETTINGS_MODULE環(huán)境變量,它被設(shè)置在settings.py中。例如,假設(shè)mysite在你的Python搜索路徑中,那么DJANGO_SETTINGS_MODULE應(yīng)該被設(shè)置為:’mysite.settings’。
當(dāng)你運行命令:python manage.py shell,它將自動幫你處理DJANGO_SETTINGS_MODULE。 在當(dāng)前的這些示例中,我們鼓勵你使用python manage.py shell
這個方法,這樣可以免去你大費周章地去配置那些你不熟悉的環(huán)境變量。
隨著你越來越熟悉Django,你可能會偏向于廢棄使用manage.py shell
,而是在你的配置文件.bash_profile中手動添加 DJANGO_SETTINGS_MODULE這個環(huán)境變量。
讓我們來了解一些模板系統(tǒng)的基本知識:
>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print t
如果你跟我們一起做,你將會看到下面的內(nèi)容:
0xb7d5f24c 每次都會不一樣,這沒什么關(guān)系;這只是Python運行時 Template 對象的ID。
當(dāng)你創(chuàng)建一個 Template 對象,模板系統(tǒng)在內(nèi)部編譯這個模板到內(nèi)部格式,并做優(yōu)化,做好 渲染的準(zhǔn)備。 如果你的模板語法有錯誤,那么在調(diào)用 Template() 時就會拋出 TemplateSyntaxError 異常:
>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "" , line 1, in ?
...
django.template.TemplateSyntaxError: Invalid block tag: 'notatag'
這里,塊標(biāo)簽(block tag)指向的是{% notatag %}
,塊標(biāo)簽與模板標(biāo)簽是同義的。
系統(tǒng)會在下面的情形拋出 TemplateSyntaxError 異常:
無效的tags
標(biāo)簽的參數(shù)無效
無效的過濾器
過濾器的參數(shù)無效
無效的模板語法
一旦你創(chuàng)建一個 Template 對象,你可以用 context 來傳遞數(shù)據(jù)給它。 一個context是一系列變量和它們值的集合。
context在Django里表現(xiàn)為 Context 類,在 django.template 模塊里。 她的構(gòu)造函數(shù)帶有一個可選的參數(shù): 一個字典映射變量和它們的值。 調(diào)用 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來區(qū)分。 在框架中,Django會一直使用Unicode對象而不是普通的字符串。 如果你明白這樣做給你帶來了多大便利的話,盡可能地感激Django在幕后有條不紊地為你所做這這么多工作吧。 如果不明白你從中獲益了什么,別擔(dān)心。你只需要知道Django對Unicode的支持,將讓你的應(yīng)用程序輕松地處理各式各樣的字符集,而不僅僅是基本的A-Z英文字符。
字典和Contexts
Python的字典數(shù)據(jù)類型就是關(guān)鍵字和它們值的一個映射。 Context 和字典很類似, Context 還提供更多的功能,請看第九章。
變量名必須由英文字符開始 (A-Z或a-z)并可以包含數(shù)字字符、下劃線和小數(shù)點。 (小數(shù)點在這里有特別的用途,稍后我們會講到)變量是大小寫敏感的。
下面是編寫模板并渲染的示例:
>>> from django.template import Template, Context
>>> raw_template = """Dear {{ person_name }},
...
... Thanks for placing an order from {{ company }}. It's scheduled to
... ship on {{ ship_date|date:"F j, Y" }}.
...
... {% if ordered_warranty %}
... Your warranty information will be included in the packaging.
... {% else %}
... You didn't order a warranty, so you're on your own when
... the products inevitably stop working.
... {% endif %}
...
... Sincerely,
{{ company }}
"""
>>> 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"Dear John Smith,
\n\nThanks for placing an order from Outdoor
Equipment. It's scheduled to\nship on April 2, 2009.
\n\n\nYou
didn't order a warranty, so you're on your own when\nthe products
inevitably stop working.
\n\n\nSincerely,
Outdoor Equipment
"
讓我們逐步來分析下這段代碼:
首先我們導(dǎo)入 (import)類 Template 和 Context ,它們都在模塊 django.template 里。
我們把模板原始文本保存到變量 raw_template 。注意到我們使用了三個引號來 標(biāo)識這些文本,因為這樣可以包含多行。
接下來,我們創(chuàng)建了一個模板對象 t ,把 raw_template 作為 Template 類構(gòu)造函數(shù)的參數(shù)。
我們從Python的標(biāo)準(zhǔn)庫導(dǎo)入 datetime 模塊,以后我們將會使用它。
然后,我們創(chuàng)建一個 Context 對象, c 。 Context 構(gòu)造的參數(shù)是Python 字典數(shù)據(jù)類型。 在這里,我們指定參數(shù) person_name 的值是 'John Smith' , 參數(shù)company 的值為 ‘Outdoor Equipment’ ,等等。
最后,我們在模板對象上調(diào)用 render() 方法,傳遞 context參數(shù)給它。 這是返回渲染后的模板的方法,它會替換模板變量為真實的值和執(zhí)行塊標(biāo)簽。
注意,warranty paragraph顯示是因為 ordered_warranty 的值為 True . 注意時間的顯示, April 2, 2009, 它是按 'F j, Y' 格式顯示的。
如果你是Python初學(xué)者,你可能在想為什么輸出里有回車換行的字符('\n' )而不是 顯示回車換行? 因為這是Python交互解釋器的緣故: 調(diào)用 t.render(c) 返回字符串, 解釋器缺省顯示這些字符串的 真實內(nèi)容呈現(xiàn) ,而不是打印這個變量的值。 要顯示換行而不是 '\n' ,使用 print 語句: print t.render(c) 。
這就是使用Django模板系統(tǒng)的基本規(guī)則: 寫模板,創(chuàng)建 Template 對象,創(chuàng)建 Context , 調(diào)用 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,只進(jìn)行 一次模板創(chuàng)建然后多次調(diào)用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 模板解析非常快捷。 大部分的解析工作都是在后臺通過對簡短正則表達(dá)式一次性調(diào)用來完成。 這和基于 XML 的模板引擎形成鮮明對比,那些引擎承擔(dān)了 XML 解析器的開銷,且往往比 Django 模板渲染引擎要慢上幾個數(shù)量級。
在到目前為止的例子中,我們通過 context 傳遞的簡單參數(shù)值主要是字符串,還有一個 datetime.date 范例。 然而,模板系統(tǒng)能夠非常簡潔地處理更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如list、dictionary和自定義的對象。
在 Django 模板中遍歷復(fù)雜數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵是句點字符 (.)。
最好是用幾個例子來說明一下。 比如,假設(shè)你要向模板傳遞一個 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() 方法,你在模板中可以使用同樣的句點語法來調(diào)用它們:
>>> 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'
注意這里調(diào)用方法時并 沒有 使用圓括號 而且也無法給該方法傳遞參數(shù);你只能調(diào)用不需參數(shù)的方法。 (我們將在本章稍后部分解釋該設(shè)計觀。)
最后,句點也可用于訪問列表索引,例如:
>>> 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.'
不允許使用負(fù)數(shù)列表索引。 像 {{ items.-1 }} 這樣的模板變量將會引發(fā)TemplateSyntaxError
Python 列表類型
一點提示: Python的列表是從0開始索引。 第一項的索引是0,第二項的是1,依此類推。
句點查找規(guī)則可概括為: 當(dāng)模板系統(tǒng)在變量名中遇到點時,按照以下順序嘗試進(jìn)行查找:
字典類型查找 (比如 foo["bar"] )
屬性查找 (比如 foo.bar )
方法調(diào)用 (比如 foo.bar() )
系統(tǒng)使用找到的第一個有效類型。 這是一種短路邏輯。
句點查找可以多級深度嵌套。 例如在下面這個例子中 {{person.name.upper}} 會轉(zhuǎn)換成字典類型查找(person['name'] ) 然后是方法調(diào)用( 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.'
方法調(diào)用比其他類型的查找略為復(fù)雜一點。 以下是一些注意事項:
在方法查找過程中,如果某方法拋出一個異常,除非該異常有一個 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 .'
僅在方法無需傳入?yún)?shù)時,其調(diào)用才有效。 否則,系統(tǒng)將會轉(zhuǎn)移到下一個查找類型(列表索引查找)。
顯然,有些方法是有副作用的,好的情況下允許模板系統(tǒng)訪問它們可能只是干件蠢事,壞的情況下甚至?xí)l(fā)安全漏洞。
例如,你的一個 BankAccount 對象有一個 delete() 方法。 如果某個模板中包含了像{{ account.delete }}這樣的標(biāo)簽,其中
account
又是BankAccount 的一個實例,請注意在這個模板載入時,account對象將被刪除。要防止這樣的事情發(fā)生,必須設(shè)置該方法的 alters_data 函數(shù)屬性:
def delete(self):
# Delete the account
delete.alters_data = True
模板系統(tǒng)不會執(zhí)行任何以該方式進(jìn)行標(biāo)記的方法。 接上面的例子,如果模板文件里包含了{(lán){ account.delete }} ,對象又具有 delete()方法,而且delete() 有alters_data=True這個屬性,那么在模板載入時, delete()方法將不會被執(zhí)行。 它將靜靜地錯誤退出。
默認(rèn)情況下,如果一個變量不存在,模板系統(tǒng)會把它展示為空字符串,不做任何事情來表示失敗。 例如:
>>> 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 .'
系統(tǒng)靜悄悄地表示失敗,而不是引發(fā)一個異常,因為這通常是人為錯誤造成的。 這種情況下,因為變量名有錯誤的狀況或名稱, 所有的查詢都會失敗。 現(xiàn)實世界中,對于一個web站點來說,如果僅僅因為一個小的模板語法錯誤而造成無法訪問,這是不可接受的。
多數(shù)時間,你可以通過傳遞一個完全填充(full populated)的字典給 Context() 來初始化 上下文(Context) 。 但是初始化以后,你也可以使用標(biāo)準(zhǔn)的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'
像我們以前提到過的,模板系統(tǒng)帶有內(nèi)置的標(biāo)簽和過濾器。 下面的章節(jié)提供了一個多數(shù)通用標(biāo)簽和過濾器的簡要說明。
{% if %} 標(biāo)簽檢查(evaluate)一個變量,如果這個變量為真(即,變量存在,非空,不是布爾值假),系統(tǒng)會顯示在 {% if %} 和 {% endif %} 之間的任何內(nèi)容,例如:
{% if today_is_weekend %}
Welcome to the weekend!p>
{% endif %}
{% else %} 標(biāo)簽是可選的:
{% if today_is_weekend %}
<p>Welcome to the weekend!p>
{% else %}
<p>Get back to work.p>
{% endif %}
Python 的“真值”
在Python和Django模板系統(tǒng)中,以下這些對象相當(dāng)于布爾值的False
空列表([] )
空元組(() )
空字典({} )
空字符串('' )
零值(0 )
特殊對象None
對象False(很明顯)
除以上幾點以外的所有東西都視為True
{% if %} 標(biāo)簽接受 and , or 或者 not 關(guān)鍵字來對多個變量做判斷 ,或者對變量取反( 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 %} 標(biāo)簽不允許在同一個標(biāo)簽中同時使用 and 和 or ,因為邏輯上可能模糊的,例如,如下示例是錯誤的: 比如這樣的代碼是不合法的:
{% if athlete_list and coach_list or cheerleader_list %}
系統(tǒng)不支持用圓括號來組合比較操作。 如果你確實需要用到圓括號來組合表達(dá)你的邏輯式,考慮將它移到模板之外處理,然后以模板變量的形式傳入結(jié)果吧。 或者,僅僅用嵌套的{% if %}標(biāo)簽替換吧,就像這樣:
{% 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 %} 標(biāo)簽, 請使用嵌套的{% if %}
標(biāo)簽來達(dá)成同樣的效果:
{% 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 %} 關(guān)閉每一個 {% if %} 標(biāo)簽。
{% for %} 允許我們在一個序列上迭代。 與Python的 for 語句的情形類似,循環(huán)語法是 for X in Y ,Y是要迭代的序列而X是在每一個特定的循環(huán)中使用的變量名稱。 每一次循環(huán)中,模板系統(tǒng)會渲染在 {% for %} 和{% endfor %} 之間的所有內(nèi)容。
例如,給定一個運動員列表 athlete_list 變量,我們可以使用下面的代碼來顯示這個列表:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}li>
{% endfor %}
ul>
給標(biāo)簽增加一個 reversed 使得該列表被反向迭代:
{% for athlete in athlete_list reversed %}
...
{% endfor %}
可以嵌套使用 {% for %} 標(biāo)簽:
{% for athlete in athlete_list %}
<h1>{{ athlete.name }}h1>
<ul>
{% for sport in athlete.sports_played %}
<li>{{ sport }}li>
{% endfor %}
ul>
{% endfor %}
在執(zhí)行循環(huán)之前先檢測列表的大小是一個通常的做法,當(dāng)列表為空時輸出一些特別的提示。
{% if athlete_list %}
{% for athlete in athlete_list %}
{{ athlete.name }}
{% endfor %}
{% else %}
There are no athletes. Only computer programmers.
{% endif %}
因為這種做法十分常見,所以for
標(biāo)簽支持一個可選的{% empty %}
分句,通過它我們可以定義當(dāng)列表為空時的輸出內(nèi)容 下面的例子與之前那個等價:
{% for athlete in athlete_list %}
<p>{{ athlete.name }}p>
{% empty %}
<p>There are no athletes. Only computer programmers.p>
{% endfor %}
Django不支持退出循環(huán)操作。 如果我們想退出循環(huán),可以改變正在迭代的變量,讓其僅僅包含需要迭代的項目。 同理,Django也不支持continue語句,我們無法讓當(dāng)前迭代操作跳回到循環(huán)頭部。 (請參看本章稍后的理念和限制小節(jié),了解下決定這個設(shè)計的背后原因)
在每個{% for %}
循環(huán)里有一個稱為forloop
的模板變量。這個變量有一些提示循環(huán)進(jìn)度信息的屬性。
forloop.counter 總是一個表示當(dāng)前循環(huán)的執(zhí)行次數(shù)的整數(shù)計數(shù)器。 這個計數(shù)器是從1開始的,所以在第一次循環(huán)時 forloop.counter 將會被設(shè)置為1。
{% for item in todo_list %}
<p>{{ forloop.counter }}: {{ item }}p>
{% endfor %}
forloop.counter0 類似于 forloop.counter ,但是它是從0計數(shù)的。 第一次執(zhí)行循環(huán)時這個變量會被設(shè)置為0。
forloop.revcounter 是表示循環(huán)中剩余項的整型變量。 在循環(huán)初次執(zhí)行時 forloop.revcounter 將被設(shè)置為序列中項的總數(shù)。 最后一次循環(huán)執(zhí)行中,這個變量將被置1。
forloop.revcounter0 類似于 forloop.revcounter ,但它以0做為結(jié)束索引。 在第一次執(zhí)行循環(huán)時,該變量會被置為序列的項的個數(shù)減1。
forloop.first 是一個布爾值,如果該迭代是第一次執(zhí)行,那么它被置為```` 在下面的情形中這個變量是很有用的:
System Message: WARNING/2 (, line 1071); backlink
Inline literal start-string without end-string.
{% for object in objects %} {% if forloop.first %}
class="first">{% else %} {% endif %} {{ object }} {% endfor %}r %}forloop.last 是一個布爾值;在最后一次執(zhí)行循環(huán)時被置為True。 一個常見的用法是在一系列的鏈接之間放置管道符(|)
{% for link in links %}{{ link }}{% if not forloop.last %} | {% endif %}{% endfor %}
上面的模板可能會產(chǎn)生如下的結(jié)果:
Link1 | Link2 | Link3 | Link4
另一個常見的用途是為列表的每個單詞的加上逗號。
Favorite places:
{% for p in places %}{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}
forloop.parentloop 是一個指向當(dāng)前循環(huán)的上一級循環(huán)的 forloop 對象的引用(在嵌套循環(huán)的情況下)。 例子在此:
{% 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 變量僅僅能夠在循環(huán)中使用。 在模板解析器碰到{% endfor %}標(biāo)簽后,forloop就不可訪問了。
Context和forloop變量
在一個 {% for %} 塊中,已存在的變量會被移除,以避免 forloop 變量被覆蓋。 Django會把這個變量移動到forloop.parentloop 中。通常我們不用擔(dān)心這個問題,但是一旦我們在模板中定義了 forloop 這個變量(當(dāng)然我們反對這樣做),在 {% for %} 塊中它會在 forloop.parentloop 被重新命名。
Django模板系統(tǒng)壓根兒就沒想過實現(xiàn)一個全功能的編程語言,所以它不允許我們在模板中執(zhí)行Python的語句(還是那句話,要了解更多請參看理念和限制小節(jié))。 但是比較兩個變量的值并且顯示一些結(jié)果實在是個太常見的需求了,所以Django提供了 {% ifequal %} 標(biāo)簽供我們使用。
{% ifequal %} 標(biāo)簽比較兩個值,當(dāng)他們相等時,顯示在 {% ifequal %} 和 {% endifequal %} 之中所有的值。
下面的例子比較兩個模板變量 user 和 currentuser :
{% ifequal user currentuser %}
Welcome!h1>
{% endifequal %}
參數(shù)可以是硬編碼的字符串,隨便用單引號或者雙引號引起來,所以下列代碼都是正確的:
{% ifequal section 'sitenews' %}
<h1>Site Newsh1>
{% endifequal %}
{% ifequal section "community" %}
<h1>Communityh1>
{% endifequal %}
和 {% if %} 類似, {% ifequal %} 支持可選的 {% else%} 標(biāo)簽:
{% ifequal section 'sitenews' %}
<h1>Site Newsh1>
{% else %}
<h1>No News Hereh1>
{% endifequal %}
只有模板變量,字符串,整數(shù)和小數(shù)可以作為 {% ifequal %} 標(biāo)簽的參數(shù)。下面是合法參數(shù)的例子:
{% 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 #}
注釋的內(nèi)容不會在模板渲染時輸出。
用這種語法的注釋不能跨越多行。 這個限制是為了提高模板解析的性能。 在下面這個模板中,輸出結(jié)果和模板本身是 完全一樣的(也就是說,注釋標(biāo)簽并沒有被解析為注釋):
This is a {# this is not
a comment #}
test.
如果要實現(xiàn)多行注釋,可以使用{% comment %}
模板標(biāo)簽,就像這樣:
{% comment %}
This is a
multi-line comment.
{% endcomment %}
就象本章前面提到的一樣,模板過濾器是在變量被顯示前修改它的值的一個簡單方法。 過濾器使用管道字符,如下所示:
{{ name|lower }}
顯示的內(nèi)容是變量 {{ name }} 被過濾器 lower 處理后的結(jié)果,它功能是轉(zhuǎn)換文本為小寫。
過濾管道可以被 套接 ,既是說,一個過濾器管道的輸出又可以作為下一個管道的輸入,如此下去。 下面的例子實現(xiàn)查找列表的第一個元素并將其轉(zhuǎn)化為大寫。
{{ my_list|first|upper }}
有些過濾器有參數(shù)。 過濾器的參數(shù)跟隨冒號之后并且總是以雙引號包含。 例如:
{{ bio|truncatewords:"30" }}
這個將顯示變量 bio 的前30個詞。
以下幾個是最為重要的過濾器的一部分。 附錄F包含其余的過濾器。
addslashes : 添加反斜杠到任何反斜杠、單引號或者雙引號前面。 這在處理包含JavaScript的文本時是非常有用的。
date : 按指定的格式字符串參數(shù)格式化 date 或者 datetime 對象, 范例:
{{ pub_date|date:"F j, Y" }}
格式參數(shù)的定義在附錄F中。
length : 返回變量的長度。 對于列表,這個參數(shù)將返回列表元素的個數(shù)。 對于字符串,這個參數(shù)將返回字符串中字符的個數(shù)。 你可以對列表或者字符串,或者任何知道怎么測定長度的Python 對象使用這個方法(也就是說,有 len() 方法的對象)。
現(xiàn)在你已經(jīng)對Django的模板語言有一些認(rèn)識了,我們將指出一些特意設(shè)置的限制和為什么要這樣做 背后的一些設(shè)計哲學(xué)。
相對與其他的網(wǎng)絡(luò)應(yīng)用的組件,模板的語法很具主觀性,因此可供程序員的選擇方案也很廣泛。 事實上,Python有成十上百的 開放源碼的模板語言實現(xiàn)。 每個實現(xiàn)都是因為開發(fā)者認(rèn)為現(xiàn)存的模板語言不夠用。 (事實上,對一個 Python開發(fā)者來說,寫一個自己的模板語言就象是某種“成人禮”一樣! 如果你還沒有完成一個自己的 模板語言,好好考慮寫一個,這是一個非常有趣的鍛煉。 )
明白了這個,你也許有興趣知道事實上Django并不強制要求你必須使用它的模板語言。 因為Django 雖然被設(shè)計成一個FULL-Stack的Web框架,它提供了開發(fā)者所必需的所有組件,而且在大多數(shù)情況 使用Django模板系統(tǒng)會比其他的Python模板庫要 更方便 一點,但是并不是嚴(yán)格要求你必須使用 它。 你將在后續(xù)的“視圖中應(yīng)用模板”這一章節(jié)中看到,你還可以非常容易地在Django中使用其他的模板語言。
雖然如此,很明顯,我們對Django模板語言的工作方式有著強烈的偏愛。 這個模板語言來源于World Online的開發(fā)經(jīng)驗和Django創(chuàng)造者們集體智慧的結(jié)晶。 下面是關(guān)于它的一些設(shè)計哲學(xué)理念:
業(yè)務(wù)邏輯應(yīng)該和表現(xiàn)邏輯相對分開 。我們將模板系統(tǒng)視為控制表現(xiàn)及表現(xiàn)相關(guān)邏輯的工具,僅此而已。 模板系統(tǒng)不應(yīng)提供超出此基本目標(biāo)的功能。
出于這個原因,在 Django 模板中是不可能直接調(diào)用 Python 代碼的。 所有的編程工作基本上都被局限于模板標(biāo)簽的能力范圍。 當(dāng)然, 是 有可能寫出自定義的模板標(biāo)簽來完成任意工作,但這些“超范圍”的 Django 模板標(biāo)簽有意地不允許執(zhí)行任何 Python 代碼。
語法不應(yīng)受到 HTML/XML 的束縛 。盡管 Django 模板系統(tǒng)主要用于生成 HTML,它還是被有意地設(shè)計為可生成非 HTML 格式,如純文本。 一些其它的模板語言是基于 XML 的,將所有的模板邏輯置于 XML 標(biāo)簽與屬性之中,而 Django 有意地避開了這種限制。 強制要求使用有效 XML 編寫模板將會引發(fā)大量的人為錯誤和難以理解的錯誤信息,而且使用 XML 引擎解析模板也會導(dǎo)致令人無法容忍的模板處理開銷。
假定設(shè)計師精通 HTML 編碼 。模板系統(tǒng)的設(shè)計意圖并不是為了讓模板一定能夠很好地顯示在 Dreamweaver 這樣的所見即所得編輯器中。 這種限制過于苛刻,而且會使得語法不能像目前這樣的完美。 Django 要求模板創(chuàng)作人員對直接編輯 HTML 非常熟悉。
假定設(shè)計師不是 Python 程序員 。模板系統(tǒng)開發(fā)人員認(rèn)為:模板通常由設(shè)計師而非程序員來編寫,因此不應(yīng)被假定擁有Python開發(fā)知識。
當(dāng)然,系統(tǒng)同樣也特意地提供了對那些 由 Python 程序員進(jìn)行模板制作的小型團隊的支持。 它提供了一種工作模式,允許通過編寫原生 Python 代碼進(jìn)行系統(tǒng)語法拓展。 (詳見第十章)
目標(biāo)并不是要發(fā)明一種編程語言 。目標(biāo)是恰到好處地提供如分支和循環(huán)這一類編程式功能,這是進(jìn)行與表現(xiàn)相關(guān)判斷的基礎(chǔ)。
在學(xué)習(xí)了模板系統(tǒng)的基礎(chǔ)之后,現(xiàn)在讓我們使用相關(guān)知識來創(chuàng)建視圖。 重新打開我們在前一章在 mysite.views中創(chuàng)建的 current_datetime 視圖。 以下是其內(nèi)容:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "It is now %s." % now
return HttpResponse(html)
讓我們用 Django 模板系統(tǒng)來修改該視圖。 第一步,你可能已經(jīng)想到了要做下面這樣的修改:
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = Template("It is now {{ current_date }}.")
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
沒錯,它確實使用了模板系統(tǒng),但是并沒有解決我們在本章開頭所指出的問題。 也就是說,模板仍然嵌入在Python代碼里,并未真正的實現(xiàn)數(shù)據(jù)與表現(xiàn)的分離。 讓我們將模板置于一個 單獨的文件 中,并且讓視圖加載該文件來解決此問題。
你可能首先考慮把模板保存在文件系統(tǒng)的某個位置并用 Python 內(nèi)建的文件操作函數(shù)來讀取文件內(nèi)容。 假設(shè)文件保存在 /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() 函數(shù)調(diào)用將會引發(fā) IOError 異常。
這里對模板文件的位置進(jìn)行了硬編碼。 如果你在每個視圖函數(shù)都用該技術(shù),就要不斷復(fù)制這些模板的位置。 更不用說還要帶來大量的輸入工作!
為了解決這些問題,我們采用了 模板自加載 跟 模板目錄 的技巧.
為了減少模板加載調(diào)用過程及模板本身的冗余代碼,Django 提供了一種使用方便且功能強大的 API ,用于從磁盤中加載模板,
要使用此模板加載API,首先你必須將模板的保存位置告訴框架。 設(shè)置的保存文件就是我們前一章節(jié)講述ROOT_URLCONF配置的時候提到的 settings.py。
如果你是一步步跟隨我們學(xué)習(xí)過來的,馬上打開你的settings.py配置文件,找到TEMPLATE_DIRS這項設(shè)置吧。 它的默認(rèn)設(shè)置是一個空元組(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.
)
該設(shè)置告訴 Django 的模板加載機制在哪里查找模板。 選擇一個目錄用于存放模板并將其添加到TEMPLATE_DIRS 中:
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)