跳轉到

使用訊息框架提供操作回饋

開始之前

任務目標

在這個章節中,我們會完成:

  • 了解 Django Messages Framework
  • 在建立文章後顯示成功訊息
  • 在編輯文章後顯示成功訊息
  • 在刪除文章後顯示成功訊息
  • 使用 django-bootstrap5 美化訊息樣式

為什麼需要訊息回饋?

目前我們的網站在執行操作後,使用者可能不確定操作是否成功:

使用者建立文章 → 重導向到文章詳情頁
→ 使用者心想:「文章真的建立成功了嗎?」

使用者編輯文章 → 重導向到文章詳情頁
→ 使用者心想:「修改真的儲存了嗎?」

使用者刪除文章 → 重導向到文章列表
→ 使用者心想:「文章真的被刪除了嗎?」

缺少回饋的問題

  1. 使用者不確定操作是否成功
  2. 可能重複提交表單(以為沒成功,再送一次)
  3. 使用者體驗不佳

好的使用者介面應該:

  • ✅ 明確告知操作結果
  • ✅ 提供視覺回饋
  • ✅ 讓使用者安心

Django Messages Framework

Django 內建的 Messages Framework 可以輕鬆實作「一次性訊息」:

特色

  1. 自動清除:訊息顯示一次後自動消失
  2. 支援不同等級:成功、錯誤、警告、資訊
  3. 跨頁面顯示:使用 Cookie 或 Session 儲存,可以在重導向後顯示
  4. 整合簡單:只需要幾行程式碼

訊息等級

等級 函式 用途 Bootstrap 樣式
DEBUG messages.debug() 開發時的除錯訊息 alert-warning
INFO messages.info() 一般資訊 alert-info
SUCCESS messages.success() 操作成功 alert-success
WARNING messages.warning() 警告訊息 alert-warning
ERROR messages.error() 錯誤訊息 alert-danger

訊息等級對應

Django Messages Framework 的等級名稱會自動對應到 Bootstrap 的 alert 樣式:

  • successalert-success(綠色)
  • erroralert-danger(紅色)
  • warningalert-warning(黃色)
  • infoalert-info(藍色)

這讓我們可以輕鬆整合 Bootstrap 的樣式!

在 Views 加入訊息

讓我們在建立、編輯、刪除操作後加入訊息。

修改 Views

修改 blog/views.py

blog/views.py
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render

from blog.forms import ArticleForm
from blog.models import Article


def article_list(request):
    articles = Article.objects.select_related("author").prefetch_related("tags")
    return render(request, "blog/article_list.html", {"articles": articles})


def article_detail(request, article_id):
    article = get_object_or_404(
        Article.objects.select_related("author").prefetch_related("tags"),
        id=article_id,
    )
    return render(request, "blog/article_detail.html", {"article": article})


def article_create(request):
    form = ArticleForm(request.POST or None)
    if form.is_valid():
        article = form.save()
        messages.success(request, f"文章「{article.title}」已成功建立。")  # (1)!
        return redirect("blog:article_detail", article_id=article.id)

    return render(request, "blog/article_create.html", {"form": form})


def article_edit(request, article_id):
    article = get_object_or_404(Article, id=article_id)
    form = ArticleForm(request.POST or None, instance=article)
    if form.is_valid():
        article = form.save()
        messages.success(request, f"文章「{article.title}」已成功更新。")  # (2)!
        return redirect("blog:article_detail", article_id=article.id)

    return render(request, "blog/article_edit.html", {"form": form, "article": article})


def article_delete(request, article_id):
    article = get_object_or_404(Article, id=article_id)

    if request.method == "POST":
        article.delete()
        messages.success(request, f"文章「{article.title}」已成功刪除。")  # (3)!
        return redirect("blog:article_list")

    return render(request, "blog/article_delete.html", {"article": article})
  1. 建立文章後加入成功訊息
  2. 編輯文章後加入成功訊息
  3. 刪除文章後加入成功訊息

程式碼重點

  1. 匯入 messages 模組from django.contrib import messages
  2. 加入訊息messages.success(request, "訊息內容")
  3. 在 redirect 前加入:訊息會儲存起來(Cookie 或 Session),重導向後依然可以顯示
  4. 使用 f-string:動態顯示文章標題

在 Template 顯示訊息

使用 django-bootstrap5

django-bootstrap5 套件提供了方便的 template tag 來顯示訊息。

修改 templates/base.html

templates/base.html
{% load django_bootstrap5 %}

<!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>

    {% bootstrap_css %}

    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" />

    {% block extra_head %}
    {% endblock extra_head %}
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <span class="navbar-brand">Django 大冒險</span>
        <button class="navbar-toggler"
                type="button"
                data-bs-toggle="collapse"
                data-bs-target="#navbarNav">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav ms-auto">
            <li class="nav-item">
              <a class="nav-link" href="{% url 'blog:article_list' %}">文章列表</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="{% url 'admin:index' %}">管理後台</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>

    <main class="container my-4">
      {% bootstrap_messages %}

      {% block content %}
      {% endblock content %}
    </main>

    <footer class="bg-light py-4 mt-5">
      <div class="container text-center">
        <p class="text-muted mb-0">
          Django Playground
        </p>
      </div>
    </footer>

    {% bootstrap_javascript %}

    {% block extra_scripts %}
    {% endblock extra_scripts %}
  </body>
</html>

bootstrap_messages 的功能

{% bootstrap_messages %} 會自動:

  1. 迭代所有訊息:顯示所有待顯示的訊息
  2. 套用正確的 Bootstrap 樣式:根據訊息等級自動使用對應的 alert 樣式
  3. 加入關閉按鈕:使用者可以手動關閉訊息
  4. 加入動畫效果:使用 Bootstrap 的 fade 效果

這比手動寫 template 程式碼簡單多了!

手動寫法(參考)

如果想要更客製化,也可以手動寫:

手動寫法範例
{% if messages %}
  {% for message in messages %}
    <div class="alert alert-{{ message.tags }} alert-dismissible fade show"
         role="alert">
      {{ message }}
      <button type="button"
              class="btn-close"
              data-bs-dismiss="alert"
              aria-label="Close"></button>
    </div>
  {% endfor %}
{% endif %}

兩種寫法比較

項目 {% bootstrap_messages %} 手動寫法
程式碼量 1 行 10+ 行
樣式 自動套用 Bootstrap 需要手動設定
客製化 較少 完全自由
維護性 簡單 較複雜

大多數情況下,使用 {% bootstrap_messages %} 就足夠了!

測試訊息功能

啟動開發伺服器:

uv run manage.py runserver

測試建立文章

  1. 訪問 http://127.0.0.1:8000/blog/articles/create/
  2. 填寫表單並送出
  3. 重導向到文章詳情頁
  4. 看到綠色的成功訊息:「文章「{標題}」已成功建立。」

測試編輯文章

  1. 在文章詳情頁點擊「編輯」
  2. 修改內容並儲存
  3. 重導向到文章詳情頁
  4. 看到綠色的成功訊息:「文章「{標題}」已成功更新。」

測試刪除文章

  1. 在文章詳情頁點擊「刪除」
  2. 在確認頁面點擊「確認刪除」
  3. 重導向到文章列表頁
  4. 看到綠色的成功訊息:「文章「{標題}」已成功刪除。」

測試關閉訊息

  1. 點擊訊息右上角的 ✕ 按鈕
  2. 訊息消失

測試自動清除

  1. 看到訊息後,重新整理頁面
  2. 訊息不再出現(已自動清除)

訊息功能運作正常

現在使用者可以:

  • ✅ 明確知道操作是否成功
  • ✅ 看到綠色的成功提示(視覺回饋)
  • ✅ 手動關閉訊息
  • ✅ 訊息不會一直出現(自動清除)

進階:客製化訊息顯示

修改訊息位置

如果想讓訊息顯示在特定位置,可以移動 {% bootstrap_messages %}

<body>
  <div class="container my-4">
    <h1 class="mb-4">Django 大冒險</h1>

    {% block blog_content %}{% endblock blog_content %}

    <!-- 訊息顯示在底部 -->
    {% bootstrap_messages %}
  </div>
</body>

使用不同的訊息等級

在不同情況下使用不同的訊息等級:

範例
# 成功訊息(綠色)
messages.success(request, "操作成功!")

# 資訊訊息(藍色)
messages.info(request, "這是一個提示訊息。")

# 警告訊息(黃色)
messages.warning(request, "請注意這個操作。")

# 錯誤訊息(紅色)
messages.error(request, "發生錯誤!")

實際應用範例

修改 article_create view,加入驗證失敗的訊息:

範例
def article_create(request):
    form = ArticleForm(request.POST or None)
    if form.is_valid():
        article = form.save()
        messages.success(request, f"文章「{article.title}」已成功建立。")
        return redirect("blog:article_detail", article_id=article.id)

    # 表單驗證失敗
    if request.method == "POST":
        messages.error(request, "表單填寫有誤,請檢查並重新送出。")

    return render(request, "blog/article_create.html", {"form": form})

何時使用錯誤訊息

一般來說,Django Form 的驗證錯誤會直接顯示在表單欄位旁,不需要額外加入錯誤訊息。

只有在以下情況才需要加入錯誤訊息:

  • 非表單驗證的錯誤(例如:權限不足)
  • 需要在頁面頂部顯示總體錯誤
  • 自訂的業務邏輯錯誤

大多數情況下,使用成功訊息就足夠了!

Messages Framework 運作原理

讓我們了解 Messages Framework 如何運作。

儲存機制

Django Messages Framework 預設使用 FallbackStorage,它會智慧地選擇儲存方式:

1. 在 View 中呼叫 messages.success()
   → 訊息先嘗試儲存在 Cookie 中

2. 如果訊息太大(超過 2048 bytes)
   → 自動改用 Session 儲存

3. 重導向到其他頁面
   → Cookie 或 Session 資料跟著使用者移動

4. Template 顯示訊息
   → 從 Cookie 或 Session 讀取訊息並顯示

5. 訊息顯示後
   → 自動清除訊息

為什麼使用 Cookie 優先?

  1. 效能更好:不需要在伺服器端儲存資料
  2. 減少 Session 寫入:只有大訊息才會用到 Session
  3. 自動降級:訊息太大時自動切換到 Session

這就是 FallbackStorage 的設計精髓!

為什麼可以跨頁面?

sequenceDiagram
    participant View as article_create View
    participant Storage as FallbackStorage
    participant Browser as 瀏覽器
    participant Template as article_detail Template

    View->>Storage: 儲存訊息
    Note over Storage: 先嘗試 Cookie<br/>太大則用 Session
    View->>Browser: redirect("article_detail")
    Note over Browser: Cookie 跟著請求傳送
    Browser->>Template: GET /articles/1/
    Template->>Storage: 讀取訊息
    Storage-->>Template: ["文章已建立"]
    Template->>Browser: 顯示訊息
    Template->>Storage: 清除訊息

FallbackStorage 的優勢

使用 FallbackStorage 儲存訊息的好處:

  1. 可以跨頁面:重導向後依然可以顯示
  2. 自動清除:顯示一次後自動刪除
  3. 使用者隔離:每個使用者看到自己的訊息
  4. 不影響 URL:訊息不會出現在網址列
  5. 效能優化:優先使用 Cookie,減少 Session 負擔

這就是為什麼 Messages Framework 如此好用!

常見問題

為什麼訊息沒有顯示?

檢查以下幾點:

  1. 是否載入 django_bootstrap5

    {% load django_bootstrap5 %}
    
  2. 是否加入 bootstrap_messages

    {% bootstrap_messages %}
    
  3. 是否在正確的時機加入訊息

    # ✅ 正確:在 redirect 前加入
    messages.success(request, "成功!")
    return redirect("...")
    
    # ❌ 錯誤:在 redirect 後加入
    return redirect("...")
    messages.success(request, "成功!")  # 永遠不會執行
    
  4. 是否載入 Bootstrap JavaScript

    <script src="{% static 'blog/js/bootstrap.bundle.min.js' %}"></script>
    

    關閉按鈕需要 Bootstrap 的 JavaScript。

訊息會一直顯示嗎?

不會!訊息是「一次性」的:

  • 第一次訪問頁面:顯示訊息
  • 重新整理頁面:訊息消失

如果訊息一直出現,可能是:

  1. 每次訪問頁面都執行了 messages.success()
  2. 檢查 View 的邏輯

可以一次顯示多個訊息嗎?

可以!Messages Framework 會自動處理:

範例
messages.success(request, "文章建立成功。")
messages.info(request, "記得定期備份文章。")
# 兩個訊息都會顯示

任務結束

完成!

恭喜你完成了這個章節!現在你已經:

  • 了解 Django Messages Framework
  • 在建立文章後顯示成功訊息
  • 在編輯文章後顯示成功訊息
  • 在刪除文章後顯示成功訊息
  • 使用 django-bootstrap5 美化訊息樣式

你學會了:

  1. 使用者體驗的重要性:明確的操作回饋讓使用者更安心
  2. Messages Framework 的運作原理:使用 FallbackStorage(Cookie + Session)實作跨頁面訊息
  3. 整合 django-bootstrap5:一行程式碼搞定訊息顯示
  4. 不同的訊息等級:成功、錯誤、警告、資訊