在 Django 中實現軟刪除功能:完整指南
更新日期: 2024 年 12 月 3 日
本文為 Django 高階教學,第 8 篇:
- 使用 Poetry 管理 Django 專案:完整指南
- 使用 ModelForm 優化 Django 表單與資料庫操作
- Django 留言功能實作指南:從模型設計到應用完成
- Django 留言功能新增與視圖互動詳解
- 在 Django 中實現留言檢索與排序功能:完整指南
- 在 Django 中實現留言換行與顯示時間功能
- 在 Django 中實現留言刪除功能:完整指南
- 在 Django 中實現軟刪除功能:完整指南 👈 所在位置
- 在 Django 中實現軟刪除功能:Fat Model, Thin View
建議閱讀本文前,先閱讀完 Django 進階教學 系列文
在應用中,某些資料可能需要保留歷史記錄而不是直接刪除,例如留言系統中的留言記錄。
透過「軟刪除」,我們可以標記資料為已刪除,而不是實際從資料庫中刪除。
以下將詳細說明如何實現軟刪除功能。
軟刪除的設計概念
軟刪除的需求
- 避免資料丟失
軟刪除不會真正刪除資料,而是通過標記的方式(如設置deleted_at
時間戳)判定資料是否有效。 - 提升操作靈活性
支援資料的恢復和追蹤已刪除資料的歷史記錄。
使用 deleted_at
欄位
- 新增一個
deleted_at
欄位作為軟刪除的標記:null=True
:允許未刪除的資料此欄位為空。- 使用
DateTimeField
紀錄刪除時間,若資料未刪除,該欄位為NULL
。
- 優化查詢速度:使用索引
在deleted_at
欄位上設置索引(db_index=True
),可以提升查詢效率。- 注意:雖然索引加快了讀取速度,但可能影響新增和更新的效能,因為需要維護索引。
修改模型以支持軟刪除
在 comments/models.py
中,對 Comment
模型進行調整:
from django.db import models
from resumes.models import Resume
class Comment(models.Model):
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
deleted_at = models.DateTimeField(null=True, db_index=True) # 軟刪除欄位
調整刪除邏輯
代碼所在目錄位置
mysite/
├── comments/
│ ├── views.py # 刪除邏輯的修改
在 comments/views.py
中新增一個刪除函數,將邏輯改為設置 deleted_at
欄位的值,而非真正刪除資料。
代碼說明
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_POST
from django.utils import timezone
from .models import Comment
@require_POST
def delete(request, id):
# 獲取指定的 Comment
comment = get_object_or_404(Comment, id=id)
# 設置刪除時間
comment.deleted_at = timezone.now()
comment.save()
# 轉址回 Resume 詳細頁
return redirect("resumes:show", id=comment.resume.id)
運作機制
- 獲取資料
get_object_or_404(Comment, id=id)
根據 ID 從資料庫查詢 Comment。若未找到,返回 404 頁面。 - 標記為已刪除
使用timezone.now()
設定deleted_at
欄位為當前時間,作為刪除標記。 - 保存修改
調用.save()
更新資料庫。 - 轉址到詳情頁
調用redirect
,並使用id=comment.resume.id
指定轉址目標的 ID。
深入解析:id=comment.resume.id
的運作機制
comment.resume.id
是什麼?
comment
一個從資料庫中查詢出的Comment
物件,包含與該留言相關的所有欄位資料。comment.resume
透過ForeignKey
關聯模型,comment.resume
獲取與該留言相關聯的Resume
物件。comment.resume.id
取得該Resume
的主鍵 ID,作為redirect
的參數傳遞,用於生成目標頁面的 URL。
運作流程示意圖
1. 瀏覽器發送 POST 請求至 /comments/<comment_id>/delete
2. 後端 `delete` 函數執行:
- 從資料庫獲取 Comment 物件
- 設置 `deleted_at` 時間戳記
- 通過 `comment.resume.id` 獲取 Resume 的 ID
3. 轉址至 /resumes/<resume_id> 詳細頁
常見問題
為什麼可以直接使用 comment.resume
?
- 因為在
Comment
模型中,我們使用了ForeignKey
對Resume
進行了關聯:
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
- 這使得 Django 自動為
Comment
物件添加了一個屬性resume
,用於獲取相關聯的Resume
物件。
修改顯示邏輯
代碼所在目錄位置
mysite/
├── resumes/
│ ├── views.py # 調整顯示邏輯
修改 show
函數
在 resumes/views.py
中,過濾掉已刪除的留言:
def show(request, id):
# 找到指定的 Resume
resume = get_object_or_404(Resume, id=id)
# 僅顯示未刪除的 Comment
comments = resume.comment_set.order_by("-created_at").filter(deleted_at=None)
return render(
request,
"resumes/show.html",
{
"resume": resume,
"comments": comments
}
)
重點解析
comment_set
的使用
Django 自動為ForeignKey
關聯模型創建了一個反向管理器,comment_set
用於訪問與Resume
關聯的所有Comment
。filter(deleted_at=None)
過濾掉已刪除的留言,僅顯示deleted_at
為空的資料。
等號在這裡的語法是 Python 的關鍵字引數(keyword argument)語法。這與函數調用時的關鍵字參數使用方式一致。
對於filter
,它內部會解讀這些關鍵字參數並將它們映射到資料庫的查詢條件。例如,deleted_at=None
翻譯成 SQL 語句時,會變成類似於WHERE deleted_at IS NULL
。- 排序顯示
使用.order_by("-created_at")
按照留言的建立時間倒序排列,保證最新留言顯示在最上方。
更新模板
在 resumes/templates/resumes/show.html
中,留言的顯示邏輯保持不變,仍然按時間排序顯示:
<ul>
{% for comment in comments %}
<li>
{{ comment.content|linebreaksbr }}
<small>{{ comment.created_at }}</small>
<form action="{% url 'comments:delete' comment.id %}" method="post">
{% csrf_token %}
<button type="submit">刪除</button>
</form>
</li>
{% endfor %}
</ul>
為什麼選擇時間戳而非布林值
時間戳的優勢
- 追蹤歷史記錄
可以記錄資料的刪除時間,便於日後進行數據分析或恢復。 - 操作靈活性
相較於布林值(is_deleted
),時間戳允許多層次的操作,例如:- 判定刪除資料的存續時間。
- 自動清理刪除超過一定時間的資料。
SQLite 不支持布林值
- 雖然 SQLite 不直接支持布林值,但可以通過
BooleanField
模擬。然而,DateTimeField
的時間戳記錄更為精確,適合實現軟刪除。
小結
功能完成的成果
- 支持軟刪除邏輯:刪除資料時不會真正移除,而是標記為已刪除。
- 資料顯示邏輯調整:過濾已刪除的留言,保持介面的清晰性。
- 提升資料管理靈活性:通過
deleted_at
時間戳記錄刪除時間,支持多樣化的操作。
適用場景
- 保留歷史資料:例如留言、交易記錄等。
- 後續數據恢復:允許用戶手動或自動恢復刪除的資料。
Django 的軟刪除功能不僅能提升資料的安全性,還能滿足更高層次的業務需求,適合對數據管理有嚴格要求的應用場景。希望本指南能幫助你快速掌握軟刪除的實現方法!