Django第三篇-----模板前半篇
目錄
什麼是Django模板
Django 模板是一些文字字串,作用是把文件的表現與資料區分開。模板定義一些佔位符和基本的邏輯(模板標籤),規定如何顯示文件。通常,模板用於生成 HTML,不過 Django 模板可以生成任何基於文字的格式。
Django 模板描述一個 HTML 頁面
<html> <head> <title>Ordering notice</title> </head> <body> 25 <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>
模板標籤詳細說明
• 兩對花括號包圍的文字(如 {{ person_name }} )是變數,意思是“把指定變數的值插入這裡”。如何指
定變數的值呢?稍後說明。
• 一對花括號和百分號包圍的文字(如 {% if ordered_warranty %} )是模板標籤。
標籤的定義相當寬泛:只要能讓模板系統“做些事”的就是標籤。
• 這個示例模板中有一個 for 標籤( {% for item in item_list %} )和一個 if 標籤( {% if or-
dered_warranty %} )。 for 標籤的作用與 Python 中的 for 語句很像,用於迭代序列中的各個元素。與
你預期的一樣, if 標籤的作用是編寫邏輯判斷語句。
這裡, if 標籤檢查 ordered_warranty 變數的求值結果是不是 True 。如果是,模板系統將顯示 {% if
ordered_warranty %} 和 {% else %} 之間的內容;如果不是,模板系統將顯示 {% else %} 和 {% endif
%} 之間的內容。注意, {% else %} 是可選的。
• 最後,這個模板的第二段包含一個過濾器,這是調整變數格式最為便利的方式。對這個示例中的 {{
ship_date|date:"F j, Y" }} 來說,我們把 ship_date 變數傳給 date 過濾器,並且為 date 過濾器指定
"F j, Y" 引數。 date 過濾器使用引數指定的格式格式化日期。過濾器使用管道符號( | )依附,類似
於 Unix 管道。
Template 物件
若想在 Python 程式碼中使用 Django 的模板系統,基本方式如下:
1. 以字串形式提供原始的模板程式碼,建立 Template 物件。
2. 在 Template 物件上呼叫 render() 方法,傳入一系列變數(上下文)。返回的是完全渲染模板後得到的字串,模板中的變數和模板標籤已經根據上下文求出值了。
>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Nige'})
>>> print (t.render(c))
My name is Nige.
>>> c = template.Context({'name': 'Barry'})
>>> print (t.render(c))
My name is Barry.
錯誤標籤
>>> from django.template import Template #
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "", line 1, in ?
...
django.template.base.TemplateSyntaxError: Invalid block tag: 'notatag'
這裡的“block tag”(塊級標籤)指的是 {% notatag %} 。“塊級標籤”和“模板標籤”是同一個事物。遇到下述各種情況時,模板系統都會丟擲 TemplateSyntaxError :
• 無效標籤
• 有效標籤的無效引數
• 無效過濾器
• 有效過濾器的無效引數
• 無效模板句法
• 未關閉的標籤(對需要結束標籤的模板標籤而言)
渲染模板
有了 Template 物件之後,可以為其提供上下文,把資料傳給它。上下文就是一系列模板變數和相應的值。模板使用上下文填充變數,求值標籤。在 Django 中,上下文使用 django.template 模組中的 Context 類表示。它的構造方法接受一個可選引數:一個字典,把變數名對映到值上。“填充”模板的方式是,在 Template 物件上呼叫 render() 方法,並傳入上下文:
>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
'My name is Stephane.'
字典和上下文
得到 Template 物件之後,可以用其渲染多個上下文。例如:
>>> 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
像這樣使用同一個模板渲染多個上下文,比分成多次建立 Template 物件再呼叫 render() 效率高:
# 不好
for name in ('John', 'Julie', 'Pat'):
t = Template('Hello, {{ name }}')
print (t.render(Context({'name': name})))
# 好
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print (t.render(Context({'name': name})))
上下文變數查詢
假如我們把一個 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)
'Sally is 43 years old.'
類似地,通過點號還可以訪問物件的屬性。例如,Python 的 datetime.date 物件有 year 、 month 和 day 屬性,在 Django 模板中可以使用點號訪問這些屬性:
>>> 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)
'The month is 5 and the year is 1993.'
下述示例自定義一個類,以此說明也可以通過點號訪問任意物件的屬性:
>>> 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)
'Hello, John Smith.'
還可以通過點號引用物件的方法。例如,每個 Python 字串都有 upper() 和 isdigit() 方法,在 Django 模板中可以使用點號句法呼叫它們
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'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)
'Item 2 is carrots.'
不允許使用負數索引。例如,模板變數 {{ items.-1 }} 會導致 TemplateSyntaxError 丟擲
總結起來,模板系統遇到變數名中的點號時會按照下述順序嘗試查詢:
• 字典查詢(如 foo["bar"] )
• 屬性查詢(如 foo.bar )
• 方法呼叫(如 foo.bar() )
• 列表索引查詢(如 foo[2] )
方法呼叫的行為
在方法查詢的過程中,如果方法丟擲異常,異常會向上冒泡,除非異常有 silent_variable_failure 屬性,而且值為 True 。如果異常確實有 silent_variable_failure 屬性,使用引擎的 string_if_invalid配置選項(預設為一個空字串)渲染變數。例如:
>>> 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(Exception):
... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
'My name is .'
• 方法不能有必須的引數。否則,模板系統向後移動到下一種查詢型別(列表索引查詢)。
• 按照設計,Django 限制了在模板中可以處理的邏輯量,因此在模板中不能給方法傳遞引數。資料應該在檢視中計算之後再傳給模板顯示。
• 顯然,有些方法有副作用,如果允許模板系統訪問這樣的方法,那就愚蠢之極,甚至還可能埋下安全漏洞。
• 假如有個 BankAccount 物件,它有個 delete() 方法。如果模板中有 {{ account.delete }} 這樣的內容,其中 account 是 BankAccount 物件,那麼渲染模板時會把 account 刪除。
為了避免這種行為,在方
法上設定函式屬性 alters_data :
def delete(self):
# 刪除賬戶
delete.alters_data = True
這樣標記之後,模板系統不會執行方法。繼續使用前面的例子。如果模板中有 {{ account.delete}} ,而 delete() 方法設定了 alters_data=True ,那麼渲染模板時不會執行 delete() 方法,引擎會使用string_if_invalid 的值替換那個變數。注意:為 Django 模型物件動態生成的 delete() 和 save() 方法自動設定了alters_data = True 。
如何處理無效變數
一般來說,如果變數不存在,模板系統在變數處插入引擎的 string_if_invalid 配置選項。這個選項的預設值為一個空字串。例如
>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
'Your name is .'
>>> t.render(Context({'var': 'hello'}))
'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
'Your name is .'
基本的模板標籤和過濾器
if/else
{% 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 %}
if 標籤還可以有一個或多個 {% elif %} 子句:
{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
<p>Athletes should be out of the locker room soon! </p>
{% elif ...
...
{% else %}
<p>No athletes. </p>
{% endif %}
{% if %} 支援使用 and 、 or 或 not 測試多個變數,或者取反指定的變數。例如:
{% if athlete_list and coach_list %}
<p>Both athletes and coaches are available. </p>
{% endif %}
{% if not athlete_list %}
<p>There are no athletes. </p>
{% endif %}
{% if athlete_list or coach_list %}
<p>There are some athletes or some coaches. </p>
{% endif %}
{% if not athlete_list or coach_list %}
<p>There are no athletes or there are some coaches. </p>
{% endif %}
{% if athlete_list and not coach_list %}
<p>There are some athletes and absolutely no coaches. </p>
{% endif %}
在同一個標籤中可以同時使用 and 和 or ,此時, and 的優先順序比 or 高。例如:
{% if athlete_list and coach_list or cheerleader_list %}
在 if 標籤中使用括號是無效的句法。
多次使用相同的邏輯運算子沒問題,但是不能混用不同的運算子。例如,下述寫法是有效的:
{% if athlete_list or coach_list or parent_list or teacher_list %}
每個 {% if %} 都必須有配對的 {% endif %} 。否則,Django 會丟擲 TemplateSyntaxError 異常。
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 %} 標籤可以巢狀:
{% for athlete in athlete_list %}
<h1>{{ athlete.name }}</h1>
<ul>
{% for sport in athlete.sports_played %}
<li>{{ sport }}</li>
{% endfor %}
</ul>
{% endfor %}
如果需要訪問字典中的元素,也可以使用這個標籤。如果上下文中包含一個字典 data ,可以使用下述模板顯示字典的鍵和值
{% for key, value in data.items %}
{{ key }}: {{ value }}
{% endfor %}
這種做法太常見了,因此 for 標籤支援一個可選的 {% empty %} 子句,用於定義列表為空時顯示的內容。
{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% empty %}
<p>There are no athletes. Only computer programmers.</p>
{% endfor %}
在迴圈結束之前,無法“跳出”。如果需要這麼做,修改要迭代的變數,只包含需要迭代的值。同樣,也沒有“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 。
• forloop.revcounter 的值是一個整數,表示迴圈中剩餘的元素數量。第一次迴圈時, for-loop.revcounter 的值是序列中要遍歷的元素總數。最後一次迴圈時, forloop.revcounter 的值為 1 。
• forloop.revcounter0 與 forloop.revcounter 類似,不過索引是基於零的。第一次迴圈時, for-loop.revcounter0 的值是序列中元素數量減去一。最後一次迴圈時, forloop.revcounter0 的值為 0 。
• forloop.first 是個布林值,第一次迴圈時為 True 。需要特殊處理第一個元素時很方便:
{% for object in objects %}
{% if forloop.first %}
<li class="first">
{% else %}
<li>
{% endif %}
{{ object }}
</li>
{% endfor %}
• forloop.last 是個布林值,最後一次迴圈時為 True 。經常用它在一組連結之間放置管道符號:
{% for link in links %}
{{ link }}{% 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 隨之消失。
ifequal/ifnotequal
Django 模板系統不是功能全面的程式語言,因此不允許執行任意的 Python 語句(3.5 節詳述)。然而,模板經常需要比較兩個值,在相等時顯示一些內容。為此,Django 提供了 {% ifequal %} 標籤。{% ifequal %} 標籤比較兩個值,如果相等,顯示 {% ifequal %} 和 {% endifequal %} 之間的內容。下述示例比較模板標籤 user 和 currentuser :
{% ifequal user currentuser %}
<h1>Welcome!</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" %}
ifequal 標籤可以替換成 if 標籤和 == 運算子。
{% ifnotequal %} 的作用與 ifequal 類似,不過它測試兩個引數是否不相等。 ifnotequal 標籤可以替換成 if
標籤和 != 運算子。
註釋
與 HTML 和 Python 一樣,Django 模板語言支援註釋。註釋使用 {# #} 標明:
{# This is a comment #}
渲染模板時,不輸出註釋。使用這種句法編寫的註釋不能分成多行。這一限制有助於提升模板解析效能。
{% comment %}
This is a
multi-line comment.
{% endcomment %}