在 Django 中實現軟刪除功能:Fat Model, Thin View
更新日期: 2024 年 12 月 3 日
本文為 Django 高階教學,第 9 篇:
- 使用 Poetry 管理 Django 專案:完整指南
- 使用 ModelForm 優化 Django 表單與資料庫操作
- Django 留言功能實作指南:從模型設計到應用完成
- Django 留言功能新增與視圖互動詳解
- 在 Django 中實現留言檢索與排序功能:完整指南
- 在 Django 中實現留言換行與顯示時間功能
- 在 Django 中實現留言刪除功能:完整指南
- 在 Django 中實現軟刪除功能:完整指南
- 在 Django 中實現軟刪除功能:Fat Model, Thin View 👈 所在位置
建議閱讀本文前,先閱讀完 Django 進階教學 系列文, 若已有基本認識,可前往閱讀 Django 與前端框架教學 系列
在軟刪除的設計中,遵循「Fat Model, Thin View」的設計原則可以提升系統的可維護性和可讀性:
- Fat Model:將查詢過濾、排序等邏輯集中在模型中進行處理,減少視圖中的業務邏輯。
- Thin View:視圖僅負責資料的傳遞,避免直接處理業務邏輯。
本文將以軟刪除功能為例,介紹如何在 Django 中設計符合上述原則的高效解決方案。
自定義模型刪除邏輯
軟刪除的核心是保留資料庫中的記錄,並標記資料為「已刪除」。
這可以通過在模型中新增一個 deleted_at
欄位並覆寫 delete
方法來實現。
修改模型
目前所在目錄位置:comments/models.py
以下是修改後的 Comment
模型代碼:
from django.db import models
from resumes.models import Resume
from django.utils import timezone
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)
def delete(self):
"""覆寫刪除方法,更新刪除時間"""
self.deleted_at = timezone.now()
self.save()
邏輯解析
deleted_at
欄位:記錄刪除的時間。若為null
,則資料未刪除。db_index=True
:為欄位新增索引,加快查詢速度。- 覆寫
delete
方法:當呼叫刪除時,將deleted_at
設置為當前時間,而非直接從資料庫移除記錄。
修改刪除邏輯的視圖
更新 views.py
中的刪除功能,使其調用覆寫後的 delete
方法。
更新 delete
函數
目前所在目錄位置:comments/views.py
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_POST
from .models import Comment
@require_POST
def delete(request, id):
# 獲取指定的 Comment
comment = get_object_or_404(Comment, id=id)
# 調用覆寫後的刪除方法
comment.delete()
# 重定向回 Resume 詳細頁
return redirect("resumes:show", id=comment.resume.id)
邏輯解析
get_object_or_404
:檢查指定的id
是否存在,若不存在則返回 404。comment.delete()
:調用覆寫後的delete
方法,更新刪除時間。redirect
:將用戶導回對應的 Resume 詳細頁。
自定義 Manager 優化查詢
Django 提供的經理人(Manager)可用來自定義模型的查詢邏輯。
透過自定義經理人,我們可以為 Comment
模型新增過濾條件,避免在視圖中重複定義。
定義自定義經理人
目前所在目錄位置:comments/models.py
這是一個自定義的模型管理器(Manager),通過覆寫 get_queryset()
方法,定義了查詢的預設行為:
class CommentManager(models.Manager):
def get_queryset(self):
"""僅返回未刪除的評論,並按 ID 倒序排列"""
return super().get_queryset().order_by("-id").filter(deleted_at=None)
get_queryset()
:
- 覆寫了 Django 默認的查詢集方法。
- 該方法返回一個新的查詢集,帶有自定義的篩選條件和排序。
- 在這裡,返回的查詢集只包含
deleted_at
為None
的記錄(未刪除的評論),並按 ID 降序排列。
super().get_queryset()
:
- 調用基類的
get_queryset()
方法,獲取最原始的查詢集(包括所有記錄)。 - 在此基礎上進一步添加篩選和排序條件。
整合到模型
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)
objects = CommentManager() # 使用自定義的 Manager
def delete(self):
self.deleted_at = timezone.now()
self.save()
objects
是 Django 模型的默認管理器:
- Django 模型中的查詢都是通過
objects
屬性進行的,比如Comment.objects.all()
或Comment.objects.filter(...)
。 - 默認情況下,
objects
使用的是models.Manager
,即基礎管理器,返回全部數據,無篩選或特殊邏輯。
自定義 CommentManager
作為默認 Manager:
- 這段代碼將
CommentManager
賦值給objects
,用於取代默認的管理器。 - 這樣一來,當使用
Comment.objects
查詢時,就會自動應用自定義的邏輯(篩選掉已刪除的評論,並按 ID 倒序排序)。
簡化視圖中的查詢邏輯
由於經理人自動過濾已刪除資料,我們可以將視圖中的查詢邏輯大幅簡化。
修改前的視圖
目前所在目錄位置:resumes/views.py
def show(request, id):
resume = get_object_or_404(Resume, id=id)
comments = resume.comment_set.order_by("-created_at").filter(deleted_at=None)
return render(request, "resumes/show.html", {"resume": resume, "comments": comments})
修改後的視圖
def show(request, id):
resume = get_object_or_404(Resume, id=id)
comments = resume.comment_set.all() # 自動應用經理人的過濾條件
return render(request, "resumes/show.html", {"resume": resume, "comments": comments})
邏輯解析
- 自動過濾條件:透過
CommentManager
,自動過濾掉deleted_at
不為null
的記錄。 - 清晰的查詢語法:視圖僅需調用
all()
即可獲取所需資料。
Fat Model, Thin View 的實踐價值
透過自定義模型方法和經理人,我們可以實現「Fat Model, Thin View」的設計模式:
- 集中業務邏輯:將查詢和數據處理集中在模型中,增強邏輯的可復用性。
- 簡化視圖邏輯:視圖專注於資料傳遞,避免繁瑣的業務邏輯,提升可讀性。
總結與最佳實踐
- 覆寫
delete
方法:- 在模型中實現刪除邏輯,通過
deleted_at
標記記錄的刪除時間。
- 在模型中實現刪除邏輯,通過
- 自定義經理人:
- 將常用的過濾條件和排序邏輯封裝在經理人中,減少重複代碼。
- 簡化查詢語法:
- 視圖中使用
Model.objects.all()
即可獲取所需資料,減少業務邏輯耦合。
- 視圖中使用
- 遵循 MVC 設計模式:
- 實現「Fat Model, Thin View」的設計原則,提升代碼結構的清晰度和可維護性。
透過這些實踐,新手可以快速掌握 Django 軟刪除的設計技巧,並將其應用於更廣泛的場景中。