Django 留言功能新增與視圖互動詳解
更新日期: 2024 年 12 月 2 日
本文為 Django 高階教學,第 4 篇:
- 使用 Poetry 管理 Django 專案:完整指南
- 使用 ModelForm 優化 Django 表單與資料庫操作
- Django 留言功能實作指南:從模型設計到應用完成
- Django 留言功能新增與視圖互動詳解 👈 所在位置
- 在 Django 中實現留言檢索與排序功能:完整指南
- 在 Django 中實現留言換行與顯示時間功能
- 在 Django 中實現留言刪除功能:完整指南
- 在 Django 中實現軟刪除功能:完整指南
- 在 Django 中實現軟刪除功能:Fat Model, Thin View
建議閱讀本文前,先閱讀完 Django 進階教學 系列文
在建立完整的留言功能時,不僅需要處理頁面顯示與表單提交,還需要理解 Django 應用之間如何協作。
本指南將帶你完成留言功能的設置與優化,並詳細解釋應用間的互動原理。
調整 Resumes 應用的顯示頁面
在 resumes
應用的 show.html
文件中,我們需要新增留言表單,讓用戶可以針對履歷新增留言。
目錄結構
mysite/
├── resumes/
│ ├── templates/
│ ├── resumes/
│ ├── show.html
新增留言表單
{% extends "shared/layout.html" %} {% block content %} {% if messages %}
<div class="alert-box">
{% for message in messages %}
<div class="alert {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
<h1>show! {{ resume.title }}</h1>
<h3>{{ resume.skill }}</h3>
<article>
<p>{{ resume.location }}</p>
<p>{{ resume.content }}</p>
</article>
<footer>
<a href="{% url 'resumes:edit' resume.id %}">編輯</a>
<a href="{% url 'resumes:delete' resume.id %}">刪除</a>
</footer>
<section>
<form action="{% url 'resumes:comments' resume.id %}" method="post">
{% csrf_token %}
<textarea name="content" id=""></textarea>
<button>新增留言</button>
</form>
</section>
新增留言表單功能詳解
在新增留言功能的表單中,我們對頁面的兩個區塊進行重點講解,分別是消息提示區塊與留言提交表單的處理邏輯。
消息提示區塊
{% if messages %}
<div class="alert-box">
{% for message in messages %}
<div class="alert {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
區塊功能
- 檢查消息是否存在
{% if messages %}
:確認是否有消息需要顯示。messages
是 Django 中的內建消息框架,主要用於在視圖中傳遞用戶提示消息。
- 迴圈渲染消息
{% for message in messages %}
:遍歷所有消息,將其逐一顯示在頁面中。
- 渲染樣式
<div class="alert {{ message.tags }}">
:通過message.tags
為消息添加動態的 CSS 樣式類別(如success
、error
等)。message.tags
通常是視圖中傳遞的消息類型(如success
或warning
),用於設定消息的樣式。
2. 留言提交表單區塊
<section>
<form action="{% url 'resumes:comments' resume.id %}" method="post">
{% csrf_token %}
<textarea name="content" id=""></textarea>
<button>新增留言</button>
</form>
</section>
區塊功能
- 表單提交路徑
resume.id
:將當前履歷的 ID 作為參數傳遞,確保留言正確關聯至對應的履歷。<form action="{% url 'resumes:comments' resume.id %}" method="post">
:{% url 'resumes:comments' resume.id %}
:- 生成動態路徑,例如:
http://localhost:8000/resumes/1/comments
。 - 對應於
resumes
應用的路由設定:
- 生成動態路徑,例如:
path("<int:id>/comments", comment_views.index, name="comments")
- CSRF 安全保護
{% csrf_token %}
:插入跨站請求保護令牌,防止表單被惡意提交。- Django 的中間件會檢查該令牌,只有匹配的請求才能被接受。
- 留言內容輸入
<textarea name="content" id=""></textarea>
:- 提供用戶輸入留言內容的區域。
name="content"
:對應於提交時的鍵值,後端可通過request.POST.get('content')
獲取內容。
- 提交按鈕
<button>新增留言</button>
:- 提交表單內容,觸發 POST 請求。
配置 Resumes 應用的路由
在 resumes
應用的 urls.py
文件中,新增留言處理的路由。
from django.urls import path
from . import views
from comments import views as comment_views # 引入 comments 應用的視圖
app_name = 'resumes'
urlpatterns = [
path("", views.home, name='list'),
path("new/", views.new, name='nn'),
path("<int:id>", views.show, name="show"),
path("<int:id>/edit", views.edit, name="edit"),
path("<int:id>/delete", views.delete, name="delete"),
path("<int:id>/comments", comment_views.index, name="comments"), # 新增留言路徑
]
解釋
comment_views.index
:- 來自
comments
應用的視圖函數,負責處理留言的新增。
- 來自
<int:id>
:- 透過路徑參數傳遞履歷的 ID,讓視圖知道留言屬於哪份履歷。
編寫 Comments 應用的視圖
在 comments
應用的 views.py
中,新增留言處理邏輯:
from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.http import require_POST
from django.contrib import messages
from resumes.models import Resume
from .models import Comment
@require_POST
def index(request, id):
# 找出對應的 Resume
resume = get_object_or_404(Resume, id=id)
# 新增 Comment
comment = Comment()
comment.content = request.POST.get('content') # 取得表單中的留言內容
comment.resume_id = resume.id
comment.save()
# 顯示成功訊息
messages.success(request, "新增留言成功!")
# 轉址回原頁面
return redirect('resumes:show', id=resume.id)
根據 ID 檢索履歷
resume = get_object_or_404(Resume, id=id)
- 作用:
- 從資料庫中查找 ID 為
id
的履歷。 - 如果找不到對應的履歷,返回 HTTP 404 錯誤頁面。
- 從資料庫中查找 ID 為
- 為什麼使用
get_object_or_404
:- 確保代碼簡潔:相比於手動檢查是否找到對象並拋出錯誤,這是一種更簡潔的寫法。
- 增強用戶體驗:當目標履歷不存在時,直接返回標準的 404 錯誤頁面,避免程序崩潰。
創建並保存留言
以下代碼負責創建並保存留言的核心邏輯:
comment = Comment()
comment.content = request.POST.get('content') # 取得表單中的留言內容
comment.resume_id = resume.id # 將留言與履歷關聯
comment.save() # 保存留言至資料庫
2.1 創建 Comment
實例
comment = Comment()
- 作用:初始化一個
Comment
模型的空白實例,為即將保存的留言準備容器。 - 背後邏輯:
- Django 模型類提供了默認的構造函數,允許創建一個空的模型對象,稍後可通過設置其屬性來補全內容。
2.2 設置留言內容
comment.content = request.POST.get('content') # 取得表單中的留言內容
request.POST.get('content')
的作用:- 從用戶提交的表單中獲取
content
欄位的值。 - 該值對應於留言表單中的
<textarea name="content">
。
- 從用戶提交的表單中獲取
- 處理數據的注意點:
get()
方法在鍵不存在時不會拋出錯誤,而是返回None
,這樣可以避免程序崩潰。- 如果需要處理必填欄位,建議在提交表單之前先進行表單驗證。
2.3 設置關聯的履歷 ID
comment.resume_id = resume
- 作用:
- 通過設置
resume_id
,將該留言與目標履歷進行關聯。 resume_id
是Comment
模型中的外鍵欄位。
- 通過設置
- 背後邏輯:
- 在 Django 中,外鍵欄位用於建立模型之間的關聯。
- 這裡的
resume_id
是一個整數,對應Resume
模型的主鍵(id
欄位)。
2.4 保存到資料庫
comment.save()
- 作用:將已設置好屬性的
Comment
實例保存到資料庫中。 - 背後邏輯:
- 查詢轉換:Django 自動將模型對象的屬性轉換為 SQL INSERT 語句。
- 例如,這段代碼會執行類似於以下的 SQL 操作:
INSERT INTO comments_comment (content, resume_id, created_at) VALUES ('留言內容', 1, '2024-11-21 12:00:00');
- 例如,這段代碼會執行類似於以下的 SQL 操作:
- 驗證與保存:
save()
方法會先檢查所有欄位是否符合模型定義的要求(如必填欄位、最大長度等),然後將數據保存到資料庫中。
- 查詢轉換:Django 自動將模型對象的屬性轉換為 SQL INSERT 語句。
顯示成功訊息
messages.success(request, "新增留言成功!")
- 作用:通知用戶操作已成功完成。
- 背後邏輯:
- Django 的
messages
框架提供了一種簡單的方式來顯示用戶友好的通知。 - 這段代碼會將一條 “新增留言成功!” 的消息添加到會話中,稍後可以在模板中顯示
- Django 的
重定向至原始履歷頁面
return redirect('resumes:show', id=resume.id)
- 作用:
- 使用
redirect
函數將用戶導回原始履歷頁面。 - 避免用戶在刷新頁面時重新提交表單,導致重複數據。
- 使用
- 背後邏輯:
- 路由名稱解析:
'resumes:show'
是路由的命名空間,指向目標頁面的路由。 - URL 生成:
- 通過傳遞
id=resume.id
,動態生成目標 URL,例如:http://localhost:8000/resumes/1/
。
- 通過傳遞
- 路由名稱解析:
補充:Django 外鍵運作機制
1. 外鍵的本質
外鍵(ForeignKey
)是一種資料庫關聯欄位,用來建立表與表之間的關聯。
在 Django 中,當你在模型中定義外鍵時:
class Comment(models.Model):
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
resume
欄位在 Python 層面上是一個 對應的模型實例(Resume
),讓你可以方便地存取或操作整個Resume
資料。- 在資料庫層面,外鍵欄位實際上只會儲存被關聯表的主鍵(
Resume
的id
值),這樣可以節省空間並加速查詢。
2. Django 為什麼只儲存主鍵(id
)
當你執行以下程式碼:
comment.resume = resume
Django 會自動提取 resume.id
,並將其儲存在資料庫中對應的外鍵欄位中(通常命名為 resume_id
)。
原因如下:
- 資料庫的最佳實踐
關聯式資料庫設計中,外鍵的本質是儲存被參照資料表的主鍵,這樣可以保持資料結構的簡潔,並避免重複儲存同樣的資料。 - 外鍵欄位的設計
在資料庫中,外鍵只需要指向關聯表的一條記錄。通過儲存主鍵(例如resume.id
),可以快速查詢到完整的Resume
資料。 - 節省空間與效率
如果外鍵欄位直接儲存整個Resume
資料(例如title
、skill
等),不僅會浪費儲存空間,還會讓資料冗餘並難以維護。
3. Django 的物件關聯
儘管資料庫層面只儲存 resume_id
,Django 幫助你隱藏了這些細節,讓你可以直接操作模型實例:
- 取值時:當你存取
comment.resume
時,Django 會自動幫你查詢資料庫,並返回與該resume_id
對應的Resume
實例。 - 賦值時:當你將
Resume
實例賦值給comment.resume
時,Django 只會提取並儲存主鍵resume.id
。
留言功能的完整流程與順序
以下是新增留言功能的詳細流程,涵蓋從頁面顯示、表單提交到視圖處理與頁面轉址的每一步邏輯與細節。
目錄結構
Project/
├── resumes/
│ ├── templates/
│ │ ├── resumes/
│ │ │ ├── show.html
│ ├── urls.py
│ ├── views/
│ │ ├── comment_views.py
├── db.sqlite3
步驟 1: 表單設計
目錄指引
目前位置:templates/resumes/show.html
操作內容
- 在
show.html
中新增留言表單,用於提交用戶輸入內容。 - 代碼:
<form action="{% url 'resumes:comments' resume.id %}" method="post">
{% csrf_token %}
<textarea name="content"></textarea>
<button>新增留言</button>
</form>
步驟 2: 設置路由
目錄指引
目前位置:resumes/urls.py
操作內容
- 在
urls.py
中新增路由,匹配表單提交的目標地址:
path("<int:id>/comments", comment_views.index, name="comments")
- 路由解析:
- URL 示例:http://localhost:8000/resumes/1/comments
- 將請求導向
comment_views.index
。
步驟 3: 表單提交與處理
目錄指引
目前位置:views/comment_views.py
操作內容
- 處理 POST 請求,執行以下步驟:
- 檢索履歷:
resume = get_object_or_404(Resume, id=id)
- 查找 ID 對應的履歷,若找不到則返回 404。
- 保存留言:
comment = Comment() comment.content = request.POST.get('content') # 提取內容 comment.resume_id = resume.id # 關聯到履歷 comment.save() # 保存
- 顯示成功訊息並重定向:
messages.success(request, "新增留言成功!") return redirect('resumes:show', id=resume.id)
步驟 4: 頁面轉址與成功提示
目錄指引
目前位置:templates/resumes/show.html
操作內容
- 在表單提交後,頁面會重定向到原履歷顯示頁面。
- 用戶體驗改進:
- 成功訊息展示:「新增留言成功!」
- 頁面即時顯示新增的留言內容。
完整流程示意圖
以下為操作邏輯的圖示:
1. 表單設計 (show.html)
↓
2. 設置路由 (urls.py)
↓
3. 視圖處理 (comment_views.py)
↓
4. 頁面轉址 (show.html)
- 每一步操作對應的檔案及邏輯處理清晰呈現,便於快速理解與實踐。
小結
- 留言功能的實現:
- 配置 Resumes 與 Comments 的應用與路由,實現跨應用協作。
- 表單提交與視圖處理:
- 使用
POST
方法與視圖邏輯保存留言。
- 使用
留言功能不僅增強了網站的互動性,也展示了 Django 應用之間的高效協作方式。
透過本文,期望你能熟悉表單提交、跨應用視圖調用與功能優化的實踐!