本節將介紹Django模版系統的語法。Django模版語言致力於在性能和簡單性上取得平衡。css
若是你有過其它編程背景,或者使用過一些在HTML中直接混入程序代碼的語言,那麼你須要記住,Django的模版系統並非簡單的將Python嵌入到HTML中。html
模版是純文本文件,能夠生成任何基於文本的文件格式,好比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 %}
變量看起來就像是這樣: {{ variable }}。
django
當模版引擎遇到一個變量,它將從上下文context中獲取這個變量的值,而後用值替換掉它自己。編程
變量的命名包括任何字母數字以及下劃線("_")的組合。點(".")也有可能會在變量名中出現,不過它有特殊的含義。最重要的是,變量名稱中不能有空格或標點符號。api
當模版系統遇到點("."),它將以這樣的順序查詢這個圓點具體表明的功能:瀏覽器
若是你使用的變量不存在,模版系統將插入string_if_invalid
選項的值,默認設置爲''(空字符串)。安全
注意,像{{ foo.bar }}
這種模版表達式中的「bar」,若是在模版上下文中存在,將解釋爲一個字面意義的字符串而不是使用變量bar的值 。app
過濾器看起來是這樣的:{{ name|lower }}
。使用管道符號(|
)來應用過濾器。該過濾器將文本轉換成小寫。
過濾器能夠「連接」。一個過濾器的輸出應用於下一個過濾器。例如:{{ text|escape|linebreaks }}
就是一個經常使用的過濾器鏈,它首先轉移文本內容,而後把文本行轉成<p>
標籤。
一些過濾器帶有參數。 過濾器的參數看起來像是這樣: {{ bio|truncatewords:30 }}
。 這將顯示bio變量的前30個詞。
過濾器參數包含空格的話,必須用引號包起來。例如,使用逗號和空格去鏈接一個列表中的元素,你須要使用{{ list|join:", " }}
。
Django提供了大約六十個內置的模版過濾器,不少時候你想要的功能,它都已經提供了,常常查看這些過濾器,發現新大陸吧。下面是一些經常使用的模版過濾器:
爲false或者空變量提供默認值,像這樣:
{{ value|default:"nothing" }}
返回值的長度。它對字符串和列表都起做用。
{{ value|length }}
若是value是['a', 'b', 'c', 'd'],那麼輸出4。
格式化爲「人類可讀」文件大小單位(即'13 KB',4.1 MB','102 bytes'等)。
{{ value|filesizeformat }}
若是value是123456789,輸出將會是117.7MB。
咱們能夠建立自定義的模板過濾器和標籤,這是最終極的武器。
標籤看起來像是這樣的: {% tag %}
。
標籤比變量複雜得多,有些用於在輸出中建立文本,有些用於控制循環或判斷邏輯,有些用於加載外部信息到模板中供之後的變量使用。
一些標籤須要開始和結束標籤(即 {% 標籤 %} ... 標籤 內容 ... {% ENDTAG %}
)。
Django自帶了大約24個內置的模版標籤。下面是一些經常使用的標籤:
循環對象中每一個元素。須要結束標籤{% endfor %}
。例如,顯示athlete_list
中提供的運動員列表:
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul>
計算一個表達式,而且當表達式的值是「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是一個例外。
繼承和複寫模版。相似Python的類繼承和重寫機制。
要註釋模版中一行的部份內容,使用註釋語法:{# #}
。
例如,下面的模版將被渲染爲'hello':
{# greeting #}hello
註釋能夠包含任何模版內的代碼,有效的或者無效的均可以。 像這樣:
{# {% if foo %}bar{% else %} #}
以上是單行註釋(在{# .... #}
中,不容許有新行)。
若是須要註釋掉模版中的多行內容,請使用comment標籤。
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
模版,用來控制整個站點的主要視覺和體驗。base_SECTIONNAME.html
模版。 例如base_news.html
,base_sports.html
。這些模版都繼承base.html
,而且包含了各自特有的樣式和設計。上面的方式可使代碼獲得最大程度的複用,而且使得添加內容到共享的內容區域更加簡單,例如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 %}
標籤被關閉了。
當從模版中生成HTML文件時,總會存在各類風險,好比xss代碼注入等惡意攻擊。好比下面的模版片斷:
Hello, {{ name }}
首先,它看起來像是無害的,用來顯示用戶的名字,可是設想一下,若是用戶像下面這樣輸入他的名字,會發生什麼:
<script>alert('hello')</script>
使用這個名字的值,模版將會被渲染成這樣:
Hello, <script>alert('hello')</script>
這意味着瀏覽器會彈出一個JavaScript警報框!
相似的,若是名字包含一個 '<' 符號(好比下面這樣),會發生什麼呢?
<b>username
這將會致使模版被渲染成這樣:
Hello, <b>username
這會致使網頁的其他部分被粗體化!
顯然,用戶提交的數據都被不該該被盲目的信任,而且被直接插入到網頁中,由於一個懷有惡意的用戶可能會使用這樣的漏洞來作一些壞事。 這種類型的安全問題被叫作跨站腳本攻擊(Cross Site Scripting)(XSS)。
爲避免這個問題,有兩個選擇:
第二,利用Django的自動HTML轉義功能。默認狀況下,Django中的每一個模板會自動轉義每一個變量。也就是說,下面五個字符將被轉義:
<
會轉換爲<
>
會轉換爲>
'
(單引號)轉換爲'
"
(雙引號)會轉換爲"
&
會轉換爲&
強烈建議:將第二種功能作爲默認打開的設置,不要關閉它!
可是,凡事都有正反兩面。有時,模板變量含有一些你打算渲染成原始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: <b> 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 & that{% endblock %} {% block content %}{{ greeting }}{% endblock %}
因爲自動轉義標籤在base模板中關閉,它也會在child模板中關閉,致使當greeting變量含有<b>Hello!</b>
字符串時,會渲染HTML。
<h1>This & that</h1> <b>Hello!</b>
過濾器的字符串參數:
以前咱們展現過,過濾器的參數能夠是字符串:
{{ data|default:"This is a string literal." }}
要注意,全部這種字符串參數在插入模板時都不會進行任何自動轉義。緣由是,模板的做者能夠控制字符串字面值的內容,因此能夠確保在模板編寫時文本通過正確轉義。白話講,就是,你個程序員對本身傳遞的參數內心要有數!
也便是說你應該這樣編寫:
{{ data|default:"3 < 2" }}
而不是:
{{ data|default:"3 < 2" }} {# 錯誤的作法#}
這部份內容,若是你掌握的極大提升你的模版語言能力。
大多數對象上的方法調用一樣可用於模板中。這意味着模板可以訪問到的不只僅是對象的屬性(好比字段名稱)和視圖中傳入的變量,還能夠執行對象的方法。 例如,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操做。
對於以下的模型:
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
,相似多對多。
某些應用提供了自定義的標籤和過濾器。想要在模板中使用它們,首先要確保該應用已經在INSTALLED_APPS
中(好比在下面的例子中,咱們添加了'django.contrib.humanize'),以後在模板中使用load標籤:
{% load humanize %} {{ 45000|intcomma }}
上面的例子中, load標籤加載了humanize
app的標籤庫,以後咱們可使用它的intcomma過濾器。
若是你開啓了django.contrib.admindocs
,能夠查詢admin站點中的文檔,查看你安裝的自定義庫列表。
load標籤能夠同時接受多個庫名稱,由空格分隔。 例如:
{% load humanize i18n %}
自定義庫和模板繼承:
當你加載一個自定義標籤或過濾器庫時,標籤或過濾器只在當前模板中有效--並非帶有模板繼承關係的任何父模板或者子模版中都有效。白話說就是,你在父模板中可能加載了自定義標籤,然並卵,你在子模版中還要再加載一次!
例如,若是一個模板foo.html
帶有{% load humanize %}
,子模版(例如,帶有{% extends "foo.html" %}
)中不能訪問humanize模板標籤和過濾器。 子模版須要再添加本身的{% load humanize %}
。
這個特性是出於保持可維護性和邏輯性的目的。