🚀 Day 8:視圖與前端渲染(View & Template)

5 天營隊第二週 (Day 8 / 總時數 6 Hours) | 將 SQLite 資料庫紀錄掏出來,套用 Bootstrap 5 打造現代化 App 網頁!

📖 為什麼學 these?——「理財咖啡館網頁擺盤與現代化包裝」關卡冒險故事線

💡 故事情境:告別純文字,讓記帳網站變得跟市面上 App 一樣好看!

昨天我們成功把資料庫建立好了,但純文字 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 體驗優化!

📌 課程前導與簡介說明

🎓 教師心法與教學策略 (Teacher Only)
  • 營隊定位:從純文字 HttpResponse 正式邁向高質感 HTML/CSS 前端渲染。
  • 高中生心理亮點:當掛上 Bootstrap 5 CDN 之後,原本簡陋的網頁瞬間變成媲美市面商業 App 的質感網站,學生會充滿視覺震憾與成就感!
  • 教學銜接點:不斷連結舊觀念({{ 變數 }} 對應 Day 1 f-string{% for %} 對應 Day 2 for 迴圈;{% extends %} 對應 Day 5 DRY 重構)。
🌟 學生自主學習指引 (Student Self-Study)

歡迎來到 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)

🎨 故事單元說明:擺盤設計師 (render & {{ var }})

前一天我們只能在 View 中傳回純文字 HttpResponse("<h1>...</h1>")。今天我們要改用 render(request, 'home.html', context)!這就像是 View 主廚將菜煮好後打包成名為 context 的字典(傳入資料),丟給擺盤設計師(HTML Template),擺盤師只需在 HTML 中寫入 {{ total_cost }} 雙大括號,資料就會瞬間動態填入!對應的就是我們 Day 1 的 f-string

💡 深度觀念:什麼是 Context 字典與 `{{ variable }}` 渲染?

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):

探索 Prompt (點擊複製)

請向初學者詳細說明 Django render() 函式的三個參數 (request, template_name, context),以及在 HTML 中如何使用 {{ 變數名稱 }} 印出 Context 字典傳來的數據。

🎓 教師備課手冊 (Teacher Only)
  • 學習 render(request, 'expenses/home.html', context)
  • 在 App 目錄下建立 templates/expenses/home.html 檔案。
  • 概念連結:對應 Day 1 的 f-string 動態字串帶入。
💻 程式碼段落 (`expenses/views.py`)
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/` 資料夾內。
🤖 單元 1 常見問題 AI 協作除錯提示詞

我在執行 render() 時跳出了 TemplateDoesNotExist 錯誤。請幫我檢查 templates 資料夾的路徑與檔案名稱是否放置正確。

⏰ 單元 2:網頁上的清單走訪 ({% for %})(10:00 - 11:00)

🛒 故事單元說明:網頁菜單印表機 ({% for %})

當我們從資料庫用 Expense.objects.all() 抓出 10 筆資料時,要怎麼在網頁表格印出來?我們使用 DTL 的 {% for r in records %} ... {% endfor %}!它會自動循環印出 HTML <tr> 表格列,你甚至可以用 {{ forloop.counter }} 自動印出 1, 2, 3 編號!這完全對應 Day 2 的 for 迴圈!

💡 深度觀念:`{% for %}` 迴圈與 `forloop` 內建變數

DTL 標籤 `{% %}` 用於控制邏輯流程:

  • {% empty %} 標籤:當清單長度為 0 時,自動觸發並顯示預設提示訊息(如「目前無任何紀錄」)。
  • forloop.counter:從 1 開始計數當前迴圈的次數(forloop.counter0 則從 0 開始)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明如何在 Django HTML 模板中使用 {% for r in records %} 走訪資料庫清單,並示範使用 {% empty %} 與 forloop.counter 變數。

🎓 教師備課手冊 (Teacher Only)
  • 使用 Django ORM Expense.objects.all() 抓取資料庫集合。
  • 在 HTML 中撰寫 {% for r in records %}{% endfor %}
  • 概念連結:對應 Day 2 的 Python for 迴圈走訪 List of Dicts。
💻 HTML 範例 (`templates/expenses/home.html`)
<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'
🤖 單元 2 常見問題 AI 協作除錯提示詞

我在 HTML 中寫 {% for %} 時出現了 TemplateSyntaxError: Unclosed tag 'for' 錯誤。請幫我檢查 HTML 末端是否漏寫了 {% endfor %}。

⏰ 單元 3:引入 Bootstrap 5 美化(11:00 - 12:00)

✨ 故事單元說明:現代化 App 魔法外衣 (Bootstrap)

純 HTML 網頁看起來實在太像 1990 年代的舊網站了。今天我們要套用全球最受歡迎的前端 CSS 框架 **Bootstrap 5**!我們不需要自己寫幾百行 CSS,只要在 `` 掛載 Bootstrap CDN 連結,然後給元素加上 class(如 class="card"class="table table-hover"),網頁立刻瞬間變身為質感的現代化 App!

💡 深度觀念:什麼是 CDN 與 Bootstrap CSS Class?

Bootstrap 提供高效率的 UI 元件樣式系統:

  • CDN (Content Delivery Network):透過全球內容傳遞網路直接引入線上編譯好的 CSS/JS 檔案,無需下載至本機。
  • 常用 Bootstrap 類別.card (卡片面板)、.table (風格表格)、.badge (彩色徽章)、.btn-primary (質感按鈕)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明如何透過 CDN 在 HTML 中引入 Bootstrap 5,並示範使用 .card 與 .table 工具類別來設計一個漂亮的記帳明細列表。

🎓 教師備課手冊 (Teacher Only)
  • <head> 中加入 Bootstrap 5 CDN `<link>` 標籤。
  • 給與套用 class:table, table-hover, badge, card
  • 概念連結:對應 Day 5 CLI 的 ASCII 排版與 Emoji 裝飾。
💻 HTML 範例 (Bootstrap CDN 與 Table)
<!-- 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"` 或網址拼錯。
🤖 單元 3 常見問題 AI 協作除錯提示詞

我引入了 Bootstrap 5 CDN 但網頁樣式完全沒有改變。請幫我檢查 標籤中的 href 網址與 rel="stylesheet" 屬性是否正確。

⏰ 單元 4:模板繼承 (Template Inheritance)(13:00 - 14:00)

🏛️ 故事單元說明:通用建築母板 (base.html)

假設網站有 10 個頁面,難道每個頁面都要複製貼上相同的 `<head>` 與頂部導覽列 (Navbar) 嗎?這完全違反了 **DRY 原則 (Don't Repeat Yourself)**!我們建立一個 `base.html` 基礎母模板,把導覽列放在裡面,並留下一個挖空的區塊 {% block content %}{% endblock %}。其他子頁面只需寫 {% extends 'base.html' %},就能一秒繼承所有視覺結構!

💡 深度觀念:模板繼承 (Template Inheritance) 哲學

模板繼承是 Django 處理前端頁面組裝的核心利器:

  • {% extends 'base.html' %}:必須放在子模板檔的**第一行**,宣告繼承母板。
  • {% block block_name %} 區塊:在母模板預留槽位 (Slot),子模板填入該區塊覆蓋內容。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向初學者解釋 Django 的模板繼承 (Template Inheritance),並示範如何在 base.html 寫出 {% block content %} 以及在子模板寫出 {% extends 'base.html' %}。

🎓 教師備課手冊 (Teacher Only)
  • 建立共用母板 templates/base.html
  • 在母板使用 {% block content %}{% endblock %}
  • 子模板第一行宣告 {% extends 'base.html' %}
  • 概念連結:對應 Day 5 DRY 重構原則(不重複寫程式碼)。
💻 HTML 範例 (`base.html` 與子模板)
<!-- 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`。
🤖 單元 4 常見問題 AI 協作除錯提示詞

我在子模板使用 {% extends 'base.html' %} 時跳出了錯誤。請提醒我是否必須將 {% extends %} 放在檔案的『第一行』且不能有前導空格。

⏰ 單元 5:邏輯判斷與條件渲染 ({% if %})(14:00 - 15:00)

🚨 故事單元說明:動態預算超支警報器 ({% if %})

我們的網頁也能有智慧!當月總消費金額超過 1000 元時,網頁應該自動跳出紅色的「預算超支」標籤!我們在 View 算好總額 total_cost,並在 HTML 中使用 DTL 的條件控制語法 {% if total_cost > 1000 %} ... {% else %} ... {% endif %}!對應 Day 3 的 if-else 判斷!

💡 深度觀念:`{% if %}` 條件判斷與模板過濾器 Filter

DTL 在 HTML 中支援基本邏輯判斷與數據過濾:

  • {% if %} ... {% elif %} ... {% else %} ... {% endif %}:根據情境動態渲染不同的 HTML UI 標籤。
  • Template Filters 管道符 |:例如 {{ total|floatformat:2 }} (控制小數點) 或 {{ r.date|date:"Y-m-d" }} (格式化日期)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明如何在 Django HTML 模板中使用 {% if total > 1000 %} 進行條件判斷,並介紹如何用管道符 | (Template Filters) 來格式化數字與日期。

🎓 教師備課手冊 (Teacher Only)
  • 在 View 算出 total_cost 帶入 Context。
  • 在 HTML 中撰寫 {% if total_cost > 1000 %} 顯示紅色徽章。
  • 概念連結:對應 Day 3 CLI 的 if-elif-else 分支判斷。
💻 HTML 範例 (條件渲染)
{% if total_cost > 1000 %}
    <span class="badge bg-danger">🚨 預算超支警告!(已花費 ${1000} 以上)</span>
{% else %}
    <span class="badge bg-success">✅ 本月預算掌控良好</span>
{% endif %}
⚠️ 常見陷阱與問題案例
  • 忘記寫 {% endif %}:拋出 TemplateSyntaxError: Unclosed tag 'if'
🤖 單元 5 常見問題 AI 協作除錯提示詞

我在 HTML 模板中使用 {% if %} 判斷式時跳出了 TemplateSyntaxError: Unclosed tag 'if'。請幫我檢查是否忘記加上 {% endif %} 標籤。

⏰ 單元 6:介面調教與階段驗收(15:00 - 16:00)

🏆 故事單元說明:理財咖啡館網頁首頁發布

恭喜你!今天你親手完成了理財咖啡館第一個精美的動態網頁首頁!包含頂部導覽列、總開銷卡片、動態超支徽章與從 SQLite 資料庫自動生成的歷史明細表格!請微調你的配色與版面,上台展示你的網站首頁!

💡 深度觀念:前端用戶體驗 (UI/UX) 微調

高品質網頁介面的三大核心法則:

  • 視覺階層 (Visual Hierarchy):將最重要的數據(如總金額)用較大字體與卡片呈現於首頁上方。
  • 狀態即時反饋 (Feedback):用顏色徽章 (Badge) 即時回應使用者當前的財務健康狀況。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請給我三個用 Bootstrap 5 提升記帳網頁視覺質感的小技巧(如邊框陰影 shadow-sm、對齊工具類與顏色徽章 badge 的搭配)。

🎓 教師備課手冊 (Teacher Only)
  • 輔導學生調教首頁色彩與表格縮排。
  • 階段驗收:每人上台展示瀏覽器連線 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>` 導致後續卡片樣式變形。
🤖 單元 6 常見問題 AI 協作除錯提示詞

我在套用 Bootstrap 5 樣式後網頁版面歪掉了。請幫我檢查 HTML 中的 <div> 或 <table> 標籤是否沒有正確閉合。