Django 模板語言

本文說明 Django 模板系統的語言語法。如果您正在尋找關於其運作方式以及如何擴充它的更技術性的觀點,請參閱 Django 模板語言:給 Python 程式設計師的說明

Django 的模板語言旨在在功能和易用性之間取得平衡。它旨在讓那些習慣使用 HTML 的人感到舒適。如果您接觸過其他基於文字的模板語言,例如 SmartyJinja2,您應該會對 Django 的模板感到賓至如歸。

理念

如果您有程式設計背景,或者您習慣使用將程式碼直接混入 HTML 的語言,您會想要記住 Django 模板系統不僅僅是嵌入 HTML 的 Python。這是經過設計的:模板系統旨在表達呈現,而不是程式邏輯。

Django 模板系統提供類似於某些程式結構的標籤 – 用於布林測試的 if 標籤,用於迴圈的 for 標籤等 – 但這些標籤不僅僅是作為相應的 Python 程式碼執行,而且模板系統也不會執行任意的 Python 運算式。預設只支援以下列出的標籤、篩選器和語法(儘管您可以根據需要將 您自己的擴充功能新增至模板語言)。

模板

模板是一個文字檔案。它可以產生任何基於文字的格式(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 模板。您可以將模板語言用於任何基於文字的格式,例如電子郵件、JavaScript 和 CSV。

變數

變數看起來像這樣: {{ variable }}。當模板引擎遇到變數時,它會評估該變數並將其替換為結果。變數名稱由字母數字字元和底線 ("_") 的任意組合組成,但不能以底線開頭,也不能是數字。點 (".") 也會出現在變數區段中,儘管它具有特殊的含義,如下所示。重要的是,變數名稱中不能有空格或標點符號。

使用點 (.) 來存取變數的屬性。

幕後原理

從技術上講,當模板系統遇到點時,它會按以下順序嘗試查詢:

  • 字典查詢

  • 屬性或方法查詢

  • 數值索引查詢

如果結果值是可呼叫的,則會呼叫它且不帶引數。呼叫的結果會變成模板值。

此查詢順序可能會導致某些物件覆寫字典查詢時出現意外行為。例如,請考慮以下程式碼片段,該程式碼片段嘗試迴圈遍歷 collections.defaultdict

{% for k, v in defaultdict.items %}
    Do something with k and v here...
{% endfor %}

由於字典查詢先發生,因此該行為會啟動並提供預設值,而不是使用預期的 .items() 方法。在這種情況下,請考慮先轉換為字典。

在上面的範例中,{{ section.title }} 將被替換為 section 物件的 title 屬性。

如果您使用不存在的變數,模板系統會插入 string_if_invalid 選項的值,該選項預設設定為 ''(空字串)。

請注意,模板運算式(如 {{ foo.bar }})中的「bar」會被解釋為文字字串,而不是使用變數「bar」的值(如果範本內容中存在一個)。

開頭為底線的變數屬性可能無法存取,因為它們通常被視為私有。

篩選器

您可以使用篩選器來修改變數以進行顯示。

篩選器看起來像這樣: {{ name|lower }}。這會顯示 {{ name }} 變數的值,該值已透過 lower 篩選器篩選,該篩選器會將文字轉換為小寫。使用管道符號 (|) 來套用篩選器。

篩選器可以「串聯」。一個篩選器的輸出會套用到下一個篩選器。{{ text|escape|linebreaks }} 是一種常見的慣用法,用於跳脫文字內容,然後將換行符號轉換為 <p> 標籤。

某些篩選器會接受引數。篩選器引數看起來像這樣:{{ bio|truncatewords:30 }}。這會顯示 bio 變數的前 30 個單字。

包含空格的篩選器引數必須用引號括起來;例如,若要使用逗號和空格聯結清單,您可以使用 {{ list|join:", " }}

Django 提供了大約六十個內建範本篩選器。您可以在 內建篩選器參考中閱讀所有相關資訊。為了讓您瞭解可用的內容,以下是一些更常用的範本篩選器

default

如果變數為 false 或空白,則使用給定的預設值。否則,請使用變數的值。例如

{{ value|default:"nothing" }}

如果未提供 value 或為空白,則上述內容會顯示「nothing」。

length

傳回值的長度。這適用於字串和清單。例如

{{ value|length }}

如果 value['a', 'b', 'c', 'd'],則輸出會是 4

filesizeformat

將值格式化為「人類可讀」的檔案大小(即 '13 KB''4.1 MB''102 bytes' 等)。例如

{{ value|filesizeformat }}

如果 value 為 123456789,則輸出會是 117.7 MB

同樣,這些只是一些範例;如需完整清單,請參閱內建篩選器參考

您也可以建立自己的自訂範本篩選器;請參閱如何建立自訂範本標籤和篩選器

另請參閱

Django 的管理介面可以包含適用於特定網站的所有範本標籤和篩選器的完整參考。請參閱Django 管理文件產生器

標籤

標籤看起來像這樣: {% tag %}。標籤比變數更複雜:有些會在輸出中建立文字,有些會透過執行迴圈或邏輯來控制流程,有些則會將外部資訊載入到範本中,供後續的變數使用。

有些標籤需要起始和結束標籤(即 {% tag %} ... tag contents ... {% endtag %})。

Django 內建了大約二十幾個範本標籤。您可以在內建標籤參考中閱讀所有相關資訊。為了讓您體驗一下可用的功能,以下是一些更常用的標籤:

for

迴圈遍歷陣列中的每個項目。例如,要顯示 athlete_list 中提供的運動員列表:

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
ifelifelse

評估一個變數,如果該變數為「真」,則顯示區塊的內容。

{% 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 不為空,則運動員的數量將由 {{ athlete_list|length }} 變數顯示。否則,如果 athlete_in_locker_room_list 不為空,則會顯示訊息「運動員應該出去了...」。如果兩個列表都為空,則會顯示「沒有運動員」。

您也可以在 if 標籤中使用篩選器和各種運算符。

{% if athlete_list|length > 1 %}
   Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
   Athlete: {{ athlete_list.0.name }}
{% endif %}

雖然上面的範例可行,但請注意,大多數範本篩選器會返回字串,因此使用篩選器進行數學比較通常不會如您預期的那樣運作。length 是一個例外。

blockextends

設定範本繼承(請參閱下文),這是一種強大的方法,可以減少範本中的「樣板」程式碼。

再次說明,以上僅是完整清單中的一部分;請參閱內建標籤參考以獲取完整清單。

您也可以建立自己的自訂範本標籤;請參閱如何建立自訂範本標籤和篩選器

另請參閱

Django 的管理介面可以包含適用於特定網站的所有範本標籤和篩選器的完整參考。請參閱Django 管理文件產生器

註解

要註解掉範本中一行的部分內容,請使用註解語法:{# #}

例如,此範本會呈現為 'hello'

{# greeting #}hello

註解可以包含任何範本程式碼,無論是否有效。例如:

{# {% if foo %}bar{% else %} #}

此語法只能用於單行註解({##} 定界符之間不允許有換行符)。如果您需要註解掉範本的多行部分,請參閱 comment 標籤。

範本繼承

Django 範本引擎最強大(因此也最複雜)的部分是範本繼承。範本繼承允許您建構一個基本的「骨架」範本,其中包含您網站的所有通用元素,並定義子範本可以覆寫的區塊

讓我們從一個範例開始探討範本繼承

<!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 骨架文件,您可以使用該文件來建立一個雙欄頁面。「子」範本的工作是用內容填滿空白區塊。

在此範例中,block 標籤定義了三個子範本可以填寫的區塊。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 中的三個 block 標籤,並將這些區塊替換為子範本的內容。根據 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 %} 標籤內的內容始終用作後備選項。

您可以根據需要使用多個層級的繼承。一種常見的使用繼承方式是以下三層方法:

  • 建立一個 base.html 範本,其中包含您網站的主要外觀和風格。

  • 為您網站的每個「區段」建立一個 base_SECTIONNAME.html 範本。例如,base_news.htmlbase_sports.html。這些範本都會擴充 base.html,並包含特定於區段的樣式/設計。

  • 為每種類型的頁面建立個別範本,例如新聞文章或部落格文章。這些範本會擴充適當的區段範本。

此方法可以最大化程式碼重複使用,並有助於將項目新增到共用內容區域,例如整個區段的導覽。

以下是一些使用繼承的技巧:

  • 如果您在範本中使用 {% extends %},則它必須是該範本中的第一個範本標籤。否則,範本繼承將不起作用。

  • 在您的基礎範本中,{% block %} 標籤越多越好。請記住,子範本不必定義所有父區塊,因此您可以在多個區塊中填寫合理的預設值,然後僅定義稍後需要的區塊。擁有的掛鉤越多越好。

  • 如果您發現自己在多個範本中重複內容,則可能表示您應該將該內容移至父範本中的 {% block %}

  • 如果您需要從父範本取得區塊的內容,則 {{ block.super }} 變數可以做到這一點。如果您想要新增至父區塊的內容,而不是完全覆寫它,這會很有用。使用 {{ block.super }} 插入的資料不會自動逸出(請參閱下一節),因為如果需要在父範本中進行逸出,則已經逸出。

  • 透過使用與您要繼承的範本相同的範本名稱,{% extends %} 可以用於在覆寫範本的同時繼承範本。結合使用 {{ block.super }},這可以成為進行小型自訂的強大方法。有關完整範例,請參閱覆寫範本操作指南中的擴充已覆寫的範本

  • 在範本標籤 as 語法中使用 {% block %} 之外建立的變數不能在區塊內使用。例如,此範本不會呈現任何內容:

    {% translate "Title" as title %}
    {% block content %}{{ title }}{% endblock %}
    
  • 為了提高可讀性,您可以選擇性地為 {% endblock %} 標籤提供一個名稱。例如:

    {% block content %}
    ...
    {% endblock content %}
    

    在較大的範本中,此技術有助於您查看哪些 {% block %} 標籤正在關閉。

  • {% block %} 標籤會先評估。這就是為什麼區塊的內容始終被覆寫的原因,而不管周圍標籤的真值如何。例如,此範本將始終覆寫 title 區塊的內容:

    {% if change_title %}
        {% block title %}Hello!{% endblock title %}
    {% endif %}
    

最後,請注意,您不能在同一個範本中定義多個具有相同名稱的 block 標籤。存在此限制的原因是區塊標籤在「兩個」方向上都有效。也就是說,區塊標籤不僅提供了一個要填寫的孔洞,它還定義了填補孔洞的內容。如果範本中有兩個名稱相似的 block 標籤,則該範本的父範本將不知道要使用哪個區塊的內容。

自動 HTML 逸出

從範本產生 HTML 時,總是存在變數包含會影響最終 HTML 的字元的風險。例如,請考慮以下範本片段:

Hello, {{ name }}

乍看之下,這似乎是一種無害的方式來顯示使用者的名稱,但是請考慮如果使用者將其名稱輸入為以下內容會發生什麼:

<script>alert('hello')</script>

使用此名稱值,範本將呈現為:

Hello, <script>alert('hello')</script>

...這表示瀏覽器會彈出一個 JavaScript 警示框!

同樣地,如果名稱包含一個 '<' 符號,例如這樣呢?

<b>username

這會導致呈現的範本如下所示:

Hello, <b>username

...這反過來會導致網頁的其餘部分以粗體顯示!

顯然,不應該盲目信任使用者提交的資料,並將其直接插入您的網頁中,因為惡意使用者可能會利用這種漏洞來執行可能有害的事情。這種安全漏洞類型稱為跨網站指令碼 (XSS) 攻擊。

為避免此問題,您有兩個選項:

  • 第一,您可以確保將每個不受信任的變數都通過 escape 篩選器(如下所述),它會將潛在有害的 HTML 字元轉換為無害的字元。這是 Django 最初幾年的預設解決方案,但問題在於它將確保所有內容都經過跳脫處理的責任放在 *您*,也就是開發人員/模板作者身上。很容易忘記跳脫資料。

  • 第二,您可以利用 Django 的自動 HTML 跳脫功能。本節的其餘部分將說明自動跳脫的運作方式。

在 Django 中,預設情況下,每個模板都會自動跳脫每個變數標籤的輸出。具體來說,以下五個字元會被跳脫:

  • < 會轉換為 &lt;

  • > 會轉換為 &gt;

  • '(單引號)會轉換為 &#x27;

  • "(雙引號)會轉換為 &quot;

  • & 會轉換為 &amp;

再次強調,此行為預設為開啟。如果您正在使用 Django 的模板系統,您就會受到保護。

如何關閉它

如果您不希望資料被自動跳脫,您可以依據網站、模板或變數層級以幾種方式關閉它。

為什麼您會想要關閉它?因為有時候,模板變數包含您 *有意* 要以原始 HTML 呈現的資料,在這種情況下,您不希望其內容被跳脫。例如,您可能會在資料庫中儲存一塊 HTML,並希望將其直接嵌入到您的模板中。或者,您可能會使用 Django 的模板系統來產生 *非* HTML 的文字,例如電子郵件訊息。

針對個別變數

若要停用個別變數的自動跳脫,請使用 safe 篩選器

This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}

將 *safe* 視為 *安全,免受進一步跳脫* 或 *可以安全地解釋為 HTML* 的簡寫。在此範例中,如果 data 包含 '<b>',則輸出將為

This will be escaped: &lt;b&gt;
This will not be escaped: <b>

針對模板區塊

若要控制模板的自動跳脫,請將模板(或模板的特定區段)包裝在 autoescape 標籤中,如下所示

{% autoescape off %}
    Hello {{ name }}
{% endautoescape %}

autoescape 標籤接受 onoff 作為其引數。有時,您可能想要在自動跳脫原本會停用的情況下強制執行自動跳脫。以下是一個範例範本

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 標籤包含的模板,就像所有區塊標籤一樣。例如

base.html
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
child.html
{% extends "base.html" %}
{% block title %}This &amp; that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}

由於自動跳脫在基本範本中已關閉,因此它也會在子範本中關閉,當 greeting 變數包含字串 <b>Hello!</b> 時,會產生下列呈現的 HTML

<h1>This &amp; that</h1>
<b>Hello!</b>

注意事項

一般來說,範本作者不需要太擔心自動跳脫的問題。Python 端 (撰寫檢視和自訂篩選器的人員) 的開發人員需要考慮資料不應該被跳脫的情況,並適當地標記資料,以便在範本中正常運作。

如果您正在建立一個可能會在不確定是否啟用自動跳脫的情況下使用的範本,請將 escape 篩選器新增至任何需要跳脫的變數。當自動跳脫開啟時,escape 篩選器沒有 *重複跳脫* 資料的風險,因為 escape 篩選器不會影響自動跳脫的變數。

字串字面值和自動跳脫

如我們先前所述,篩選器引數可以是字串

{{ data|default:"This is a string literal." }}

所有字串字面值都會被插入到範本中,**而不進行**任何自動跳脫,它們的作用就像它們都透過 safe 篩選器傳遞一樣。這樣做的理由是範本作者可以控制字串字面值中的內容,因此他們可以確保在撰寫範本時正確地跳脫文字。

這表示您會撰寫

{{ data|default:"3 &lt; 2" }}

…而不是

{{ data|default:"3 < 2" }}  {# Bad! Don't do this. #}

這不會影響來自變數本身的資料會發生什麼事。變數的內容仍然會自動跳脫,如果需要,因為它們超出範本作者的控制範圍。

存取方法呼叫

附加到物件的大多數方法呼叫也可以從範本內部使用。這表示範本可以存取的內容遠遠不只有類別屬性 (如欄位名稱) 和從檢視傳遞的變數。例如,Django ORM 提供 「entry_set」 語法來尋找外鍵上相關的物件集合。因此,假設有一個名為「comment」的模型,其與名為「task」的模型有外鍵關係,您可以像這樣迴圈處理附加到特定任務的所有註解

{% for comment in task.comment_set.all %}
    {{ comment }}
{% endfor %}

同樣地,QuerySets 提供 count() 方法來計算它們包含的物件數量。因此,您可以使用下列方式取得與目前任務相關的所有註解計數

{{ task.comment_set.all.count }}

您也可以存取您在自己的模型上明確定義的方法

models.py
class Task(models.Model):
    def foo(self):
        return "bar"
template.html
{{ task.foo }}

由於 Django 有意限制範本語言中可用的邏輯處理量,因此無法將引數傳遞給從範本內部存取的方法呼叫。資料應在檢視中計算,然後傳遞到範本以供顯示。

自訂標籤和篩選器程式庫

某些應用程式提供自訂標籤和篩選器程式庫。若要在範本中存取它們,請確保應用程式位於 INSTALLED_APPS 中 (在此範例中,我們會新增 'django.contrib.humanize'),然後在範本中使用 load 標籤

{% load humanize %}

{{ 45000|intcomma }}

在上述範例中,load 標籤會載入 humanize 標籤程式庫,這會使 intcomma 篩選器可供使用。如果您已啟用 django.contrib.admindocs,您可以查閱管理員中的文件區域,以找到您安裝中自訂程式庫的清單。

load 標籤可以採用多個程式庫名稱,以空格分隔。範例

{% load humanize i18n %}

請參閱 如何建立自訂範本標籤和篩選器,以了解如何撰寫自己的自訂範本程式庫。

自訂程式庫和範本繼承

當您載入自訂標籤或篩選器程式庫時,這些標籤/篩選器僅適用於目前的範本,而不適用於範本繼承路徑中的任何父範本或子範本。

例如,如果範本 foo.html{% load humanize %},則子範本 (例如,具有 {% extends "foo.html" %} 的範本) 將 *無法* 存取 humanize 範本標籤和篩選器。子範本負責自己的 {% load humanize %}

這是為了可維護性和健全性而設的功能。

另請參閱

範本參考

涵蓋內建標籤、內建篩選器、使用替代範本語言等等。

回到頂端