樣板進階¶
開始之前¶
任務目標
在這個章節中,我們會完成:
- 了解 Template 繼承與 Include
- 建立專案級別的 Templates 資料夾
- 實作多重繼承架構
Template 繼承¶
Template 繼承(Template Inheritance)是 Django 樣板系統最強大的功能之一,讓你可以建立一個基礎樣板,然後在其他樣板中繼承它。
為什麼需要繼承?¶
假設你的網站有多個頁面,每個頁面都有相同的導覽列、頁尾,只有中間的內容不同。如果每個頁面都重複寫一次相同的 HTML,會有以下問題:
- 程式碼重複,難以維護
- 修改導覽列時需要更新所有頁面
- 容易出錯或遺漏
使用 Template 繼承,你可以:
- 將共用的部分寫在基礎樣板中
- 在子樣板中只需要定義不同的部分
- 修改基礎樣板時,所有繼承它的頁面都會自動更新
基本概念¶
Template 繼承使用兩個主要的標籤:
{% block %}- 定義可以被子樣板覆寫的區塊{% extends %}- 繼承父樣板
建立專案級別的 Templates¶
在開始實作之前,我們先建立專案級別的 templates 資料夾。
為什麼需要專案級別的 Templates?¶
- App 級別的 templates:放在
app/templates/中,只給該 App 使用 - 專案級別的 templates:放在專案根目錄的
templates/中,可以被所有 App 共用
專案級別的 templates 適合放置:
- 網站的基礎樣板(base.html)
- 共用的元件(導覽列、頁尾等)
- 錯誤頁面(404.html、500.html)
建立 templates 資料夾¶
在專案根目錄(與 manage.py 同層)建立 templates 資料夾:
目錄結構:
django-playground/
├── blog/
├── core/
├── practices/
├── templates/ # 專案級別的 templates
├── manage.py
└── db.sqlite3
設定 Django¶
修改 core/settings.py,告訴 Django 去哪裡找專案級別的 templates:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
BASE_DIR / "templates", # (1)!
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
- 加入專案級別的 templates 路徑
DIRS vs APP_DIRS
DIRS:指定專案級別的 templates 資料夾APP_DIRS:設為True時,Django 會在每個 App 的templates/資料夾中尋找樣板
Django 的搜尋順序:
- 先在
DIRS指定的資料夾中尋找 - 再到各個 App 的
templates/資料夾中尋找
實作基礎樣板¶
建立專案級別的 base.html¶
在 templates/ 資料夾中建立 base.html:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Django Playground" />
<meta name="keywords" content="Django, Playground" />
<title>
{% block title %}
Django 大冒險
{% endblock title %}
</title>
{% block extra_head %}
{% endblock extra_head %}
</head>
<body>
<header>
<nav>
<h1>
Django 大冒險
</h1>
</nav>
</header>
<main>
{% block content %}
{% endblock content %}
</main>
<footer>
<p>
Django Playground
</p>
</footer>
{% block extra_scripts %}
{% endblock extra_scripts %}
</body>
</html>
這個基礎樣板定義了四個 block:
| Block | 用途 |
|---|---|
title |
頁面標題 |
extra_head |
額外的 CSS 或 meta 標籤 |
content |
主要內容區域 |
extra_scripts |
額外的 JavaScript |
在 App 中使用基礎樣板¶
建立 blog/templates/blog/article_list.html 檔案:
{% extends "base.html" %}
{% block title %}
文章列表 - Django 大冒險
{% endblock title %}
{% block content %}
<h2>
文章列表
</h2>
<ul>
{% for article in articles %}
<li>
{{ article.title }} - {{ article.author.name }}
</li>
{% empty %}
<li>
目前沒有文章
</li>
{% endfor %}
</ul>
{% endblock content %}
建立對應的 view:
from django.shortcuts import render
from blog.models import Article
def article_list(request):
articles = Article.objects.all()
return render(request, "blog/article_list.html", {"articles": articles})
設定 URL:
from django.urls import path
from blog import views
urlpatterns = [
path("articles/", views.article_list, name="article_list"),
]
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("practices/", include("practices.urls")),
path("blog/", include("blog.urls")),
]
Block 的預設內容¶
Block 可以有預設內容,子樣板可以選擇是否覆寫:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Django Playground" />
<meta name="keywords" content="Django, Playground" />
<title>
{% block title %}
Django 大冒險
{% endblock title %}
</title>
{% block extra_head %}
{% endblock extra_head %}
</head>
<body>
<header>
<nav>
<h1>
Django 大冒險
</h1>
</nav>
</header>
<main>
{% block content %}
<p>
歡迎來到 Django 大冒險!
</p>
{% endblock content %}
</main>
<footer>
<p>
Django Playground
</p>
</footer>
{% block extra_scripts %}
{% endblock extra_scripts %}
</body>
</html>
如果子樣板沒有覆寫 content block,就會顯示預設的「歡迎來到 Django 大冒險!」。
使用 block.super¶
有時候你想要保留父樣板的內容,並在其基礎上添加新內容,可以使用 {{ block.super }}:
{% extends "base.html" %}
{% block title %}
{{ article.title }} - {{ block.super }}
{% endblock title %}
{% block content %}
<article>
<h2>
{{ article.title }}
</h2>
<p>
作者:{{ article.author.name }}
</p>
<p>
發布時間:{{ article.created_at }}
</p>
<div>
{{ article.content }}
</div>
</article>
{% endblock content %}
建立對應的 view:
from django.shortcuts import render
from blog.models import Article
def article_list(request):
articles = Article.objects.all()
return render(request, "blog/article_list.html", {"articles": articles})
def article_detail(request, article_id):
article = Article.objects.get(id=article_id)
return render(request, "blog/article_detail.html", {"article": article})
設定 URL:
from django.urls import path
from blog import views
urlpatterns = [
path("articles/", views.article_list, name="article_list"),
path("articles/<int:article_id>/", views.article_detail, name="article_detail"),
]
接著訪問 http://localhost:8000/blog/articles/1/,會看到網站 title 是:文章標題 - Django 大冒險
Warning
上方的網址範例可能會沒有編號 2 的文章,如果出現 404 的頁面,請修正網址的 ID 部分,例如 http://localhost:8000/blog/articles/2/ 之類的
Template Include¶
除了繼承,Django 還提供 {% include %} 來引入其他樣板片段。
Include vs Extends¶
| 功能 | Extends | Include |
|---|---|---|
| 用途 | 繼承父樣板的結構 | 引入樣板片段 |
| 數量限制 | 一個樣板只能 extends 一次 | 可以 include 多次 |
| Block | 可以覆寫 block | 無法覆寫 block |
| 使用場景 | 頁面整體結構 | 可重複使用的元件 |
Include 範例¶
建立一個共用的文章卡片元件:
<div class="article-card">
<h3>
{{ article.title }}
</h3>
<p>
{{ article.author.name }} | {{ article.created_at }}
</p>
<p>
{{ article.content|truncatewords:30 }}
</p>
<a href="{% url 'article_detail' article.id %}">閱讀更多</a>
</div>
在 Template 中產生 URL
{% url %} 是 Django 內建的 template tag,用來根據 URL name 產生對應的網址。
語法:{% url 'url_name' arg1 arg2 ... %}
使用 {% url %} 的好處是:
- 不需要硬編碼網址,避免修改 URL 時要到處更新
- 自動處理參數,例如
{% url 'article_detail' article.id %}會生成/blog/articles/1/ - 如果 URL pattern 改變,只要 name 不變,所有使用
{% url %}的地方都會自動更新
如果想要知道有哪些內建的 template tag 可以使用可以參考:https://docs.djangoproject.com/en/5.2/ref/templates/builtins/
在其他樣板中使用:
{% extends "base.html" %}
{% block title %}
文章列表 - Django 大冒險
{% endblock title %}
{% block content %}
<h2>
文章列表
</h2>
<div class="article-grid">
{% for article in articles %}
{% include "blog/components/article_card.html" %}
{% empty %}
<p>
目前沒有文章
</p>
{% endfor %}
</div>
{% endblock content %}
Include 的變數傳遞
Include 的樣板可以存取父樣板的所有變數。在上面的範例中,article_card.html 可以使用 article 變數,因為它在 for 迴圈中。
你也可以使用 with 明確傳遞變數:
多重繼承架構¶
在大型專案中,我們通常會建立多層的樣板繼承結構。
架構說明¶
graph TD
A[templates/base.html<br/>專案基礎樣板] --> B[blog/templates/blog/base.html<br/>App 基礎樣板]
B --> C[blog/templates/blog/article_list.html<br/>文章列表頁]
B --> D[blog/templates/blog/article_detail.html<br/>文章詳細頁]
- 專案級 base.html:定義整個網站的共用結構(導覽列、頁尾)
- App 級 base.html:繼承專案級 base.html,定義該 App 的共用結構
- 頁面樣板:繼承 App 級 base.html,只需要定義頁面特有的內容
實作 App 級基礎樣板¶
建立 blog/templates/blog/base.html:
{% extends "base.html" %}
{% block content %}
<div class="blog-container">
<aside class="sidebar">
<h3>
文章管理
</h3>
<ul>
<li>
<a href="{% url 'article_list' %}">文章列表</a>
</li>
</ul>
</aside>
<div class="main-content">
{% block blog_content %}
{% endblock blog_content %}
</div>
</div>
{% endblock content %}
這個 App 級樣板:
- 繼承專案級的
base.html - 覆寫
contentblock,加入側邊欄 - 定義新的
blog_contentblock 給子樣板使用
使用 App 級基礎樣板¶
修改文章列表頁,改為繼承 App 級樣板:
{% extends "blog/base.html" %}
{% block title %}
文章列表 - Django 大冒險
{% endblock title %}
{% block blog_content %}
<h2>
文章列表
</h2>
<div class="article-grid">
{% for article in articles %}
{% include "blog/components/article_card.html" %}
{% empty %}
<p>
目前沒有文章
</p>
{% endfor %}
</div>
{% endblock blog_content %}
現在這個頁面會:
- 繼承
blog/base.html(App 級) - App 級樣板會繼承
base.html(專案級) - 最終頁面會包含:導覽列、側邊欄、文章列表、頁尾
繼承鏈
Django 會按照繼承鏈組合所有樣板:
每一層都可以覆寫或擴充上一層的內容。
常見問題¶
為什麼我的樣板找不到?¶
檢查以下幾點:
- 確認
TEMPLATES的DIRS設定正確 - 檢查樣板檔案路徑是否正確
- 確認 App 已經加入
INSTALLED_APPS - 重新啟動開發伺服器
Block 名稱重複怎麼辦?¶
如果多個樣板定義了相同名稱的 block,最後繼承的會生效。建議:
- 專案級樣板使用通用名稱(
content、title) - App 級樣板使用帶前綴的名稱(
blog_content、blog_sidebar)
可以繼承多個樣板嗎?¶
不行,一個樣板只能 {% extends %} 一個父樣板。但可以透過多層繼承來達成類似效果:
Include 會影響效能嗎?¶
Include 會增加額外的檔案讀取,但 Django 有樣板快取機制。在正式環境中影響很小,在開發環境中可能會稍微慢一點。
只要不過度使用(例如在迴圈中 include 大量樣板),效能影響可以忽略。
任務結束¶
完成!
恭喜你完成了這個章節!現在你已經:
- 了解 Template 繼承與 Include
- 建立專案級別的 Templates 資料夾
- 實作多重繼承架構
補充說明
Template 繼承是 Django 樣板系統的核心功能,掌握它可以:
- 大幅減少重複的程式碼
- 讓樣板結構更清晰易維護
- 建立一致的使用者介面
在實際專案中,建議:
- 規劃好樣板繼承結構
- 合理劃分 block,不要太細也不要太粗
- 使用有意義的 block 名稱
- 將可重複使用的元件抽出成獨立檔案