Django Templates 模板語言基礎入門 | Django 教學
當 View 處理完邏輯、準備好資料後,下一步就是把這些資料呈現在使用者面前。Django Templates(模板系統)正是負責這個「呈現層」的核心元件。Django 內建了一套稱為 Django Template Language(DTL) 的模板語言,讓你可以在 HTML 中嵌入變數、條件判斷和迴圈,將動態資料渲染成最終的網頁。本篇將帶你從零認識 DTL 的三種核心語法:變數、模板標籤 和 過濾器,並透過完整範例實戰演練。
Django Template Language(DTL)簡介
Django Template Language(DTL) 是 Django 內建的模板語言,其核心設計哲學是「邏輯與呈現分離(Separation of Logic and Presentation)」。DTL 刻意限制模板中可執行的邏輯,強制將商業邏輯留在 Python 層(View 和 Model),模板只負責資料的呈現。
這樣的設計帶來三個好處:
- 設計師友善:模板語法簡潔直覺,不熟悉 Python 的前端設計師也能輕鬆使用
- 安全性:限制邏輯執行,降低在模板中注入惡意程式碼的風險
- 維護性:職責分明,前端與後端各司其職,程式碼更容易維護
DTL 提供三種核心語法,所有模板操作都圍繞這三種語法展開:
<!-- 1. 變數(Variables):使用 {{ }} 輸出值 -->
{{ variable }}
<!-- 2. 模板標籤(Template Tags):使用 {% %} 執行邏輯 -->
{% if condition %}...{% endif %}
<!-- 3. 註解(Comments):使用 {# #} 撰寫單行註解 -->
{# 這段文字不會出現在 HTML 中 #}
模板目錄設定
在開始撰寫模板之前,需要先告訴 Django 去哪裡找到模板檔案。開啟專案的 settings.py,找到 TEMPLATES 設定:
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# DIRS:專案層級的模板目錄,Django 會優先在此搜尋
'DIRS': [BASE_DIR / 'templates'],
# APP_DIRS:設為 True 時,Django 會自動搜尋每個 App 的 templates/ 資料夾
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
DIRS 與 APP_DIRS 的差異
DIRS:指定專案層級的模板目錄清單,通常用來放置全站共用的模板(例如base.html)APP_DIRS:設為True時,Django 會自動到每個已安裝 App 的templates/子目錄中搜尋模板
建議的目錄結構如下:
myproject/
├── templates/ # DIRS 指定的專案層級模板
│ └── base.html
├── articles/
│ └── templates/ # APP_DIRS 自動搜尋的 App 模板
│ └── articles/
│ ├── list.html
│ └── detail.html
└── manage.py
注意 App 模板中多了一層與 App 同名的子資料夾(articles/templates/articles/),這是 Django 的命名空間慣例(Namespace Convention),用來避免不同 App 之間的模板名稱衝突。
變數輸出 {{ variable }}
使用雙大括號 {{ }} 可以在模板中輸出變數的值。Django 會自動對輸出進行 HTML 轉義(Escape),防止 XSS(跨站腳本攻擊,Cross-Site Scripting)。
<!-- 輸出簡單變數 -->
<h1>{{ title }}</h1>
<p>作者:{{ author }}</p>
點語法(Dot Notation)存取屬性
DTL 使用點語法 . 來存取物件的屬性、方法、字典的鍵值,以及串列的索引:
<!-- 存取物件屬性 -->
<p>{{ article.title }}</p>
<!-- 存取字典的值 -->
<p>{{ config.site_name }}</p>
<!-- 呼叫無參數方法(注意:不加括號) -->
<p>{{ user.get_full_name }}</p>
<!-- 存取串列索引 -->
<p>第一筆:{{ articles.0.title }}</p>
Django 遇到 . 時會按照以下順序嘗試解析:字典查找 → 屬性查找 → 方法呼叫 → 串列索引。如果變數不存在,預設會輸出空字串(可在設定中調整)。
模板標籤 {% tag %}
模板標籤(Template Tags)用來在模板中執行邏輯控制,例如條件判斷、迴圈和變數指派等。
{% if %} 條件判斷
{% if user.is_authenticated %}
<p>歡迎回來,{{ user.username }}!</p>
{% elif user.is_anonymous %}
<p>訪客您好,請先登入。</p>
{% else %}
<p>請先完成帳號設定。</p>
{% endif %}
{% if %} 支援多種運算子:and、or、not、==、!=、<、>、<=、>=、in、not in、is、is not。
{% if articles|length > 0 and user.is_staff %}
<a href="{% url 'articles:create' %}">新增文章</a>
{% endif %}
{% for %} 迴圈
{% for %} 用來遍歷可迭代物件(Iterable),例如 QuerySet 或 list:
<ul>
{% for article in articles %}
<li>{{ article.title }}</li>
{% empty %}
<li>目前沒有任何文章。</li>
{% endfor %}
</ul>
{% empty %} 是 {% for %} 的可選子句,當迭代物件為空時會執行此區塊。
在 {% for %} 迴圈內部,Django 提供了一個特殊的 forloop 變數,包含多個實用屬性:
| 變數 | 說明 |
|---|---|
forloop.counter | 從 1 開始的迭代計數 |
forloop.counter0 | 從 0 開始的迭代計數 |
forloop.first | 是否為第一次迭代(布林值) |
forloop.last | 是否為最後一次迭代(布林值) |
forloop.revcounter | 從末尾開始的反向計數(到 1 結束) |
forloop.parentloop | 巢狀迴圈中存取外層迴圈的 forloop |
<ol>
{% for tag in tags %}
<li class="{% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %}">
{{ forloop.counter }}. {{ tag.name }}
</li>
{% endfor %}
</ol>
{% comment %} 區塊註解
當需要註解多行內容時,使用 {% comment %} 區塊標籤:
{% comment "暫時隱藏側欄" %}
<aside>
<h3>側欄內容</h3>
<p>這段內容暫時不顯示</p>
</aside>
{% endcomment %}
{% comment %} 中的字串參數是可選的備註說明,方便團隊成員理解註解原因。
{% with %} 變數別名
{% with %} 用來建立臨時變數(Temporary Variable),特別適合需要重複使用某個較複雜的表達式時,避免重複計算:
{% with total=articles|length category_name=category.name %}
<h2>{{ category_name }}(共 {{ total }} 篇)</h2>
{% if total > 10 %}
<p>{{ category_name }} 分類文章數量豐富!</p>
{% endif %}
{% endwith %}
在 {% with %} 和 {% endwith %} 之間定義的變數只在此範圍內有效。
{% url %} URL 反查
{% url %} 根據 URL 名稱反向解析出完整的 URL 路徑,避免在模板中寫死 URL:
<!-- 基本用法 -->
<a href="{% url 'home' %}">首頁</a>
<!-- 帶命名空間(Namespace) -->
<a href="{% url 'articles:detail' pk=article.pk %}">{{ article.title }}</a>
<!-- 帶多個參數 -->
<a href="{% url 'articles:archive' year=2026 month=6 %}">2026 年 6 月</a>
使用 {% url %} 的好處是,當 urls.py 中的 URL 路徑修改時,模板中的連結會自動更新,不需要逐一修改。
過濾器 {{ value|filter }}
過濾器(Filters)用來在輸出變數之前對其進行轉換處理,使用管道符號 | 連接。過濾器可以鏈式串接(Chaining),從左到右依序執行。
日期與數值
<!-- 格式化日期 -->
<time>{{ article.created_at|date:"Y 年 m 月 d 日" }}</time>
<!-- 計算長度 -->
<p>共 {{ articles|length }} 篇文章</p>
<!-- 預設值:變數為 falsy 時顯示預設值 -->
<p>{{ nickname|default:"匿名使用者" }}</p>
<!-- 截斷文字到指定字數 -->
<p>{{ article.content|truncatewords:30 }}</p>
<!-- 數值格式化:保留小數位數 -->
<p>評分:{{ score|floatformat:1 }}</p>
<!-- 加法運算 -->
<p>下一頁:{{ page|add:1 }}</p>
文字轉換
<!-- 轉小寫 -->
<p>{{ title|lower }}</p>
<!-- 轉大寫 -->
<p>{{ code|upper }}</p>
<!-- 每個單字首字母大寫 -->
<p>{{ name|title }}</p>
<!-- 第一個字母大寫 -->
<p>{{ sentence|capfirst }}</p>
HTML 處理
<!-- 將換行符號轉為 <p> 和 <br> 標籤 -->
{{ content|linebreaks }}
<!-- 標記為安全 HTML,不進行轉義(謹慎使用!僅用於信任的內容) -->
{{ rich_content|safe }}
<!-- 強制 HTML 轉義 -->
{{ user_input|escape }}
鏈式過濾器
過濾器可以串接使用,從左到右依序處理:
<!-- 先移除 HTML 標籤,再截斷到 20 個字,最後首字母大寫 -->
{{ article.content|striptags|truncatewords:20|capfirst }}
註解 {# 單行註解 #}
單行註解使用 {# #} 包裹,註解內容不會出現在最終輸出的 HTML 中:
{# 這是一段單行註解,瀏覽器看不到這行 #}
<h1>{{ title }}</h1>
{# TODO: 之後要加上分頁功能 #}
需要注意的是,{# #} 不支援跨行註解。如果需要註解多行,請使用前面介紹的 {% comment %} 標籤。
Context:從 View 傳遞資料到模板
模板本身不產生資料,所有資料都必須由 View 透過 Context(上下文) 傳入。Context 本質上就是一個 Python 字典(dict),字典的 key 對應模板中的變數名稱。
# views.py
from django.shortcuts import render
from .models import Article
def article_list(request):
articles = Article.objects.all().order_by('-created_at')
context = {
'articles': articles, # 模板中用 {{ articles }}
'page_title': '最新文章', # 模板中用 {{ page_title }}
'total_count': articles.count(), # 模板中用 {{ total_count }}
}
return render(request, 'articles/list.html', context)
render() 函數接收三個主要參數:
request:當前的 HTTP 請求物件- 模板路徑:模板檔案的相對路徑(相對於模板目錄)
context:傳遞給模板的資料字典
完整範例:文章列表頁面模板
綜合前面學到的所有語法,以下是一個實際的文章列表頁面模板:
# views.py
from django.shortcuts import render
from .models import Article, Category
def article_list(request):
category_name = request.GET.get('category', '')
articles = Article.objects.filter(is_published=True).order_by('-created_at')
if category_name:
articles = articles.filter(category__name=category_name)
context = {
'articles': articles,
'category_name': category_name,
'page_title': '文章列表',
}
return render(request, 'articles/list.html', context)
{# templates/articles/list.html #}
<h2>{{ page_title }}</h2>
{% if category_name %}
<p>目前篩選分類:<strong>{{ category_name }}</strong></p>
{% endif %}
{% with total=articles|length %}
<p>共 {{ total }} 篇文章</p>
{% endwith %}
<div class="article-list">
{% for article in articles %}
<article class="{% if forloop.first %}featured{% endif %}">
<h3>
<a href="{% url 'articles:detail' pk=article.pk %}">
{{ article.title }}
</a>
</h3>
<div class="meta">
<span>{{ article.author.get_full_name|default:"匿名" }}</span>
<time>{{ article.created_at|date:"Y/m/d" }}</time>
{# 顯示迭代序號 #}
<span>第 {{ forloop.counter }} 篇</span>
</div>
<p>{{ article.content|striptags|truncatewords:30 }}</p>
{% if article.tags.all %}
<div class="tags">
{% for tag in article.tags.all %}
<span class="tag">{{ tag.name|lower }}</span>
{% if not forloop.last %} · {% endif %}
{% endfor %}
</div>
{% endif %}
</article>
{% empty %}
<div class="empty-state">
<p>目前沒有文章。</p>
{% if category_name %}
<p>試試 <a href="{% url 'articles:list' %}">查看全部文章</a>?</p>
{% endif %}
</div>
{% endfor %}
</div>
{% comment "分頁功能將在後續教學中實作" %}
<nav class="pagination">
...
</nav>
{% endcomment %}
這個範例綜合運用了:
- 變數輸出
{{ }}:顯示文章標題、作者、日期等資料 - 條件判斷
{% if %}:根據篩選條件顯示不同內容 - 迴圈
{% for %}與{% empty %}:遍歷文章列表,空列表時顯示提示 - forloop 變數:
forloop.first、forloop.counter、forloop.last - 過濾器鏈:
striptags|truncatewords:30處理摘要 {% with %}:建立臨時變數避免重複計算{% url %}:反向解析 URL{% comment %}:區塊註解標記未完成的功能{# #}:單行註解
總結
本篇介紹了 Django Template Language(DTL)的基礎語法,這是 Django MVC 架構(在 Django 中稱為 MTV:Model-Template-View)中負責呈現層的核心技術。我們學到了:
- 模板設定:透過
TEMPLATES中的DIRS和APP_DIRS告訴 Django 去哪裡找模板 - 變數輸出
{{ }}:使用點語法存取物件屬性、字典值和串列索引 - 模板標籤
{% %}:if條件判斷、for迴圈、with變數別名、url反查和comment區塊註解 - 過濾器
{{ |filter }}:date、length、default、truncatewords、safe等轉換處理 - Context:從 View 透過字典傳遞資料到模板
DTL 的設計哲學是讓模板保持簡潔,只負責呈現,商業邏輯留在 View 和 Model 中。掌握這些基礎語法後,下一篇我們將學習 模板繼承(Template Inheritance),透過 {% extends %} 和 {% block %} 實現 DRY 原則,打造可重用的頁面骨架。