Django Templates 模板語言基礎入門 | Django 教學

2026/06/04 2026/05/26
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 %} 支援多種運算子:andornot==!=<><=>=innot inisis 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() 函數接收三個主要參數:

  1. request:當前的 HTTP 請求物件
  2. 模板路徑:模板檔案的相對路徑(相對於模板目錄)
  3. 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.firstforloop.counterforloop.last
  • 過濾器鏈striptags|truncatewords:30 處理摘要
  • {% with %}:建立臨時變數避免重複計算
  • {% url %}:反向解析 URL
  • {% comment %}:區塊註解標記未完成的功能
  • {# #}:單行註解

總結

本篇介紹了 Django Template Language(DTL)的基礎語法,這是 Django MVC 架構(在 Django 中稱為 MTV:Model-Template-View)中負責呈現層的核心技術。我們學到了:

  1. 模板設定:透過 TEMPLATES 中的 DIRSAPP_DIRS 告訴 Django 去哪裡找模板
  2. 變數輸出 {{ }}:使用點語法存取物件屬性、字典值和串列索引
  3. 模板標籤 {% %}if 條件判斷、for 迴圈、with 變數別名、url 反查和 comment 區塊註解
  4. 過濾器 {{ |filter }}datelengthdefaulttruncatewordssafe 等轉換處理
  5. Context:從 View 透過字典傳遞資料到模板

DTL 的設計哲學是讓模板保持簡潔,只負責呈現,商業邏輯留在 View 和 Model 中。掌握這些基礎語法後,下一篇我們將學習 模板繼承(Template Inheritance),透過 {% extends %}{% block %} 實現 DRY 原則,打造可重用的頁面骨架。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。