🚀 Day 8:視圖與前端渲染(View & Template)
5 天營隊第二週 (Day 8 / 總時數 6 Hours) | 將 SQLite 資料庫紀錄掏出來,套用 Bootstrap 5 打造現代化 App 網頁!
📖 為什麼學 these?——「理財咖啡館網頁擺盤與現代化包裝」關卡冒險故事線
昨天我們成功把資料庫建立好了,但純文字 HttpResponse("<h1>...</h1>") 寫起來又醜又累。今天我們要擔任 **網頁擺盤設計師**:用 render() 結合 HTML 模板、掛載 Bootstrap 5 現代化 CSS 裝飾卡片與表格、用 {% extends %} 貫徹不重複寫碼原則,最後在網頁做出動態預算超支警示標籤!
| 單元名稱 | 記帳程式關卡角色 | 為什麼這個單元不可或缺?(技術關聯) |
|---|---|---|
| 單元 1:模板引擎與變數渲染 | 🎨 擺盤設計師 (render & {{ var }}) | 學習 render(request, 'home.html', context),將 View 抓出來的資料帶入 HTML,用 {{ 變數 }} 顯示,對應 Day 1 的 f-string。 |
| 單元 2:網頁上的清單走訪 | 🛒 網頁菜單印表機 ({% for %}) | 從資料庫撈出所有帳務,使用 Django 模板語法 {% for item in records %} 在網頁上自動生成表格列,對應 Day 2 的 for 迴圈。 |
| 單元 3:引入 Bootstrap 5 美化 | ✨ 現代化 App 魔法外衣 (Bootstrap) | 引入 Bootstrap 5 CDN,把醜醜的純文字變成高質感的網頁卡片 (Card) 與響應式表格 (Table),對應 Day 5 的 ASCII 裝飾與 Emoji 排版。 |
| 單元 4:模板繼承 (Inheritance) | 🏛️ 通用建築母板 (base.html) | 建立 base.html 共用導覽列,子頁面只需用 {% extends %} 與 {% block content %},貫徹 Day 5 的 DRY (Don't Repeat Yourself) 原則。 |
| 單元 5:邏輯判斷與條件渲染 | 🚨 動態預算警報器 ({% if %}) | 在 View 計算總消費,並在 HTML 中用 {% if total > 1000 %} 自動顯示紅色「預算超支」警告標籤,對應 Day 3 的 if-else 判斷。 |
| 單元 6:介面調教與階段驗收 | 🏆 理財咖啡館網頁首頁發布 | 調整網頁主題色彩,完整展示「首頁:歷史記帳明細列表」,完成高品質網頁 UI/UX 體驗優化! |
📌 課程前導與簡介說明
- 營隊定位:從純文字
HttpResponse正式邁向高質感 HTML/CSS 前端渲染。 - 高中生心理亮點:當掛上 Bootstrap 5 CDN 之後,原本簡陋的網頁瞬間變成媲美市面商業 App 的質感網站,學生會充滿視覺震憾與成就感!
- 教學銜接點:不斷連結舊觀念(
{{ 變數 }}對應 Day 1f-string;{% for %}對應 Day 2for迴圈;{% extends %}對應 Day 5 DRY 重構)。
歡迎來到 Day 8!今天我們要讓你的網站變身成超級好看的專業 App!請閱讀每個單元的 **故事單元說明**,點擊 **延伸閱讀** 展開深層觀念與 Google 搜尋,並善用每區塊的 **AI 協作除錯提示詞** 來寫出你的第一個 HTML 模板!
💻 Day 8 專案總成果:Bootstrap 5 質感記帳網頁首頁
【教師參考解答】學生於 Day 8 結束時將完成的 View 與 HTML 模板程式碼:
1. 基礎母模板 (`templates/base.html`):
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>理財咖啡館 Web</title>
<!-- 載入 Bootstrap 5 CSS CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<!-- 共用導覽列 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow-sm mb-4">
<div class="container">
<a class="navbar-brand fw-bold" href="#">☕ 理財咖啡館 Web</a>
</div>
</nav>
<!-- 子頁面填充區塊 -->
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>
2. 首頁 View 視圖 (`expenses/views.py`):
from django.shortcuts import render
from .models import Expense
from django.db.models import Sum
def home(request):
""" 從 DB 掏出資料並計算總額,丟給 Template 渲染 """
records = Expense.objects.all().order_by('-date') # 依日期倒序排列
total_cost = records.aggregate(Sum('price'))['price__sum'] or 0
context = {
'records': records,
'total_cost': total_cost,
}
return render(request, 'expenses/home.html', context)
3. 首頁渲染模板 (`templates/expenses/home.html`):
{% extends 'base.html' %}
{% block content %}
<div class="row mb-4">
<div class="col-md-12">
<div class="card shadow-sm border-0">
<div class="card-body d-flex justify-content-between align-items-center">
<h4 class="card-title mb-0 fw-bold">💵 累計總消費金額</h4>
<h3 class="text-primary mb-0 fw-bold">${{ total_cost }} 元</h3>
<!-- DTL 條件判斷:超過 1000 元顯示警示標籤 -->
{% if total_cost > 1000 %}
<span class="badge bg-danger fs-6">🚨 預算超支警示!</span>
{% else %}
<span class="badge bg-success fs-6">✅ 預算狀態良好</span>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-header bg-white fw-bold py-3">📊 歷史記帳明細列表</div>
<div class="card-body p-0">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>#</th>
<th>日期</th>
<th>品項名稱</th>
<th>分類</th>
<th>金額</th>
</tr>
</thead>
<tbody>
{% for r in records %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ r.date|date:"Y-m-d" }}</td>
<td class="fw-bold">{{ r.item }}</td>
<td><span class="badge bg-secondary">{{ r.category }}</span></td>
<td class="text-success fw-bold">${{ r.price }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-4 text-muted">📭 目前尚無任何記帳紀錄!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
【學生自主實作框架】請根據今日各單元所學,將空缺的 `____` 填入正確的 Django Template 關鍵字:
<!-- 🎯 Day 8 自主挑戰:請填入 home.html 模板空缺的 DTL 語法! -->
<!-- 1. 繼承母模板 base.html -->
{% ____ 'base.html' %} # 提示:繼承母模板的關鍵字 extends
{% block content %}
<div class="card p-3 mb-3">
<!-- 2. 印出變數 total_cost -->
<h2>總花費:${{ ____ }} 元</h2> # 提示:印出 View 傳來的變數名稱
<!-- 3. DTL 邏輯判斷條件 -->
{% ____ total_cost > 1000 %} # 提示:條件判斷開頭關鍵字 if
<span class="badge bg-danger">預算超支!</span>
{% ____ %}
<span class="badge bg-success">安全範圍</span>
{% ____ %} # 提示:結束 if 判斷的關鍵字 endif
</div>
<table class="table">
<!-- 4. DTL 清單走訪迴圈 -->
{% ____ r in records %} # 提示:走訪清單開頭關鍵字 for
<tr>
<td>{{ r.item }}</td>
<td>${{ r.price }}</td>
</tr>
{% ____ %} # 提示:結束 for 迴圈的關鍵字 endfor
</table>
{% endblock %}
⏰ 單元 1:模板引擎與變數渲染(09:00 - 10:00)
前一天我們只能在 View 中傳回純文字 HttpResponse("<h1>...</h1>")。今天我們要改用 render(request, 'home.html', context)!這就像是 View 主廚將菜煮好後打包成名為 context 的字典(傳入資料),丟給擺盤設計師(HTML Template),擺盤師只需在 HTML 中寫入 {{ total_cost }} 雙大括號,資料就會瞬間動態填入!對應的就是我們 Day 1 的 f-string!
Django 使用內建的 Django Template Language (DTL) 進行動態 HTML 渲染:
context傳參字典:View 函式將資料包裝為字典(如{'user': 'Alex', 'cost': 100})傳給render()。- 雙大括號
{{ var }}:DTL 變數標記,可以在 HTML 中取出字典 Key 或物件屬性 (如{{ r.item }})。 TEMPLATES['DIRS']設定:Django 會自動在 App 目錄下的templates/資料夾尋找指定的 HTML 檔案。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向初學者詳細說明 Django render() 函式的三個參數 (request, template_name, context),以及在 HTML 中如何使用 {{ 變數名稱 }} 印出 Context 字典傳來的數據。
- 學習
render(request, 'expenses/home.html', context)。 - 在 App 目錄下建立
templates/expenses/home.html檔案。 - 概念連結:對應 Day 1 的
f-string動態字串帶入。
from django.shortcuts import render
def home(request):
user_name = "Alex"
total_budget = 5000
context = {
'user_name': user_name,
'total_budget': total_budget,
}
return render(request, 'expenses/home.html', context)
- TemplateDoesNotExist:找不到 HTML 檔案 $\rightarrow$ 檢查 templates 目錄拼字,或沒放在 App 的 `templates/` 資料夾內。
我在執行 render() 時跳出了 TemplateDoesNotExist 錯誤。請幫我檢查 templates 資料夾的路徑與檔案名稱是否放置正確。
⏰ 單元 2:網頁上的清單走訪 ({% for %})(10:00 - 11:00)
當我們從資料庫用 Expense.objects.all() 抓出 10 筆資料時,要怎麼在網頁表格印出來?我們使用 DTL 的 {% for r in records %} ... {% endfor %}!它會自動循環印出 HTML <tr> 表格列,你甚至可以用 {{ forloop.counter }} 自動印出 1, 2, 3 編號!這完全對應 Day 2 的 for 迴圈!
DTL 標籤 `{% %}` 用於控制邏輯流程:
{% empty %}標籤:當清單長度為 0 時,自動觸發並顯示預設提示訊息(如「目前無任何紀錄」)。forloop.counter:從 1 開始計數當前迴圈的次數(forloop.counter0則從 0 開始)。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請說明如何在 Django HTML 模板中使用 {% for r in records %} 走訪資料庫清單,並示範使用 {% empty %} 與 forloop.counter 變數。
- 使用 Django ORM
Expense.objects.all()抓取資料庫集合。 - 在 HTML 中撰寫
{% for r in records %}與{% endfor %}。 - 概念連結:對應 Day 2 的 Python
for迴圈走訪 List of Dicts。
<ul>
{% for r in records %}
<li>{{ forloop.counter }}. {{ r.item }} - ${{ r.price }} ({{ r.category }})</li>
{% empty %}
<li>📭 目前尚無任何記帳資料!</li>
{% endfor %}
</ul>
- 忘記寫 {% endfor %}:DTL 語法區塊沒有關閉,導致跳出
TemplateSyntaxError: Unclosed tag 'for'。
我在 HTML 中寫 {% for %} 時出現了 TemplateSyntaxError: Unclosed tag 'for' 錯誤。請幫我檢查 HTML 末端是否漏寫了 {% endfor %}。
⏰ 單元 3:引入 Bootstrap 5 美化(11:00 - 12:00)
純 HTML 網頁看起來實在太像 1990 年代的舊網站了。今天我們要套用全球最受歡迎的前端 CSS 框架 **Bootstrap 5**!我們不需要自己寫幾百行 CSS,只要在 `
` 掛載 Bootstrap CDN 連結,然後給元素加上 class(如class="card"、class="table table-hover"),網頁立刻瞬間變身為質感的現代化 App!
Bootstrap 提供高效率的 UI 元件樣式系統:
- CDN (Content Delivery Network):透過全球內容傳遞網路直接引入線上編譯好的 CSS/JS 檔案,無需下載至本機。
- 常用 Bootstrap 類別:
.card(卡片面板)、.table(風格表格)、.badge(彩色徽章)、.btn-primary(質感按鈕)。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請說明如何透過 CDN 在 HTML 中引入 Bootstrap 5,並示範使用 .card 與 .table 工具類別來設計一個漂亮的記帳明細列表。
- 在
<head>中加入 Bootstrap 5 CDN `<link>` 標籤。 - 給與套用 class:
table,table-hover,badge,card。 - 概念連結:對應 Day 5 CLI 的 ASCII 排版與 Emoji 裝飾。
<!-- CDN 引入 (放置於 <head> 內) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- 美化後的表格 -->
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr><th>品項</th><th>金額</th></tr>
</thead>
<tbody>
<tr><td>排骨飯</td><td><span class="badge bg-success">$110</span></td></tr>
</tbody>
</table>
- CDN 網址寫錯或網路阻擋:樣式完全沒有生效,畫面依然是丑丑的純文字 $\rightarrow$ 檢查是否漏寫 `rel="stylesheet"` 或網址拼錯。
我引入了 Bootstrap 5 CDN 但網頁樣式完全沒有改變。請幫我檢查 標籤中的 href 網址與 rel="stylesheet" 屬性是否正確。
⏰ 單元 4:模板繼承 (Template Inheritance)(13:00 - 14:00)
假設網站有 10 個頁面,難道每個頁面都要複製貼上相同的 `<head>` 與頂部導覽列 (Navbar) 嗎?這完全違反了 **DRY 原則 (Don't Repeat Yourself)**!我們建立一個 `base.html` 基礎母模板,把導覽列放在裡面,並留下一個挖空的區塊 {% block content %}{% endblock %}。其他子頁面只需寫 {% extends 'base.html' %},就能一秒繼承所有視覺結構!
模板繼承是 Django 處理前端頁面組裝的核心利器:
{% extends 'base.html' %}:必須放在子模板檔的**第一行**,宣告繼承母板。{% block block_name %}區塊:在母模板預留槽位 (Slot),子模板填入該區塊覆蓋內容。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向初學者解釋 Django 的模板繼承 (Template Inheritance),並示範如何在 base.html 寫出 {% block content %} 以及在子模板寫出 {% extends 'base.html' %}。
- 建立共用母板
templates/base.html。 - 在母板使用
{% block content %}{% endblock %}。 - 子模板第一行宣告
{% extends 'base.html' %}。 - 概念連結:對應 Day 5 DRY 重構原則(不重複寫程式碼)。
<!-- 1. base.html (母模板) -->
<html>
<body>
<header><h1>☕ 理財咖啡館導覽列</h1></header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
<!-- 2. home.html (子模板) -->
{% extends 'base.html' %}
{% block content %}
<h2>這是首頁的獨立內容!</h2>
{% endblock %}
- extends 沒寫在第一行:在
{% extends %}前面放了其他 HTML 標籤或空格,導致渲染時跳出 `TemplateSyntaxError`。
我在子模板使用 {% extends 'base.html' %} 時跳出了錯誤。請提醒我是否必須將 {% extends %} 放在檔案的『第一行』且不能有前導空格。
⏰ 單元 5:邏輯判斷與條件渲染 ({% if %})(14:00 - 15:00)
我們的網頁也能有智慧!當月總消費金額超過 1000 元時,網頁應該自動跳出紅色的「預算超支」標籤!我們在 View 算好總額 total_cost,並在 HTML 中使用 DTL 的條件控制語法 {% if total_cost > 1000 %} ... {% else %} ... {% endif %}!對應 Day 3 的 if-else 判斷!
DTL 在 HTML 中支援基本邏輯判斷與數據過濾:
{% if %} ... {% elif %} ... {% else %} ... {% endif %}:根據情境動態渲染不同的 HTML UI 標籤。- Template Filters 管道符
|:例如{{ total|floatformat:2 }}(控制小數點) 或{{ r.date|date:"Y-m-d" }}(格式化日期)。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請說明如何在 Django HTML 模板中使用 {% if total > 1000 %} 進行條件判斷,並介紹如何用管道符 | (Template Filters) 來格式化數字與日期。
- 在 View 算出
total_cost帶入 Context。 - 在 HTML 中撰寫
{% if total_cost > 1000 %}顯示紅色徽章。 - 概念連結:對應 Day 3 CLI 的
if-elif-else分支判斷。
{% if total_cost > 1000 %}
<span class="badge bg-danger">🚨 預算超支警告!(已花費 ${1000} 以上)</span>
{% else %}
<span class="badge bg-success">✅ 本月預算掌控良好</span>
{% endif %}
- 忘記寫 {% endif %}:拋出
TemplateSyntaxError: Unclosed tag 'if'。
我在 HTML 模板中使用 {% if %} 判斷式時跳出了 TemplateSyntaxError: Unclosed tag 'if'。請幫我檢查是否忘記加上 {% endif %} 標籤。
⏰ 單元 6:介面調教與階段驗收(15:00 - 16:00)
恭喜你!今天你親手完成了理財咖啡館第一個精美的動態網頁首頁!包含頂部導覽列、總開銷卡片、動態超支徽章與從 SQLite 資料庫自動生成的歷史明細表格!請微調你的配色與版面,上台展示你的網站首頁!
高品質網頁介面的三大核心法則:
- 視覺階層 (Visual Hierarchy):將最重要的數據(如總金額)用較大字體與卡片呈現於首頁上方。
- 狀態即時反饋 (Feedback):用顏色徽章 (Badge) 即時回應使用者當前的財務健康狀況。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請給我三個用 Bootstrap 5 提升記帳網頁視覺質感的小技巧(如邊框陰影 shadow-sm、對齊工具類與顏色徽章 badge 的搭配)。
- 輔導學生調教首頁色彩與表格縮排。
- 階段驗收:每人上台展示瀏覽器連線
http://127.0.0.1:8000/的 Bootstrap 5 質感記帳首頁!
# Day 8 網頁首頁成果驗收檢查點:
[✓] 1. 成功建立 templates/base.html 母模板並載入 Bootstrap 5 CDN。
[✓] 2. 子頁面 home.html 成功繼承 base.html 顯示頂部導覽列。
[✓] 3. 能透過 {% for %} 自動將 SQLite 資料庫明細印在 Bootstrap 表格中。
[✓] 4. 能根據總金額透過 {% if %} 自動切換顯示綠色安全或紅色超支徽章。
- HTML 標籤結構錯亂:如 `<table>` 沒有關閉 `</table>` 導致後續卡片樣式變形。
我在套用 Bootstrap 5 樣式後網頁版面歪掉了。請幫我檢查 HTML 中的 <div> 或 <table> 標籤是否沒有正確閉合。