Django 模板語言 DTL
接下來,我們會詳細描述 Django 內(nèi)置模板語言的語法 (DTL),和 Mako、Jinja2 一樣,需要掌握其注釋、變量、過濾器、標簽、控制語句等等的寫法,并用實際的案例進行說明。
1. DTL 基礎用法
1.1 變量
DTL 中變量的寫法為 {{ variable }}
, 這和 Jinja2 非常類似。模版引擎碰到模板變量時,會從上下文 context 中獲取這個變量的值,然后用該值替換掉它本身。變量的命名包括任何字母數(shù)字以及下劃線("_")的組合,其中點(".")號在 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 循環(huán)標簽,條件判斷標簽 if/elif/else。部分標簽在使用時,需要匹配對應的結(jié)束標簽。Django 中常用的內(nèi)置標簽如下表格所示:
標簽 | 描述 |
---|---|
{% for %} | for 循環(huán),遍歷變量中的內(nèi)容 |
{% if %} | if 判斷 |
{% csrf_token %} | 生成 csrf_token 的標簽 |
{% static %} | 讀取靜態(tài)資源內(nèi)容 |
{% with %} | 多用于給一個復雜的變量起別名 |
{% url %} | 反向生成相應的 URL 地址 |
{% include 模板名稱 %} | 加載指定的模板并以標簽內(nèi)的參數(shù)渲染 |
{% 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 循環(huán)標簽中,還提供了一些變量,供我們使用:
變量 | 描述 |
---|---|
forloop.counter | 當前循環(huán)位置,從1開始 |
forloop.counter0 | 當前循環(huán)位置,從0開始 |
forloop.revcounter | 反向循環(huán)位置,從n開始,到1結(jié)束 |
forloop.revcounter0 | 反向循環(huán)位置,從n-1開始,到0結(jié)束 |
forloop.first | 如果是當前循環(huán)的第一位,返回True |
forloop.last | 如果是當前循環(huán)的最后一位,返回True |
forloop.parentloop | 在嵌套for循環(huán)中,獲取上層for循環(huán)的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),
]
# 最后激活虛擬環(huán)境并啟動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.
現(xiàn)在通過外部請求這個URL,效果圖如下。通過右鍵的檢查功能,可以看到 {% csrf_token %}
被替換成了隱藏的 input
標簽,value 屬性是一個隨機的長字符串:
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 標簽:
循環(huán)提取 cycle 中的值,用法示例如下
# 假設模板如下:
{% for l in list %}
<tr class="{% cycle 'r1' 'r2' 'r3'%}">
{{l}}
</tr>
{% endfor %}
# 對于傳入的 list 參數(shù)為:['l1', 'l2', 'l3'],最后生成的結(jié)果如下:
<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 模板文件,其內(nèi)容為:
{# base/base.html #}
Hello {{ name|default:"Unknown" }}
此時,我們引入 base.html 模板文件時,可以給 name 傳遞變量值:
{% include "base/base.html" with name="test" %}
1.3 注釋
在 DTL 中單行注釋使用 {# 單行注釋內(nèi)容 #}
這樣的方式,對于多行注釋,我們使用 comment 標簽:
{# 單行注釋內(nèi)容 #}
{% comment %}
多行注釋內(nèi)容
{% endcomment %}
1.4 過濾器
DTL 中的過濾器會在下一節(jié)中詳細介紹。
2. DTL 中的模板繼承
在這里將介紹 DTL 中的模板繼承語法,主要涉及到 block、extends 兩個標簽。Django 中的模板和 Python 中的類一樣是可以被繼承的,通過合理的模板繼承可以減少前端的工作量,提高代碼的復用性以及開發(fā)效率。例如下面的 w3school 的在線教程網(wǎng)站:
大致上,該網(wǎng)站有一個固定的頭部,有一些側(cè)邊欄,以及內(nèi)容主體部分?,F(xiàn)在我們使用 Django 中的模板教程來完成一個這樣的簡單例子。
首先在 template 目錄下準備網(wǎng)站的框架模板文件 base.html,其內(nèi)容如下:
<html>
{% load staticfiles %}
<head>
</head>
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
<body>
<!-- 大容器 -->
<div class="container">
<div class="header"><center>網(wǎng)站頭部</center></div>
<div class="sidebar">
{% block sidebar %} {% endblock %}
</div>
<div class="content">
{% block content %} {% endblock %}
</div>
</div>
</body>
</html>
由于模板文件中加載了靜態(tài)資源文件,我們除了加上靜態(tài)資源文件外,還需要加上在 Django 的全局配置文件中進行相關(guān)屬性的設置:
新建 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/'
# 新添加的配置,方便前面的模板文件中找到靜態(tài)資源路徑
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static") ]
可以看到,在前面的 base.html 文件中,我們定義了頁面的基本框架,包括了網(wǎng)站頭部、側(cè)邊欄數(shù)據(jù)以及內(nèi)容主體。其中側(cè)邊欄和內(nèi)容主體部分分別定義了兩個 block 標簽,并進行了命名。接下來我們新建模板文件 test_extends.html,該模板文件繼承自 base.html,并給出兩個 block 標簽對應的數(shù)據(jù):
{# 繼承base.html模板文件 #}
{% extends "base.html" %}
{# 側(cè)邊欄 #}
{% block sidebar %}
<ul>
{% for lesson in lessons %}
<li><a href="{{ lesson.addr }}">{{ lesson.name }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{# 內(nèi)容主體 #}
{% block content %}
<div class="title">{{ title }}</div>
{% endblock %}
準備好視圖函數(shù),這里我們會實現(xiàn)兩個視圖,使用的模板是一樣的,但是填充的數(shù)據(jù)不一樣而已:
# 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發(fā)展歷史', '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發(fā)展歷史', 'addr': '/nginx_history'},
{'name': 'Nginx優(yōu)勢', '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/
兩個地址,可以看到如下兩個效果圖,網(wǎng)頁的整體布局不變,但是數(shù)據(jù)不同。幾乎所有的大型網(wǎng)站都是靠這樣繼承模式實現(xiàn)的優(yōu)化前端代碼和統(tǒng)一頁面的風格。
通過上面的簡單實驗,我們能夠理解并初步掌握 Django 中模板的繼承用法。這種基于繼承網(wǎng)頁的做法能使得我們開發(fā)的網(wǎng)站具有統(tǒng)一的風格,也是后面經(jīng)常會用到的一種模板編寫手段。
3. 小結(jié)
本小節(jié)我們詳細介紹了 Django 中自帶的模板引擎的語法,介紹了各種 DTL 的標簽以及其用法并進行了測試。此外還通過一個簡單的實戰(zhàn)例子介紹了 DTL 中的模板繼承語法。這些是后面編寫 HTML 模板的基礎,必須熟練掌握。接下來會繼續(xù)介紹 DTL 中的過濾器以及如何自定義過濾器。