在 Django 中實現軟刪除功能:Fat Model, Thin View

更新日期: 2024 年 12 月 3 日

在軟刪除的設計中,遵循「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_atNone 的記錄(未刪除的評論),並按 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」的設計模式:

  • 集中業務邏輯:將查詢和數據處理集中在模型中,增強邏輯的可復用性。
  • 簡化視圖邏輯:視圖專注於資料傳遞,避免繁瑣的業務邏輯,提升可讀性。

總結與最佳實踐

  1. 覆寫 delete 方法
    • 在模型中實現刪除邏輯,通過 deleted_at 標記記錄的刪除時間。
  2. 自定義經理人
    • 將常用的過濾條件和排序邏輯封裝在經理人中,減少重複代碼。
  3. 簡化查詢語法
    • 視圖中使用 Model.objects.all() 即可獲取所需資料,減少業務邏輯耦合。
  4. 遵循 MVC 設計模式
    • 實現「Fat Model, Thin View」的設計原則,提升代碼結構的清晰度和可維護性。

透過這些實踐,新手可以快速掌握 Django 軟刪除的設計技巧,並將其應用於更廣泛的場景中。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *