在Django中软删除对象

16
我正在尝试为Django模型实现一般的软删除模式。在此模式下,模型会有一个is_deleted字段,该字段将已删除对象保留在数据库中,但从所有实际目的中隐藏它们:应遵循所有级联等常规规则,除了实际删除。但是,管理员应该仍然能够使用已删除的对象,以便于擦除(彻底删除)或还原它们。 (参见下面的代码)
问题:这将导致级联断裂。我原本希望发生的级联是通过我在模型和自定义查询集上覆盖的方法发生的。实际发生的是,它们被默认查询集/管理器绕过,后者还偶尔使用快速的_raw_delete内部API。因此,要么不发生级联删除,要么如果我在模型上调用super().delete()方法(并在此之后保存(save())),则会对相关对象执行标准删除。
我已经尝试了Cascading Delete w/ Custom Model Delete Method中建议的方法,但这会使事情变得非常糟糕 - 除此之外,它倡导使用已弃用的use_for_related_fields管理器属性。
我开始觉得,如果没有影响Django的私有部分,则无法实现我想要实现的内容。这很奇怪,因为软删除行为在许多DBMS情况下都是一种标准模式。
这就是我目前的状态:
  • I created a custom manager and query set for objects with a is_deleted field:

    from django.db import models
    from django.db.models.query import QuerySet
    
    
    class SoftDeleteQuerySet(QuerySet):
        #https://dev59.com/Kl4b5IYBdhLWcg3wnS0e
        def __init__(self,*args,**kwargs):
            return super(self.__class__,self).__init__(*args,**kwargs)
    
        def delete(self,*args,**kwargs):
            for obj in self: obj.delete()
    
    #http://codespatter.com/2009/07/01/django-model-manager-soft-delete-how-to-customize-admin/
    # but use get_queryset,  not get_query_set !!!
    class SoftDeleteManager(models.Manager):
        """ Use this manager to get objects that have a is_deleted field """
        def get_queryset(self,*args,**kwargs):
            return SoftDeleteQuerySet(model=self.model, using=self._db, hints=self._hints).filter(is_deleted=False)
    
        def all_with_deleted(self,*args,**kwargs):
            return SoftDeleteQuerySet(model=self.model, using=self._db, hints=self._hints).filter()
    
        def deleted_set(self,*args,**kwargs):
            return SoftDeleteQuerySet(model=self.model, using=self._db, hints=self._hints).filter(is_deleted=True)
    
        def get(self, *args, **kwargs):
            """ if a specific record was requested, return it even if it's deleted """
            return self.all_with_deleted().get(*args, **kwargs)
    
        def filter(self, *args, **kwargs):
            """ if pk was specified as a kwarg, return even if it's deleted """
            if 'pk' in kwargs:
                return self.all_with_deleted().filter(*args, **kwargs)
            return self.get_queryset().filter(*args, **kwargs)
    
  • Added a base model to use it:

    class SoftDeleteModel(models.Model):
    
        objects=SoftDeleteManager()
        is_deleted   = models.BooleanField(default=False, verbose_name="Is Deleted")
    
        def delete(self,*args,**kwargs):
            if self.is_deleted : return
            self.is_deleted=True
            self.save()
    
    
        def erase(self,*args,**kwargs):
            """
            Actually delete from database.
            """
            super(SoftDeleteModel,self).delete(*args,**kwargs)
    
        def restore(self,*args,**kwargs):
            if not self.deleted: return
            self.is_deleted=False
            self.save()
    
    
        def __unicode__(self): return "%r %s of %s"%(self.__class__,str(self.id))
    
        class Meta:
            abstract = True
    
  • And admin classes to handle the erasure, restore, etc:

    # for definitive deletion of models in admin
    def erase_model(modeladmin,request,queryset):
        """
        Completely remove models from db
        """
        for obj in queryset:
            obj.erase(user=request.user)
    
    def restore_model(modeladmin,request,queryset):
        """
        Restore a softdeletd model set 
        """
        for obj in queryset:
            obj.restore(user=request.user)
    
    #http://codespatter.com/2009/07/01/django-model-manager-soft-delete-how-to-customize-admin/        
    # but the method is now get_queryset.
    
    class SoftDeleteAdmin(admin.ModelAdmin):
        list_display = ('pk', '__unicode__', 'is_deleted',)
        list_filter = ('is_deleted',)
        actions=[erase_model, restore_model]
    
        def get_queryset(self, request):
            """ Returns a QuerySet of all model instances that can be edited by the
            admin site. This is used by changelist_view. """
            # Default: qs = self.model._default_manager.get_query_set()
            qs = self.model._default_manager.all_with_deleted()
            #TR()
            # TODO: this should be handled by some parameter to the ChangeList.
            ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
            if ordering:
                qs = qs.order_by(*ordering)
            return qs
    
        queryset=get_queryset
    

有什么想法?

编辑:所有这些的要点(除了更彻底地搜索打包解决方案 :-))是覆盖删除并正确执行可以完成,但它并不容易,也不适合弱者。我将使用的软件包 - django-softdelete,它是从http://codespatter.com/2009/07/01/django-model-manager-soft-delete-how-to-customize-admin/中提取的起点演变而来,使用通过Contenttype API计算的ChangeSet。

除此之外,在几种情况下,并不会调用覆盖的delete()(基本上,每次组删除发生时,django都会采取跳过model.delete()的快捷方式)。

在我看来,这是一个设计失误。如果覆盖它需要如此多的大脑爆炸,那么model.delete()实际上应该是model._delete()。


也许Django信号可以在某种程度上帮助您?特别是pre_deletepost_delete信号。 - Chiefir
2
有一个库被认为是实现级联软删除的:django-softdelete。你可以看一下,也许能找到一些灵感或直接使用它。免责声明:我不知道它的质量或维护状态。 - Frax
1
另一个由《Two Scoops》书籍推荐的库:django-model-utils - João Amaro
@JoãoAmaro - 感谢你提醒我 - 那个库包含了一些我本来要自己编写的东西(尽管我可能会放弃他们的软删除实现)。 - Alien Life Form
@AlienLifeForm,我已经添加了答案。 - Frax
显示剩余3条评论
3个回答

4

django-softdelete是一个实现Django软删除及级联的库。它还存储更改集,并允许撤销删除(例如,撤销整个级联)。

我不确定它的维护状态和质量如何,但它至少可以作为灵感,如果不是解决方案本身。


如何使用django-softdelete进行未删除和查询已被删除的项目? - Ali Husham
1
我不确定,我从未使用过它。通过快速查看代码,我认为您可以执行类似于MyModel.objects.all_with_deleted().filter(foo='bar').undelete()MyModel.objects.get(pk=12).undelete()的操作(.get()专门与pk一起使用,.get(id=12)显然不起作用)。请参阅此处的SoftDeleteQuerySetSoftDeleteManager类:https://github.com/scoursen/django-softdelete/blob/master/softdelete/models.py - Frax
如果我想获取所有模型的所有已删除的ITSM,该怎么办? - Ali Husham
1
似乎你可以使用 MyModel.objects.deleted_set() 获取任何模型的已删除对象。我猜你可以在 SoftDeleteRecord 上进行一些查询,以获取所有表中的已删除对象,但我真的不明白为什么你要这样做,而不是查询特定的模型。 - Frax
我想制作一个类似于Mac或Windows上的“垃圾箱”应用程序。在“django-softdelete”中没有这样的“SoftDeleteRecord”类。 - Ali Husham

4

也许你可以使用django-paranoid

它类似于Rails中的acts_as_paranoid,易于使用。

只需在模型中扩展ParanoidModel即可。

要检索已删除的对象,您可以使用objects_with_deleted管理器:

MyModel.objects_with_deleted.last()

如果您想对一个对象执行硬删除,请使用True参数:

m = MyModel.objects.last()
m.delete(True)

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接