Django模板語言詳解

本節將介紹Django模版系統的語法。Django模版語言致力於在性能和簡單性上取得平衡。css

若是你有過其它編程背景,或者使用過一些在HTML中直接混入程序代碼的語言,那麼你須要記住,Django的模版系統並非簡單的將Python嵌入到HTML中。html

1、模板

模版是純文本文件,能夠生成任何基於文本的文件格式,好比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 %} 

2、變量

變量看起來就像是這樣: {{ variable }}。django

當模版引擎遇到一個變量,它將從上下文context中獲取這個變量的值,而後用值替換掉它自己。編程

變量的命名包括任何字母數字以及下劃線("_")的組合。點(".")也有可能會在變量名中出現,不過它有特殊的含義。最重要的是,變量名稱中不能有空格或標點符號api

當模版系統遇到點("."),它將以這樣的順序查詢這個圓點具體表明的功能:瀏覽器

  • 字典查詢(Dictionary lookup)
  • 屬性或方法查詢(Attribute or method lookup)
  • 數字索引查詢(Numeric index lookup)

若是你使用的變量不存在,模版系統將插入string_if_invalid選項的值,默認設置爲''(空字符串)。安全

注意,像{{ foo.bar }}這種模版表達式中的「bar」,若是在模版上下文中存在,將解釋爲一個字面意義的字符串而不是使用變量bar的值 。app

3、過濾器

過濾器看起來是這樣的:{{ name|lower }}。使用管道符號(|)來應用過濾器。該過濾器將文本轉換成小寫。

過濾器能夠「連接」。一個過濾器的輸出應用於下一個過濾器。例如:{{ text|escape|linebreaks }}就是一個經常使用的過濾器鏈,它首先轉移文本內容,而後把文本行轉成<p>標籤。

一些過濾器帶有參數。 過濾器的參數看起來像是這樣: {{ bio|truncatewords:30 }}。 這將顯示bio變量的前30個詞。

過濾器參數包含空格的話,必須用引號包起來。例如,使用逗號和空格去鏈接一個列表中的元素,你須要使用{{ list|join:", " }}

Django提供了大約六十個內置的模版過濾器,不少時候你想要的功能,它都已經提供了,常常查看這些過濾器,發現新大陸吧。下面是一些經常使用的模版過濾器:

1. default

爲false或者空變量提供默認值,像這樣:

{{ value|default:"nothing" }}

2. length

返回值的長度。它對字符串和列表都起做用。

{{ value|length }}

若是value是['a', 'b', 'c', 'd'],那麼輸出4。

3. filesizeformat

格式化爲「人類可讀」文件大小單位(即'13 KB',4.1 MB','102 bytes'等)。

{{ value|filesizeformat }}

若是value是123456789,輸出將會是117.7MB。

咱們能夠建立自定義的模板過濾器和標籤,這是最終極的武器。

4、標籤

標籤看起來像是這樣的: {% tag %}

標籤比變量複雜得多,有些用於在輸出中建立文本,有些用於控制循環或判斷邏輯,有些用於加載外部信息到模板中供之後的變量使用。

一些標籤須要開始和結束標籤(即 {% 標籤 %} ... 標籤 內容 ... {% ENDTAG %})。

Django自帶了大約24個內置的模版標籤。下面是一些經常使用的標籤:

1. for循環標籤

循環對象中每一個元素。須要結束標籤{% endfor %} 。例如,顯示athlete_list中提供的運動員列表:

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

2. if,elif和else標籤

計算一個表達式,而且當表達式的值是「True」時,顯示塊中的內容。須要{% endif %}結束標籤。總體邏輯很是相似Python的if、elif和else,以下所示。:

{% 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不爲空,將顯示「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 %}

須要注意,大多數模版過濾器都返回字符串類型,因此使用過濾器作整數類型的比較一般是錯誤的,但length是一個例外。

3. block和extends標籤

繼承和複寫模版。相似Python的類繼承和重寫機制。

5、註釋

要註釋模版中一行的部份內容,使用註釋語法:{# #}

例如,下面的模版將被渲染爲'hello':

{# greeting #}hello

註釋能夠包含任何模版內的代碼,有效的或者無效的均可以。 像這樣:

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

以上是單行註釋(在{# .... #}中,不容許有新行)。

若是須要註釋掉模版中的多行內容,請使用comment標籤。

6、模板繼承

Django模版引擎中最強大也是最複雜的部分就是模版繼承了。模版繼承容許你建立一個包含基本「骨架」的父親模版,它包含站點中的共有元素,而且能夠定義可以被子模版覆蓋的blocks。

經過下面這個例子,理解模版繼承的概念:

<!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骨架。

「子模版」須要作的是先繼承父模板base.html,而後複寫、填充,或者說實現其中的blocks。

block是在子模版中可能會被覆蓋掉的位置。在上面的例子中,block標籤訂義了三個能夠被子模版內容填充的block,分別是title、content和siderbar。

再看下面的例子,子模版可能看起來是這樣的:

{% 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。 根據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,這種狀況下,將使用父模版中的內容。父模版的{% block %}標籤中的內容老是被用做默認內容。

Django還支持多級繼承!經常使用方式是相似下面的三級結構:

  • 建立一個base.html模版,用來控制整個站點的主要視覺和體驗。
  • 爲站點的每個app,建立一個base_SECTIONNAME.html模版。 例如base_news.html,base_sports.html。這些模版都繼承base.html,而且包含了各自特有的樣式和設計。
  • 爲每個頁面類型,建立獨立的模版,例如新聞內容或者博客文章。 這些模版繼承對應app的模版。

上面的方式可使代碼獲得最大程度的複用,而且使得添加內容到共享的內容區域更加簡單,例如app範圍內的導航條。

下面是使用繼承的一些相關說明:

  • 若是在模版中使用{% extends %}標籤,它必須是模版中的第一個標籤,必須放在文件首行!

  • 在base模版中設置越多的{% block %}標籤越好。子模版沒必要定義所有父模版中的blocks,因此能夠在大多數blocks中填充合理的默認內容,而後,只定義你須要的那一個。多一點鉤子總比少一點好。

  • 若是發現你本身在複製大量重複的模版內容,那意味着你應該把重複的內容移動到父模版中的一個{% block %}中。

  • 若是須要獲取父模板中的block的內容,可使用{{ block.super }}變量。若是想要在父block中新增內容而不是徹底覆蓋它,這將很是有用。使用{{ block.super }} 插入的數據不會被自動轉義,由於父模板中的內容已經被轉義。

  • {% block %}以外建立的變量使用模板標籤的as語法,不能在塊內使用。

例如,下面的模板不會顯示任何內容:

{% trans "Title" as title %} {% block content %}{{ title }}{% endblock %}
  • 爲了更好的可讀性,能夠給{% endblock %}標籤一個取名字,像這樣:

    {% block content %} ...

在大型模版中,這有助於你清楚的看到哪個{% block %}標籤被關閉了。

  • 最後,請注意不能在一個模版中定義多個相同名字的block標籤。

7、自動轉義HTML

當從模版中生成HTML文件時,總會存在各類風險,好比xss代碼注入等惡意攻擊。好比下面的模版片斷:

Hello, {{ name }}

首先,它看起來像是無害的,用來顯示用戶的名字,可是設想一下,若是用戶像下面這樣輸入他的名字,會發生什麼:

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

使用這個名字的值,模版將會被渲染成這樣:

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

這意味着瀏覽器會彈出一個JavaScript警報框!

相似的,若是名字包含一個 '<' 符號(好比下面這樣),會發生什麼呢?

<b>username

這將會致使模版被渲染成這樣:

Hello, <b>username

這會致使網頁的其他部分被粗體化!

顯然,用戶提交的數據都被不該該被盲目的信任,而且被直接插入到網頁中,由於一個懷有惡意的用戶可能會使用這樣的漏洞來作一些壞事。 這種類型的安全問題被叫作跨站腳本攻擊(Cross Site Scripting)(XSS)。

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

  • 第一,對每一個不被信任的值運行escape過濾器,這將把潛在的有害的HTML字符轉換成無害的字符串。在Django最初的幾年裏,這是默認的解決方案,但問題是它將責任放在開發人員/模板做者身上,以確保轉義了全部內容,並且很容易忘記轉義數據。
  • 第二,利用Django的自動HTML轉義功能。默認狀況下,Django中的每一個模板會自動轉義每一個變量。也就是說,下面五個字符將被轉義:

    • <會轉換爲&lt;
    • >會轉換爲&gt;
    • '(單引號)轉換爲&#39;
    • "(雙引號)會轉換爲&quot;
    • &會轉換爲&amp;

強烈建議:將第二種功能作爲默認打開的設置,不要關閉它!

可是,凡事都有正反兩面。有時,模板變量含有一些你打算渲染成原始HTML的數據,你並不想轉義這些內容。 例如,你可能會在數據庫中儲存一些HTML代碼,而且直接在模板中嵌入它們。或者,你可能使用Django的模板系統來生成不是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的縮寫。請確保你知道本身在用safe過濾器幹什麼!在上面的例子中,若是data含有<b>,輸出會是:

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

對於模板塊:

要控制模板上的自動轉義,將模板(或者模板中的特定區域)包裹在autoescape標籤中,像這樣:

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

autoescape標籤接受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 %}

自動轉義標籤autoescape還會做用於擴展(extend)了當前模板的模板,以及經過include標籤包含的模板,就像全部block標籤那樣。 看下面的例子:

# 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 %} 

因爲自動轉義標籤在base模板中關閉,它也會在child模板中關閉,致使當greeting變量含有<b>Hello!</b>字符串時,會渲染HTML。

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

過濾器的字符串參數:

以前咱們展現過,過濾器的參數能夠是字符串:

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

要注意,全部這種字符串參數在插入模板時都不會進行任何自動轉義。緣由是,模板的做者能夠控制字符串字面值的內容,因此能夠確保在模板編寫時文本通過正確轉義。白話講,就是,你個程序員對本身傳遞的參數內心要有數!

也便是說你應該這樣編寫:

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

而不是:

{{ data|default:"3 < 2" }} {# 錯誤的作法#}

8、方法調用

這部份內容,若是你掌握的極大提升你的模版語言能力。

大多數對象上的方法調用一樣可用於模板中。這意味着模板可以訪問到的不只僅是對象的屬性(好比字段名稱)和視圖中傳入的變量,還能夠執行對象的方法。 例如,Django ORM提供了「entry_set」語法用於查找關聯到外鍵的對象集合。 因此,若是模型「comment」有一個外鍵關聯到模型「task」,能夠根據task遍歷其全部的comments,像這樣:

{% 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有意限制了模板語言中的處理邏輯,不可以在模板中傳遞參數來調用方法。數據應該在視圖中處理,而後傳遞給模板用於展現。這點不一樣於Django的ORM操做。

9、多對多調用

對於以下的模型:

from django.db import models # Create your models here. class Student(models.Model): name = models.CharField(max_length=128) class Course(models.Model): name = models.CharField(max_length=128) students = models.ManyToManyField('Student') 

模型Course有一個多對多字段指向Student模型。

正向查詢

假設編寫了一個以下的視圖:

def test(request): course = models.Course.objects.get(pk=1) return render(request, 'course.html', locals()) 

獲取了id爲1的course對象,並將它傳遞給course.html模版,模版代碼以下:

{% for student in course.students.all %}

<p>{{ student.name }}</p> {% endfor %} 

首先經過course.students.all,查尋到course對象關聯的students對象集,而後用for標籤循環它,獲取每一個student對象,再用student模型的定義,訪問其各個字段的屬性。

反向查詢

對於反向查詢,從student往course查,假設有以下的視圖:

def test2(request): student = models.Student.objects.get(pk=1) return render(request, 'student.html', locals()) 

獲取了id爲1的student對象,並將它傳遞給student.html模版,模版代碼以下:

{% for course in student.course_set.all %} {{ course.name }} {% endfor %}

經過student.course_set.all,反向獲取到student實例對應的全部course對象,而後再for標籤循環每一個course,調用course的各類字段屬性。

對於外鍵ForeignKey,其用法基本相似。只不過正向是obj.fk,且只有1個對像,不是集合。反向則是obj.fk_set,相似多對多。

10、使用自定義標籤和過濾器

某些應用提供了自定義的標籤和過濾器。想要在模板中使用它們,首先要確保該應用已經在INSTALLED_APPS 中(好比在下面的例子中,咱們添加了'django.contrib.humanize'),以後在模板中使用load標籤:

{% load humanize %} {{ 45000|intcomma }}

上面的例子中, load標籤加載了humanizeapp的標籤庫,以後咱們可使用它的intcomma過濾器。

若是你開啓了django.contrib.admindocs,能夠查詢admin站點中的文檔,查看你安裝的自定義庫列表。

load標籤能夠同時接受多個庫名稱,由空格分隔。 例如:

{% load humanize i18n %}

自定義庫和模板繼承:

當你加載一個自定義標籤或過濾器庫時,標籤或過濾器只在當前模板中有效--並非帶有模板繼承關係的任何父模板或者子模版中都有效。白話說就是,你在父模板中可能加載了自定義標籤,然並卵,你在子模版中還要再加載一次!

例如,若是一個模板foo.html帶有{% load humanize %},子模版(例如,帶有{% extends "foo.html" %})中不能訪問humanize模板標籤和過濾器。 子模版須要再添加本身的{% load humanize %}

這個特性是出於保持可維護性和邏輯性的目的。

相關文章
相關標籤/搜索