本文將介紹Django模版系統(tǒng)的語法。如果您需要更多該系統(tǒng)如何工作的技術細節(jié),以及希望擴展它,請瀏覽 The Django template language: for Python programmers.
Django模版語言的設計致力于在性能和簡單上取得平衡。 它的設計使習慣于使用HTML的人也能夠自如應對。如果您有過使用其他模版語言的經驗,像是 Smarty 或者 Jinja2, 那么您將對Django的模版語言感到一見如故。
理念
如果您有過編程背景,或者您使用過一些在HTML中直接混入程序代碼的語言,那么現(xiàn)在您需要記住,Django的模版系統(tǒng)并不是簡單的將Python嵌入到HTML中。 設計決定了:模版系統(tǒng)致力于表達外觀,而不是程序邏輯。
Django的模版系統(tǒng)提供了和一些程序結構功能類似的標簽——用于布爾判斷的 if 標簽, 用于循環(huán)的 for 標簽等等?!沁@些都不是簡單的作為Python代碼那樣來執(zhí)行的,并且,模版系統(tǒng)也不會隨意執(zhí)行Python表達式。只有下面列表中的標簽、過濾器和語法才是默認就被支持的。 (但是您也可以根據(jù)需要添加 您自己的擴展 到模版語言中)。
模版是純文本文件。它可以產生任何基于文本的的格式(HTML,XML,CSV等等)。
模版包括在使用時會被值替換掉的 變量,和控制模版邏輯的 標簽。
下面是一個小模版,它說明了一些基本的元素。后面的文檔中會解釋每個元素。
{% extends "base_generic.html" %}
{% block title %}{{ section.title }}{% endblock %}
{% block content %}
<h1>{{ section.title }}</h1>
{% for story in story_list %}
<h2>
<a href="{{ story.get_absolute_url }}">
{{ story.headline|upper }}
</a>
</h2>
<p>{{ story.tease|truncatewords:"100" }}</p>
{% endfor %}
{% endblock %}
理念
為什么要使用基于文本的模版,而不是基于XML的(比如Zope的TAL)呢?我們希望Django的模版語言可以用在更多的地方,而不僅僅是XML/HTML模版。在線上世界,我們在email、Javascript和CSV中使用它。你可以在任何基于文本的格式中使用這個模版語言。
還有,讓人類編輯HTML簡直是施虐狂的做法!
變量看起來就像是這樣: {{ variable }}. 當模版引擎遇到一個變量,它將計算這個變量,然后用結果替換掉它本身。變量的命名包括任何字母數(shù)字以及下劃線 ("_")的組合。點(".") 也在會變量部分中出現(xiàn),不過它有特殊的含義,我們將在后面說明。重要的是, 你不能在變量名稱中使用空格和標點符號。
使用點 (.) 來訪問變量的屬性。
幕后
從技術上來說,當模版系統(tǒng)遇到點,它將以這樣的順序查詢:
如果計算結果的值是可調用的,它將被無參數(shù)的調用。調用的結果將成為模版的值。
這個查詢順序,會在優(yōu)先于字典查詢的對象上造成意想不到的行為。例如,思考下面的代碼片段,它嘗試循環(huán) collections.defaultdict:
{% for k, v in defaultdict.iteritems %}
Do something with k and v here...
{% endfor %}
因為字典查詢首先發(fā)生,行為奏效了并且提供了一個默認值,而不是使用我們期望的 .iteritems() 方法。在這種情況下,考慮首先轉換成字典。
在前文的例子中, {{ section.title }}將被替換為 section 對象的 title 屬性。
如果你使用的變量不存在, 模版系統(tǒng)將插入 string_if_invalid 選項的值, 它被默認設置為'' (空字符串) 。
注意模版表達式中的“bar”, 比如 {{ foo.bar }} 將被逐字直譯為一個字符串,而不是使用變量“bar”的值,如果這樣一個變量在模版上下文中存在的話。
您可以通過使用 過濾器來改變變量的顯示。
過濾器看起來是這樣的:{{ name|lower }}。這將在變量 {{ name }} 被過濾器 lower 過濾后再顯示它的值,該過濾器將文本轉換成小寫。使用管道符號 (|)來應用過濾器。
過濾器能夠被“串聯(lián)”。一個過濾器的輸出將被應用到下一個。{{ text|escape|linebreaks }} 就是一個常用的過濾器鏈,它編碼文本內容,然后把行打破轉成<p> 標簽。
一些過濾器帶有參數(shù)。過濾器的參數(shù)看起來像是這樣: {{ bio|truncatewords:30 }}。這將顯示 bio 變量的前30個詞。
過濾器參數(shù)包含空格的話,必須被引號包起來;例如,連接一個有逗號和空格的列表,你需要使用 {{ list|join:", " }}。
Django提供了大約六十個內置的模版過濾器。你可以在 內置過濾器參考手冊中閱讀全部關于它們的信息。為了體驗一下它們的作用,這里有一些常用的模版過濾器:
如果一個變量是false或者為空,使用給定的默認值。否則,使用變量的值。例如:
{{ value|default:"nothing" }}
如果 value沒有被提供,或者為空, 上面的例子將顯示“nothing”。
返回值的長度。它對字符串和列表都起作用。例如:
{{ value|length }}
如果 value 是 ['a', 'b', 'c', 'd'],那么輸出是 4。
將值格式化為一個 “人類可讀的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:
{{ value|filesizeformat }}
如果 value 是 123456789,輸出將會是 117.7 MB。
再說一下,這僅僅是一些例子;查看 內置過濾器參考手冊 來獲取完整的列表。
您也可以創(chuàng)建自己的自定義模版過濾器;參考 自定義模版標簽和過濾器。
更多
Django’s admin interface can include a complete reference of all template tags and filters available for a given site. See The Django admin documentation generator.
標簽看起來像是這樣的: {% tag %}。標簽比變量更加復雜:一些在輸出中創(chuàng)建文本,一些通過循環(huán)或邏輯來控制流程,一些加載其后的變量將使用到的額外信息到模版中。
一些標簽需要開始和結束標簽 (例如{% tag %} ...標簽 內容 ... {% endtag %})。
Django自帶了大約24個內置的模版標簽。你可以在 內置標簽參考手冊中閱讀全部關于它們的內容。為了體驗一下它們的作用,這里有一些常用的標簽:
循環(huán)數(shù)組中的每個元素。例如,顯示 athlete_list中提供的運動員列表:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
if, elif, and else
計算一個變量,并且當變量是“true”是,顯示塊中的內容:
{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
Athletes should be out of the locker room soon!
{% else %}
No athletes.
{% endif %}
在上面的例子中,如果 athlete_list 不是空的,運動員的數(shù)量將顯示為 {{ athlete_list|length }} 的輸出。另一方面, 如果 athlete_in_locker_room_list 不為空, 將顯示 “Athletes should be out...” 這個消息。如果兩個列表都是空的,將顯示 “No athletes.” 。
您也可以在if 標簽中使用過濾器和多種運算符:
{% if athlete_list|length > 1 %}
Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
Athlete: {{ athlete_list.0.name }}
{% endif %}
當上面的例子工作時,需要注意,大多數(shù)模版過濾器返回字符串,所以使用過濾器做數(shù)學的比較通常都不會像您期望的那樣工作。length 是一個例外。
Set up template inheritance (see below), a powerful way of cutting down on “boilerplate” in templates.
再說一下,上面的僅僅是整個列表的一部分;查看 內置標簽參考手冊 來獲取完整的列表。
您也可以創(chuàng)建您自己的自定義模版標簽;參考 自定義模版標簽和過濾器。
更多
Django’s admin interface can include a complete reference of all template tags and filters available for a given site. See The Django admin documentation generator.
要注釋模版中一行的部分內容,使用注釋語法 {# #}.
例如,這個模版將被渲染為 'hello':
{# greeting #}hello
注釋可以包含任何模版代碼,有效的或者無效的都可以。例如:
{# {% if foo %}bar{% else %} #}
這個語法只能被用于單行注釋 (在 {# 和 #} 分隔符中,不允許有新行)。如果你需要注釋掉模版中的多行內容,請查看 comment 標簽。
Django模版引擎中最強大也是最復雜的部分就是模版繼承了。模版繼承可以讓您創(chuàng)建一個基本的“骨架”模版,它包含您站點中的全部元素,并且可以定義能夠被子模版覆蓋的 blocks 。
通過從下面這個例子開始,可以容易的理解模版繼承:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}My amazing site{%/span> 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骨架。“子模版”的工作是用它們的內容填充空的blocks。
在這個例子中, block 標簽定義了三個可以被子模版內容填充的block。 block 告訴模版引擎: 子模版可能會覆蓋掉模版中的這些位置。
子模版可能看起來是這樣的:
{% extends "base.html" %}/span>
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
extends 標簽是這里的關鍵。它告訴模版引擎,這個模版“繼承”了另一個模版。當模版系統(tǒng)處理這個模版時,首先,它將定位父模版——在此例中,就是“base.html”。
那時,模版引擎將注意到 base.html 中的三個 block 標簽,并用子模版中的內容來替換這些block。根據(jù) 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,所以系統(tǒng)使用了父模版中的值。父模版的 {% block %} 標簽中的內容總是被用作備選內容(fallback)。
您可以根據(jù)需要使用多級繼承。使用繼承的一個常用方式是類似下面的三級結構:
base.html 模版來控制您整個站點的主要視覺和體驗。base_SECTIONNAME.html 模版。 例如, base_news.html, base_sports.html。這些模版都繼承自 base.html ,并且包含了每部分特有的樣式和設計。這種方式使代碼得到最大程度的復用,并且使得添加內容到共享的內容區(qū)域更加簡單,例如,部分范圍內的導航。
這里是使用繼承的一些提示:
如果你在模版中使用 {% extends %} 標簽,它必須是模版中的第一個標簽。其他的任何情況下,模版繼承都將無法工作。
在base模版中設置越多的 {% block %} 標簽越好。請記住,子模版不必定義全部父模版中的blocks,所以,你可以在大多數(shù)blocks中填充合理的默認內容,然后,只定義你需要的那一個。多一點鉤子總比少一點好。
如果你發(fā)現(xiàn)你自己在大量的模版中復制內容,那可能意味著你應該把內容移動到父模版中的一個 {% block %} 中。
If you need to get the content of the block from the parent template, the {{ block.super }} variable will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. Data inserted using {{ block.super }} will not be automatically escaped (see the next section), since it was already escaped, if necessary, in the parent template.
為了更好的可讀性,你也可以給你的 {% endblock %} 標簽一個 名字 。例如:
{% block content %}
...
{% endblock content %}
在大型模版中,這個方法幫你清楚的看到哪一個 {% block %} 標簽被關閉了。
最后,請注意您并不能在一個模版中定義多個相同名字的 block 標簽。這個限制的存在是因為block標簽的作用是“雙向”的。這個意思是,block標簽不僅提供了一個坑去填,它還在 _父模版_中定義了填坑的內容。如果在一個模版中有兩個名字一樣的 block 標簽,模版的父模版將不知道使用哪個block的內容。
當從模版中生成HTML時,總會有這樣一個風險:值可能會包含影響HTML最終呈現(xiàn)的字符。例如,思考這個模版片段:
Hello, {{ name }}
首先,它看起來像是一個無害的方式來顯示用戶的名字,但是設想一下,如果用戶像下面這樣輸入他的名字,會發(fā)生什么:
<script>alert('hello')</script>
使用這個名字值,模版將會被渲染成這樣:
Hello, <script>alert('hello')</script>
...這意味著,瀏覽器將會彈出一個Javascript警示框!
類似的,如果名字包含一個 '<' 符號(比如下面這樣),會發(fā)生什么呢?
<b>username
這將會導致模版唄渲染成這樣:
Hello, <b>username
...進而這將導致網(wǎng)頁的剩余部分都被加粗!
顯然,用戶提交的數(shù)據(jù)都被應該被盲目的信任,并且被直接插入到你的網(wǎng)頁中,因為一個懷有惡意的用戶可能會使用這樣的漏洞來做一些可能的壞事。這種類型的安全問題被叫做 跨站腳本(Cross Site Scripting) (XSS) 攻擊。
為避免這個問題,你有兩個選擇:
escape 過濾器(下面的文檔中將提到)運行,它將把潛在的有害HTML字符轉換成無害的。This was the default solution in Django for its first few years, but the problem is that it puts the onus on you, the developer / template author, to ensure you’re escaping everything. It’s easy to forget to escape data.By default in Django, every template automatically escapes the output of every variable tag. Specifically, these five characters are escaped:
<會轉換為<>會轉換為>' (單引號) 會轉換為'" (雙引號)會轉換為 "& 會轉換為 &我們要再次強調這是默認行為。如果你使用Django的模板系統(tǒng),會處于保護之下。
如果你不希望數(shù)據(jù)自動轉義,在站點、模板或者變量級別,你可以使用幾種方法來關閉它。
然而你為什么想要關閉它呢?由于有時,模板變量含有一些你_打算_渲染成原始HTML的數(shù)據(jù),你并不想轉義這些內容。例如,你可能會在數(shù)據(jù)庫中儲存一些HTML代碼,并且直接在模板中嵌入它們?;蛘?,你可能使用Django的模板系統(tǒng)來生成_不是_HTML的文本 -- 比如郵件信息。
使用safe過濾器來關閉獨立變量上的自動轉移:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
_safe_是_safe from further escaping_或者_can be safely interpreted as HTML_的縮寫。
在這個例子中,如果data含有'<b>',輸出會是:
This will be escaped: <b>
This will not be escaped: <b>
要控制模板上的自動轉移,將模板(或者模板中的特定區(qū)域)包裹在autoescape標簽 中,像這樣:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
autoescape標簽接受on 或者 off作為它的參數(shù)。有時你可能想在自動轉移關閉的情況下強制使用它。下面是一個模板的示例:
Auto-escaping is on by default. Hello {{ name }}
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
自動轉移標簽作用于擴展了當前模板的模板,以及通過 include 標簽包含的模板,就像所有block標簽那樣。例如:
base.html
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
child.html
{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
由于自動轉義標簽在base模板中關閉,它也會在child模板中關閉,導致當 greeting變量含有<b>Hello!</b>字符串時,會渲染HTML。
<h1>This & that</h1>
<b>Hello!</b>
通常,模板的作用并不非常擔心自動轉義。Python一邊的開發(fā)者(編寫視圖和自定義過濾器的人)需要考慮數(shù)據(jù)不應被轉移的情況,以及合理地標記數(shù)據(jù),讓這些東西在模板中正常工作。
如果你創(chuàng)建了一個模板,它可能用于你不確定自動轉移是否開啟的環(huán)境,那么應該向任何需要轉移的變量添加 escape過濾器。當自動轉移打開時,escape過濾器_雙重過濾_數(shù)據(jù)沒有任何危險 -- escape過濾器并不影響自動轉義的變量。
像我們之前提到的那樣,過濾器參數(shù)可以是字符串:
{{ data|default:"This is a string literal." }}
所有字面值字符串在插入模板時都 不會帶有任何自動轉義 -- 它們的行為類似于通過 safe過濾器傳遞。背后的原因是,模板作者可以控制字符串字面值得內容,所以它們可以確保在模板編寫時文本經過正確轉義。
也即是說你可以編寫
{{ data|default:"3 < 2" }}
...而不是:
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
這并不影響來源于模板自身的數(shù)據(jù)。模板內容在必要時仍然會自動轉移,因為它們不受模板作者的控制。
大多數(shù)對象上的方法調用同樣可用于模板中。這意味著模板必須擁有對除了類屬性(像是字段名稱)和從視圖中傳入的變量之外的訪問。例如,Django ORM提供了_“entryset” 語法用于查找關聯(lián)到外鍵的對象集合。所以,提供一個模型叫做“comment”,并帶有一個關聯(lián)到 “task” 模型的外鍵,你就可以遍歷給定任務附帶的所有評論,像這樣:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
與之類似,QuerySets提供了 count()方法來計算含有對象的總數(shù)。因此,你可以像這樣獲取所有關于當前任務的評論總數(shù):
{{ task.comment_set.all.count }}
當然,你可以輕易訪問已經顯式定義在你自己模型上的方法:
models.py
class Task(models.Model):
def foo(self):
return "bar"
template.html
{{ task.foo }}
由于Django有意限制了模板語言中邏輯處理的總數(shù),不能夠在模板中傳遞參數(shù)來調用方法。數(shù)據(jù)應該在視圖中處理,然后傳遞給模板用于展示。
特定的應用提供自定義的標簽和過濾器庫。要在模板中訪問它們,確保應用在INSTALLED_APPS之內(在這個例子中我們添加了'django.contrib.humanize'),之后在模板中使用load標簽:
{% load humanize %}
{{ 45000|intcomma }}
上面的例子中, load標簽加載了humanize標簽庫,之后我們可以使用intcomma過濾器。如果你開啟了django.contrib.admindocs,你可以查詢admin站點中的文檔部分,來尋找你的安裝中的自定義庫列表。
load標簽可以接受多個庫名稱,由空格分隔。例如:
{% load humanize i18n %}
關于編寫你自己的自定義模板庫,詳見自定義模板標簽和過濾器。
當你加載一個自定義標簽或過濾器庫時,標簽或過濾器只在當前模板中有效 -- 并不是帶有模板繼承關系的任何父模板或者子模版中都有效。
例如,如果一個模板foo.html帶有{% load humanize %},子模版(例如,帶有{% extends "foo.html" %})中不能 訪問humanize模板標簽和過濾器。子模版需要添加自己的 {% load humanize %}。
這個特性是可維護性和邏輯性的緣故。
另見
Covers built-in tags, built-in filters, using an alternative template, language, and more.
譯者:Django 文檔協(xié)作翻譯小組,原文:Language overview。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉載請保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。
{% endraw %}