1. 程式人生 > Django入門教學 >13 Django 模板語言 DTL

13 Django 模板語言 DTL

接下來,我們會詳細描述 Django 內建模板語言的語法 (DTL),和 Mako、Jinja2 一樣,需要掌握其註釋、變數、過濾器、標籤、控制語句等等的寫法,並用實際的案例進行說明。

1. DTL 基礎用法

1.1 變數

DTL 中變數的寫法為 {{ variable }}, 這和 Jinja2 非常類似。模版引擎碰到模板變數時,會從上下文 context 中獲取這個變數的值,然後用該值替換掉它本身。變數的命名包括任何字母數字以及下劃線("_")的組合,其中點(".")號在 DTL 中是有特殊含義的,因此要注意:變數名稱中不能有空格或標點符號

<p>{{ content }}</
p
>
<div>{{ spyinx.age }}</div>

注意:spyinx.age 表示的是 spyinx 的 age 屬性值。我們可以基於 manager.py 的 shell 命令做如下測試:

(django-manual) [root@server first_django_app]# cat templates/test1.html 
<p>{{ content }}</p>
<div>{{ spyinx.age }}</div>

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> tp = get_template('test1.html')
>>> tp.render(context={'content': '正文我文字', 'spyinx': {'age': 29, 'sex': 'male'}})
'<p>正文我文字</p>\n<div>29</div>\n'
>>> text = tp.render(context={'content': '正文我文字', 'spyinx': {'age': 29, 'sex': 'male'}})
>>> print(text)
<p>正文我文字</p>
<div>29</div>

1.2 標籤

DTL 中標籤的寫法為: {% 標籤 %},常用的標籤有 for 迴圈標籤,條件判斷標籤 if/elif/else。部分標籤在使用時,需要匹配對應的結束標籤。Django 中常用的內建標籤如下表格所示:

標籤 描述
{% for %} for 迴圈,遍歷變數中的內容
{% if %} if 判斷
{% csrf_token %} 生成 csrf_token 的標籤
{% static %} 讀取靜態資源內容
{% with %} 多用於給一個複雜的變數起別名
{% url %} 反向生成相應的 URL 地址
{% include 模板名稱 %} 載入指定的模板並以標籤內的引數渲染
{% extends 模板名稱 %} 模板繼承,用於標記當前模板繼承自哪個父模板
{% block %} 用於定義一個模板塊

1.2.1 for 標籤的用法

{# 遍歷列表 #}
<ul>
{% for person in persons %}
<li>{{ person }}</li>
{% endfor %}
</ul>

{# 遍歷字典 #}
<ul>
{% for key, value in data.items %}
<li>{{ key }}:{{ value }}</li>
{% endfor %}
</ul>

在 for 迴圈標籤中,還提供了一些變數,供我們使用:

變數 描述
forloop.counter 當前迴圈位置,從1開始
forloop.counter0 當前迴圈位置,從0開始
forloop.revcounter 反向迴圈位置,從n開始,到1結束
forloop.revcounter0 反向迴圈位置,從n-1開始,到0結束
forloop.first 如果是當前迴圈的第一位,返回True
forloop.last 如果是當前迴圈的最後一位,返回True
forloop.parentloop 在巢狀for迴圈中,獲取上層for迴圈的forloop

實驗

(django-manual) [root@server first_django_app]# cat templates/test_for.html 
遍歷列表:
<ul>
{% spaceless %}
{% for person in persons %}
{% if forloop.first %}
<li>第一次:{{ forloop.counter }}:{{ forloop.counter0 }}:{{ person }}:{{ forloop.revcounter }}:{{ forloop.revcounter }}</li>
{% elif forloop.last %}
<li>最後一次:{{ forloop.counter }}:{{ forloop.counter0 }}:{{ person }}:{{ forloop.revcounter }}:{{ forloop.revcounter }}</li>
{% else %}
</li>{{ forloop.counter }}:{{ forloop.counter0 }}:{{ person }}:{{ forloop.revcounter }}:{{ forloop.revcounter }}</li>
{% endif %}
{% endfor %}
{% endspaceless %}
</ul>

{% for name in name_list %} 
{{ name }}
{% empty %} 
<p>name_list變數為空</p>
{% endfor %} 

倒序遍歷列:
{% spaceless %}
{% for person in persons reversed %}
<p>{{ person }}:{{ forloop.revcounter }}</p>
{% endfor %}
{% endspaceless %}

遍歷字典:
{% spaceless %}
{% for key, value in data.items %}
<p>{{ key }}:{{ value }}</p>
{% endfor %}
{% endspaceless %}


(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> tp = get_template('test_for.html')
>>> content = tp.render(context={'persons':['張三', '李四', '王二麻子'], 'name_list': [], 'data': {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}})
>>> print(content)
遍歷列表:
<ul>
<li>第一次:1:0:張三:3:3</li></li>2:1:李四:2:2</li><li>最後一次:3:2:王二麻子:1:1</li>
</ul>

 
<p>name_list變數為空</p>
 

倒序遍歷列:
<p>王二麻子:3</p><p>李四:2</p><p>張三:1</p>

遍歷字典:
<p>key1:value1</p><p>key2:value2</p><p>key3:value3</p>

1.2.2 if 標籤

支援巢狀,判斷的條件符號與變數之間必須使用空格隔開,示例如下。

(django-manual) [root@server first_django_app]# cat templates/test_if.html
{% if spyinx.sex == 'male' %}
<label>他是個男孩子</label>
{% else %}
<label>她是個女孩子</label>
{% endif %}
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> tp = get_template('test_if.html')
>>> tp.render(context={'spyinx': {'age':29, 'sex': 'male'}})
'\n<label>他是個男孩子</label>\n\n'
>>> tp.render(context={'spyinx': {'age':29, 'sex': 'male'}})

1.2.3 csrf_token 標籤

這個標籤會生成一個隱藏的 input 標籤,其值為一串隨機的字串。這個標籤通常用在頁面上的 form 標籤中。在渲染模組時,使用 RequestContext,由它處理 csrf_token 這個標籤。下面來做個簡單的測試:

# 模板檔案
[root@server first_django_app]# cat templates/test_csrf.html 
<html>
<head></head>
<body>
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
<div><span>賬號:</span><input type="text" style="margin-bottom: 10px" placeholder="請輸入登入手機號/郵箱" /></div>
<div><span>密碼:</span><input type="password" style="margin-bottom: 10px" placeholder="請輸入密碼" /></div>
<div><label style="font-size: 10px; color: grey"><input type="checkbox" checked="checked"/>7天自動登入</label></div>
<div style="margin-top: 10px"><input type="submit"/></div>
</form>
</body>
</html>

# 定義檢視:hello_app/views.py
[root@server first_django_app]# cat hello_app/views.py 
from django.shortcuts import render

# Create your views here.
def test_csrf_view(request, *args, **kwargs):
    return render(request, 'test_csrf.html', context={})

# 配置URLconf:hello_app/urls.py
[root@server first_django_app]# cat hello_app/urls.py
from django.urls import path

urlpatterns = [
    path('test-csrf/', views.test_csrf_view),
]

# 最後啟用虛擬環境並啟動django工程
[root@server first_django_app] pyenv activate django-manual
(django-manual) [root@server first_django_app]# python manage.py runserver 0:8881
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

March 27, 2020 - 04:10:05
Django version 2.2.11, using settings 'first_django_app.settings'
Starting development server at http://0:8881/
Quit the server with CONTROL-C.

現在通過外部請求這個URL,效果圖如下。通過右鍵的檢查功能,可以看到 {% csrf_token %} 被替換成了隱藏的 input 標籤,value 屬性是一個隨機的長字串:

圖片描述

csrf_token標籤

1.2.4 with 標籤

對某個變數重新命名並使用:

(django-manual) [root@server first_django_app]# cat templates/test_with.html 
{% spaceless %}
{% with age1=spyinx.age %}
<p>{{ age1 }}</p>
{% endwith %}
{% endspaceless %}

{% spaceless %}
{% with spyinx.age as age2 %}
<div>{{ age2 }} </div>
{% endwith %}
{% endspaceless %}

(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.template.loader import get_template
>>> tp = get_template('test_with.html')
>>> content = tp.render(context={'spyinx': {'age': 29}})
>>> print(content)
<p>29</p>

<div>29 </div>

1.2.5 spaceless 標籤

移除 HTML 標籤中的空白字元,包括空格、tab鍵、換行等。具體示例參見上面的示例;

1.2.6 cycle 標籤

迴圈提取 cycle 中的值,用法示例如下

# 假設模板如下:
{% for l in list %}
<tr class="{% cycle 'r1' 'r2' 'r3'%}">
{{l}}
</tr>
{% endfor %}

# 對於傳入的 list 引數為:['l1', 'l2', 'l3'],最後生成的結果如下:
<tr class="r1">
l1
</tr>

<tr class="r2">
l2
</tr>

<tr class="r3">
l3
</tr>

1.2.7 include 標籤

載入其他模板進來。

{% include "base/base.html" %}

除了載入模板進來外,include 標籤還可以像載入進來的模板傳遞變數。假設我們有個 base/base.html 模板檔案,其內容為:

{# base/base.html #}
Hello {{ name|default:"Unknown" }}

此時,我們引入 base.html 模板檔案時,可以給 name 傳遞變數值:

{% include "base/base.html" with name="test"  %}

1.3 註釋

在 DTL 中單行註釋使用 {# 單行註釋內容 #} 這樣的方式,對於多行註釋,我們使用 comment 標籤:

{# 單行註釋內容 #}

{% comment %}
多行註釋內容
{% endcomment %}

1.4 過濾器

DTL 中的過濾器會在下一節中詳細介紹。

2. DTL 中的模板繼承

在這裡將介紹 DTL 中的模板繼承語法,主要涉及到 block、extends 兩個標籤。Django 中的模板和 Python 中的類一樣是可以被繼承的,通過合理的模板繼承可以減少前端的工作量,提高程式碼的複用性以及開發效率。例如下面的 w3school 的線上教程網站:

圖片描述

w3school線上教程首頁

大致上,該網站有一個固定的頭部,有一些側邊欄,以及內容主體部分。現在我們使用 Django 中的模板教程來完成一個這樣的簡單例子。

首先在 template 目錄下準備網站的框架模板檔案 base.html,其內容如下:

<html>
{% load staticfiles %}
<head>
</head>

<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
<body>

<!-- 大容器 -->
<div class="container">
	<div class="header"><center>網站頭部</center></div>
	<div class="sidebar">
                {% block sidebar %} {% endblock %}
	</div>
	<div class="content">
		{% block content %} {% endblock %}
	</div>
</div>

</body>
</html>

由於模板檔案中載入了靜態資原始檔,我們除了加上靜態資原始檔外,還需要加上在 Django 的全域性配置檔案中進行相關屬性的設定:

新建 static 目錄,並在其下新建 css 目錄,然後準備樣式表 main.css:

.container {
	border-style: dotted;
	border-color: red;
	border-width: 10px;
	width: 90%;
	height: 80%;
}

.container .header {
    border-bottom-style: solid;
    border-color: black;
	border-width: 5px;
	font-size: 24px;
	height: 100px;
    line-height: 100px;
}

.container .sidebar {
	border-right-style: solid;
    border-color: black;
	border-width: 5px;
	font-size: 24px;
	width: 20%;
	float: left;
    height: 80%;
}

.container .content {
    float: left;
    width: 60%;
}

.container .content .title {
	margin-top: 20px;
    margin-left: 40%;
    width: 100%;
    font-size: 24px;
    font-weight: bold;
}

在 Django 的 settings.py 檔案中新增 STATICFILES_DIRS 屬性:

STATIC_URL = '/static/'
# 新新增的配置,方便前面的模板檔案中找到靜態資源路徑
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static") ]

可以看到,在前面的 base.html 檔案中,我們定義了頁面的基本框架,包括了網站頭部、側邊欄資料以及內容主體。其中側邊欄和內容主體部分分別定義了兩個 block 標籤,並進行了命名。接下來我們新建模板檔案 test_extends.html,該模板檔案繼承自 base.html,並給出兩個 block 標籤對應的資料:

{# 繼承base.html模板檔案 #}
{% extends "base.html" %}

{# 側邊欄 #}
{% block sidebar %}
<ul>
    {% for lesson in lessons %}
    <li><a href="{{ lesson.addr }}">{{ lesson.name }}</a></li>
    {% endfor %}
</ul>
{% endblock %}

{# 內容主體 #}
{% block content %}
<div class="title">{{ title }}</div>
{% endblock %}

準備好檢視函式,這裡我們會實現兩個檢視,使用的模板是一樣的,但是填充的資料不一樣而已:

# hello_app/views.py
from django.shortcuts import render

def test_django_view(request, *args, **kwargs):
    data = {
       'title': 'Django教程手冊',
       'lessons': [
          {'name': 'web框架', 'addr': '/web_framework'},
          {'name': 'django發展歷史', 'addr': '/django_history'},
          {'name': 'django基礎上', 'addr': '/base_one'},
          {'name': 'django基礎下', 'addr': '/base_two'},
          
       ]
    }
    return render(request, 'test_extends.html', context=data)

def test_nginx_view(request, *args, **kwargs):
    data = {
       'title': 'Nginx教程手冊',
       'lessons': [
          {'name': 'Nginx介紹', 'addr': '/web_server'},
          {'name': 'Nginx發展歷史', 'addr': '/nginx_history'},
          {'name': 'Nginx優勢', 'addr': '/nginx_advantages'},

       ]
    }
    return render(request, 'test_extends.html', context=data)

準備好 URLconf 配置:

from django.urls import path, re_path

urlpatterns = [
    path('test-django/', views.test_django_view),
    path('test-nginx/', views.test_nginx_view),
]

啟動 Django 服務頁面,然後分別請求/hello/test-django//hello/test-nginx/ 兩個地址,可以看到如下兩個效果圖,網頁的整體佈局不變,但是資料不同。幾乎所有的大型網站都是靠這樣繼承模式實現的優化前端程式碼和統一頁面的風格。

圖片描述

test-django效果圖

圖片描述

test-nginx效果圖

通過上面的簡單實驗,我們能夠理解並初步掌握 Django 中模板的繼承用法。這種基於繼承網頁的做法能使得我們開發的網站具有統一的風格,也是後面經常會用到的一種模板編寫手段。

3. 小結

本小節我們詳細介紹了 Django 中自帶的模板引擎的語法,介紹了各種 DTL 的標籤以及其用法並進行了測試。此外還通過一個簡單的實戰例子介紹了 DTL 中的模板繼承語法。這些是後面編寫 HTML 模板的基礎,必須熟練掌握。接下來會繼續介紹 DTL 中的過濾器以及如何自定義過濾器。