前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >django优雅的实现软删除,支持Admin和DRF的软删除

django优雅的实现软删除,支持Admin和DRF的软删除

原创
作者头像
MicLon
发布2023-03-11 12:45:26
2.1K0
发布2023-03-11 12:45:26
举报
文章被收录于专栏:python-进阶

何为软删除

当你想对数据进行删除的时候,如果使用物理删除,那么数据真的消失了。使用软删除,可以让数据保留,但是不会被真的删除。只是在字段上设置了一个值,表示数据已经被删除。

需要解决的问题

  • DRF
    • 暴露DELETE方法一旦被执行,就需要操作软删除,把is_deleted字段设置为True。
    • 同样的,DRF对外操作的其他接口,如查询,修改操作,就不允许找到已经软删除的数据。
  • 自带的Admin
    • 既然是超级管理后台,那么就允许操作任何数据,包括已经软删除的,而不是列表找不到软删除的数据。
    • 后台执行删除操作的时候,实际上是对数据进行软删除。

简而言之:

  1. drf找不到删除的数据,admin需要全部数据
  2. drf和admin删除数据都是软删除

解决方案

DRF

Django Manager 赋予了 Django的模型(Model)中操作数据库的能力。如果你还未能了解Manager,可以先去官方文档^first查阅。

其实你在项目中无时不刻不在使用Manager,还记得objects吗?也就是如:Book.objects.all()中的objects。有没有想过它到底是什么?

显然,默认的模型Manager并不能解决我们的问题,所以我们需要自定义模型的Manager。

代码语言:python
代码运行次数:0
复制
class ModelManager(models.Manager):
    # 重写get_queryset方法
    def get_queryset(self):
        # 查询出所有的数据,但是不包括软删除的数据
        return super().get_queryset().filter(is_deleted=False)

这样,最简单的自定义模型Manager就完成了。我们需要把它挂载到需要的模型上。

我们格局打开,将拥有is_deleted属性的模型抽离成抽象模型基类,凡是继承此类的都自带这个Manager。

代码语言:python
代码运行次数:0
复制
class BaseModel(models.Model):
    """
    模型基类
    """
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True
        ordering = ['-created_at', '-updated_at']
    # 替换默认的objects
    objects = ModelManager()

不仅如此,刚刚只是过滤了软删除数据,我们还需要将接口删除的操作,进行软删除,而不是真删除。

使用DRF操作删除实际上调用的是mixins.DestroyModelMixindestroy方法,具体执行删除的方法是perform_destroy

所以下一步我们需要重写这个perform_destroy方法。

回到视图层(views),重写:

代码语言:python
代码运行次数:0
复制
# views.py
class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()

    def perform_destroy(self, instance):
        instance.is_deleted = True
        instance.save(update_fields=['is_deleted'])

OK,在DRF层面上,我们解决了软删除的处理。即:

  1. drf找不到删除的数据
  2. drf执行删除是软删除

Admin

首先再刚刚代码基础上,我们启用Admin,进入后台看看效果。

可以发现,由于模型Manager的加持,直接把is_deleted的数据一并过滤了。但是我们并不想如此。

所以第一反应,就是去注册模型的地方,重写模型的查询。

代码语言:python
代码运行次数:0
复制
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    pass

这是原来的模型注册,笔者进入admin.ModelAdmin中翻阅源码,发现get_queryset方法是执行获取查询的,那么把它重写了。

那……应该重写成什么?由于我们已经在模型层通过Manager直接改变了最初的数据过滤后的样子,这里怎么重写也是无事于补的。

于是我在想,那就在定义一个模型管理器,在Admin中使用这个管理器不就好了?

代码语言:python
代码运行次数:0
复制
class ModelAdminManager(models.Manager):
    pass

class BaseModel(models.Model):
    ...

    objects = ModelManager()
    objects_all = ModelAdminManager()
    # 如果仅仅是空的Manager, 可以直接写成objects_all = models.Manager()

回到Admin注册中,重写get_queryset

代码语言:python
代码运行次数:0
复制
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):

    def get_queryset(self, request):
        return Book.objects_all.all()

剩下最后一个问题,在admin后台执行删除的时候,是软删除。当下如果执行删除是真正的物理删除数据。

此时问题就变得简单,Manager进阶用法中,可以自定义其QuerySet^second

代码语言:python
代码运行次数:0
复制
class DeleteQuerySet(models.QuerySet):
    def delete(self):
        self.update(is_deleted=True)

# 修改原来的objects_all
class BaseModel(models.Model):
    ...

    objects = ModelManager()
    # objects_all = ModelAdminManager.from_queryset(DeleteQuerySet)()
    # or
    objects_all = models.Manager.from_queryset(DeleteQuerySet)()

改完后在admin进行删除操作:

OK,在Admin层面上,我们解决了软删除的处理。即:

  1. admin能够展示被软删除的数据
  2. admin执行删除是软删除

完整代码:

代码语言:python
代码运行次数:0
复制
#admin.py
from apps.book.models import Book


@admin.register(Book)
class BookAdmin(admin.ModelAdmin):

    list_display = ('title', 'is_deleted')

    def get_queryset(self, request):
        return Book.objects_all.all()
代码语言:python
代码运行次数:0
复制
# models.py
from django.db import models


# Create your models here.
class DeleteQuerySet(models.QuerySet):
    def delete(self):
        self.update(is_deleted=True)


class ModelManager(models.Manager):
    _queryset_class = DeleteQuerySet

    def get_queryset(self):
        return self._queryset_class(
            model=self.model, using=self._db, hints=self._hints
        ).filter(is_deleted=False)


class ModelAdminManager(models.Manager):
    pass
    # _queryset_class = DeleteQuerySet


class BaseModel(models.Model):
    """
    BaseModel
    """
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False, verbose_name='软删除')

    class Meta:
        abstract = True
        ordering = ['-created_at', '-updated_at']

    objects = ModelManager()
    # objects_all = ModelAdminManager.from_queryset(DeleteQuerySet)()
    objects_all = models.Manager.from_queryset(DeleteQuerySet)()
代码语言:python
代码运行次数:0
复制
# views.py
from rest_framework.viewsets import ModelViewSet


class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def perform_destroy(self, instance):
        instance.is_deleted = True
        instance.save(update_fields=['is_deleted'])

参考:

Manager

Manager QuerySet

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需要解决的问题
  • 解决方案
    • DRF
      • Admin
      • 完整代码:
      • 参考:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档