Django 模板語言(譯)

聲明:轉載請註明出處http://www.jianshu.com/p/1664dcfd840c

最近在折騰Django,看到官網的Django模板文章寫得挺詳細的,就想着翻譯下以加深自己的學習印象,如果能幫助到大家我也會感到很欣慰,以下是譯文。

這篇文章主要講解了Django模板系統語法,如果你想了解更多關於Django模板如何工作以及如何擴展的話,可以看這篇文章The Django template language: for Python programmers.

Django 模板語言意在強大和輕鬆之間取得平衡。這讓那些習慣用HTML的人感到很舒服。如果你有接觸過任何其他的基於文本的模板語言,比如 Smarty 或者 Jinja2,那麼你應該對Django的模板語言倍感親切。

注意:
如果你有編程背景,或者習慣於那些混合代碼直接嵌入到HTML中的語言,你要記住Django模板系統不是將Python簡單的嵌入HTML中。這是它設計的初衷:模板系統主要爲了表達而不是程序邏輯。

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的模板呢?我們是想讓Django的模板語言用在更多的地方而不是僅僅在XML或HTML中。在聯網的世界裏,我們把它用在電子郵箱,JavaScript以及CSV。你可以在任何文本格式中使用模板語言。

哦,順便說下,讓人們編輯XML文件是很苦逼的。

變量

變量看起來像這樣:{{ 變量名 }},當模板引擎遇到變量時,會計算這個變量,並把結果賦值給它。變量名是由任何的數字,字母以及下劃線組成。點(“.”)也會出現在變量部分,當然他有特殊用途,稍後會說明。特別注意的是,變量名中不能出現空格符以及任何的標點符號。

上面說到點也會出現在變量部分,它的作用就是用來訪問變量的屬性的。

背景

嚴格來講,當模板系統遇到點時,它會根據以下的順序查找:

  • 字典查找
  • 屬性或方法查找
  • 數字索引查找

如果結果的值是可調用,那麼它就會被無參數調用。調用的結果就會變成模板中的值。

這個查找順序會對那些覆寫了字典查找的對象造成一些不可預期的行爲。比如下面是遍歷collections.defaultdict的代碼片:

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

因爲先發生字典查找,所以這不可預期的行爲就發生了,它會返回defaultdict的默認值而不會調用它的iteritems()方法。在這個例子中,可以先轉化成字典。

在上面的模板例子中,{{ section.title }}會被section對象的title屬性代替。

如果你調用的變量不存在,那麼模板系統將默認插入空字符串‘’

注意:如果模板的上下文(context)中傳入“bar”變量,而模板中存在{{ foo.bar }}表達式,那麼該表達式僅僅具有字面意思,即調用foo的bar屬性,而不會調用context中“bar”的值。

過濾器(FIlters)

你可以用filters來改變變量最終顯示的值。

過濾器看起來就像這樣{{ name|lower }}。這是將name變量通過lower過濾器全部轉換爲小寫字母。|用來調用過濾器。

過濾器可以是鏈式的,一個過濾器的輸出會被用在下一個。{{ text|escape|linebreaks }} 是一種通用的形式用來將文本內容轉義,然後再轉爲<P>標籤以適用HTML。

有些過濾器帶參數。如以下帶參過濾器:{{ bio|truncatewords:30 }}意思是隻展示bio變量的前30個字符。

如果過濾器參數含有空格那麼參數必須被引號引起來;比如想要用逗號和空格將list中的元素拼接起來,你可以這樣{{ list|join:“, ”}}。

Django提供了大約60個內置模板過濾器。你可以到built-in filter reference.查閱更多的過濾器。爲了讓你嘗試下,下面是一些常用的過濾器:

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.7MB。

重申下,這裏僅是很少的例子,可以到built-in filter reference.查看更多完整列表。

當然你也可以創建自定義模板過濾器,可以查閱這篇文章Custom template tags and filters.

提示
Django 的admin交互界面使用了所有的模板標籤和過濾器。你可以參考這篇文章The Django admin documentation generator.

標籤(Tags)

標籤的形式是 {% tag %}。標籤比變量要更復雜:有些標籤在輸出中創建文本,有些通過展示循環和邏輯來控制流,有些引入額外的信息到模板中以供後續變量使用。

有些標籤需要開頭和結尾(比如:{% tag %}...標籤內容...{% endtag %})

Django大約有兩打(24個)內置模板標籤。你可以查閱這篇文檔built-in tag reference.。以下僅列舉一些常用標籤:

for

循環遍歷數組或列表,比如展示名爲athete_list的運動員列表:

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

if,elif,else

{% if athlete_list %}
    Number of athletes: {{ athete_list|lengh }}
{% 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列表不爲空,則顯示“Athletes should be out of the locker room soon!”信息。如果兩個列表都爲空,則顯示“”No athletes.”。

你可以在if標籤中使用過濾器以及進行一系列的操作:

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

當上面的代碼運行時,注意大部分模板過濾器是以字符串類型返回的,所以上面過濾器返回的length是無法與數字1進行比較的。

block,extend

用於模板繼承(下面會講到),一種 給力的方法可以在模板中減少樣板的使用。

再說下,上面僅是模板標籤的一部分,查看更多內容請移步至此built-intag reference

當然你可以參考這篇文檔Custom template tags and filters來創建你自己的模板標籤。

提示
Django 的admin交互界面使用了所有的模板標籤和過濾器。你可以參考這篇文章The Django admin documentation generator.

註釋

可以用{# #}來註釋模板中的某一行。

如下,下面只有“hello”會在模板中起作用。

{# greeting #}hello

註釋中可以包含任何模板語句,如下:

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

上述的註釋語句只能用於單行註釋({ # # }該註釋語句中不允許存在多行)。如果你想用多行註釋,請移步至此註釋

模板繼承

在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標籤,然後會將子模板的內容填充到父模板相應的區塊中。最後的結果如下({% block content %}區塊裏具體的內容是由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_部分名稱.html的模板,比如,base_news.html,base_sports.html。這些模板都是繼承base.html模板,並且都有自己的設計樣式。
  • 爲每一種類型的網頁創建個性化模板,比如新聞或者博客,它們都是繼承上述相應的部分模板

上述的繼承方案可以極大化的重用你的代碼,並且方便擴展。

以下是使用模板繼承需要注意的地方:

  • 如果你要在模板標籤中使用{% extends %},那麼它必須作爲第一個模板標籤,否則模板繼承將失效。
  • 在你的基礎模板中多使用{ % block % }。記住,在子模板中不一定都要重寫父模板的block標籤,所以你可以在父模板中多定義些block,在裏面填充合理的默認值,這樣在子模板中你可以根據需要重寫相應的block,其他的使用父模板的默認值就可以了。俗話說,有備無患嗎。
  • 如果你發現在很多模板中有相同的內容,那麼你可以將這些相同的內容提取出來放在父模板的block中。
  • 如果你想獲取父模板block中的內容,你可以使用變量{{ block.super }}。如果你想要增加父模板block的內容,而不是重寫它,那麼這個變量將會非常有用。使用該變量插入的數據不會被自動轉義(下面會提到),因爲如果有必要的話它已經在父模板中被轉以了。
  • 在{ % block % }標籤外面用as標籤創建的變量是無法在{ % block % }內部使用的,如下,該模板不會顯示任何內容:
{% trans "Title" as title %}
{% block content %}{{ title }}{% endblock %}
  • 另外,你可以給 {% endblock %} 標籤取任意名字,比如:
{% block content %}
...
{% endblock content %}

在很大的模板中,這可以讓你知道哪個block標籤正在關閉。

最後,注意你不能在同一模板中定義相同名字的block標籤,這是因爲block標籤是雙向作用的,block標籤不僅僅提供區域讓子模板填充,它也可以在父模板中填充相應的內容。如果在模板中有相同名字的block標籤,那麼,父模板將不知道到底使用哪個block中的內容來填充。

HTML自動轉義

當通過模板生成HTML時,有個風險,就是變量中可能存在一些特殊字符會影響HTML的結果。比如,如下模板片段:

hello, {{ name }}

這從表面上看上去沒什麼問題,但是你需要考慮到,如果用戶的變量name輸入以下內容會發生什麼:

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

如果name變量是以上內容,那麼最後HTML中會是這樣的結果:

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

...這意味着瀏覽器會彈出一個腳本對話框!

類似的,如果name變量包含‘<’字符呢,比如這樣:

<b>username

這會導致網頁的剩餘部分加粗。

顯然,用戶提交的數據是不能盲目信任的,並且不能直接插入到網頁中,因爲會有惡意的用戶利用這種漏洞來幹壞事。這種安全漏洞叫做跨站腳本攻擊(XSS)。

爲了避免這個問題,你有兩種選擇:

  • 一種,你可以利用escape過濾器來運行每個不被信任的變量,這會把有潛在危險的字符轉換爲安全的。當然在Django推出的頭幾年,這是一種默認的解決方法。但是存在一個問題,你需要確保每個變量都被轉義,顯然我們很容易漏掉一些數據。

  • 第二種,你可以利用Django的HTML自動轉義機制。這部分的剩餘部分會講解自動轉義是如何工作的。

在Django中,每個模板都會默認轉義每個變量,以下是五個比較特別的字符轉義。

  • < 會被轉義成<
  • > 會被轉義成>
  • '(單引號)會被轉義成'
  • " (雙引號)會被轉義成"
  • & 會被轉義成&

在強調一遍,這種自動轉義行爲是默認的,當你在使用Django的模板系統時你是受保護的。

如果關閉自動轉義

如果你不想要數據被自動轉義,比如你不想要某個網頁,或者某個模板亦或者某個變量被轉義的話,你有幾個方法來關閉它。

爲什麼你會想關閉自動轉義呢?因爲有時候你想把原生的HTML語句嵌入到模板變量中,在這種情況下,你是不會想要將變量裏的內容轉義的。舉個例子,你可能在數據庫中存了一點HTML,你想把它直接嵌入到模板中。或者,你可能正在用Django的模板系統生成非HTML文本,比如電子郵箱信息。

關閉變量的自動轉義

可以用safe過濾器來關閉單個變量的自動轉義:

這會被轉義: {{ data }}
這不會被轉義: {{ data|safe }}

如果data變量中存在<b>,那麼上面兩句輸出的結果是這樣的:

這會被轉義: <b>
這不會被轉義: <b>

關閉模板區塊的自動轉義

爲了控制模板的自動轉義,需要在外面套一層autoescape標籤,比如:

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

你可以用on或者off來控制是否自動轉義,如下例子:

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 & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}

由於在base.html中自動轉義是關閉的,在他的子模板child.html中也將是關閉的。如果greeting變量傳入字符串<b>Hello!</b>,那麼模板最終的結果將會是如下:

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

注意事項

通常來講,我們不用過多的擔心自動轉義。只有在寫views時或者自定義過濾器時需要注意自動轉義,併合理的標記數據。所以盡情的使用模板吧。

如果你創建的模板在被使用時無法確定自動轉義是否打開,你可以添加escape過濾器到每個需要轉義的變量上。當自動轉義已開啓,escape過濾器對已轉義的變量進行再次轉義並不會對該變量產生影響。

字符串與自動轉義

在前面提到,過濾器的參數可以是字符串:

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

所有在模板中的字面上的字符串都不會經過轉義--他們看上去像使用了safe過濾器。這背後的原因是字符串內容是由模板創建者決定的,所以他們可以確保文本是被正確轉義的。

也就是說你應該這樣寫:

{{ data|default:"3 < 2" }}

而不是這樣:

{{ data|default:"3 < 2" }}  {# 別這樣寫! #}

這不會影響變量data自身,變量的內容在必要時仍然會被自動轉義,因爲這不是模板作者能控制的。

訪問方法調用

大多數綁定在對象上的方法調用也存在於模板中。這意味着模板可以訪問的不僅僅是類的屬性以及從view中傳來的變量。舉個例子,Django的ORM(對象關係映射)框架提供個一個叫“entry_set”語法,這可以找到由外鍵關聯的對象集。所以如果模型“comment”有個外鍵關聯到模型“task”的話,你可以遍歷該task的所有comments:

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

類似的,QuerySets提供了一個叫count()的方法用來計算所包含對象的總個數。因此你可以得到關聯到task上的comment總和:

{{ task.comment_set.all.count }}

當然你可以輕鬆的訪問你之前已經定義過的模型中的方法:

models.py:

class Task(models.Model):
    def foo(self):
        return "bar"

template.html:

{{ task.foo }}

由於Django有意限制了模板語言中的邏輯處理,你不可以在方法中傳遞參數,數據應該在view處理,再傳入模板中展示。

自定義標籤和過濾器庫

一些應用提供了自定義標籤和過濾器的庫,要想在模板中使用,請確保該應用在INSTALLED_APPS(這裏我添加了'django.contrib.humanize'庫),然後用load標籤來加載:

{% load humanize %}

{{ 45000|intcomma }}

在上面例子中,我們用load標籤來加載humanize標籤庫,這樣我們就可以使用該庫中的intcomma過濾器了。如果你開啓了django.contrib.admindocs,你可以在admin站查詢文檔然後找出你安裝的自定義庫列表。

load標籤後面可以跟多個庫名,當中用空格隔開:

{% load humanize i18n %}

查看Custom template tags and filters文章來獲取更多關於自定義模板庫的信息。

自定義庫與模板繼承

當你加載自定義標籤或過濾器庫時,這個庫中的標籤以及過濾器只能在當前模板使用,在其父模板或子模板是不起作用的。

比如,有一個模板foo.html寫了{% load humanize %},其子模板中寫了{ % extends “foo.html” %},但子模板還是無法訪問父模板中humanize庫中的標籤和過濾器的,要想訪問就必須在子模板中再次添加{% load humanize %}

這樣的特性主要是爲了項目的維護性和邏輯性。

擴展
模板參考
含內置標籤,內置過濾器,使用其他模板,語言,以及更多其他內容

官方原文:https://docs.djangoproject.com/en/1.11/ref/templates/language/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章