【问题标题】:Soft deleting objects in djangodjango中的软删除对象
【发布时间】:2018-10-17 05:56:02
【问题描述】:

我正在尝试为 Django 模型实现一个通用的软删除模式。

模型有一个 is_deleted 字段,它将删除的对象保留在 DB 中,但出于所有实际目的将它们隐藏:应遵循所有常规的级联规则等,但实际删除除外。然而,管理应用程序应该仍然能够处理已删除的对象,以便擦除(肯定将它们丢弃)或恢复它们。 (见下面的代码)

问题:这会破坏级联。我期望发生的事情是通过我在模型和自定义查询集上覆盖的方法级联发生的。实际发生的是它们被默认的查询集/管理器绕过,而默认的查询集/管理器也恰好使用了一个快速的 _raw_delete 内部 API。因此,级联删除不会发生,或者如果我在我的模型上调用 super().delete() 方法(然后是 save()),则会对相关对象执行标准删除。

我已经尝试了Cascading Delete w/ Custom Model Delete Method 中的建议,但这会严重破坏事情 - 除了它提倡使用已弃用的 use_for_related_fields 管理器属性。

我开始认为,如果不影响 Django 隐私的主要分离,我想要实现的目标是不可能的 - 很奇怪,因为这种软删除行为是许多 DBMS 情况下的标准模式。

这就是我现在的位置:

  • 我为具有 is_deleted 字段的对象创建了一个自定义管理器和查询集:

    from django.db import models
    from django.db.models.query import QuerySet
    
    
    class SoftDeleteQuerySet(QuerySet):
        #https://stackoverflow.com/questions/28896237/override-djangos-model-delete-method-for-bulk-deletion
        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)
    
  • 添加了一个基本模型来使用它:

    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
    
  • 以及处理擦除、恢复等的管理类:

    # 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 信号。
  • 有一个库应该实现级联软删除:django-softdelete。您可以看一下,也许会找到一些灵感或按原样使用它。免责声明:我不知道它的质量或维护状态。
  • @Chiefir 这就是我现在正在探索的东西——但从表面上看,因为我从不调用基类删除,我从不触发信号——除非我自己发送它们,但我可以做到仅适用于调用我的方法的对象...
  • @Frax 我现在正在看它,它确实是让我开始做这件事的代码的后代,它可能正是我想要的。在让编辑器出来之前我应该​​看起来更好。
  • 另一个图书馆,被两勺书推荐:django-model-utils

标签: django django-models


【解决方案1】:

也许你可以使用django-paranoid

类似于rails的acts_as_paranoid,易于使用。

您只需要使用 ParanoidModel 扩展至模型。

要查看已删除的对象,您可以使用 objects_with_deleted:

MyModel.objects_with_deleted.last()

如果你想硬删除一个对象,你只应该使用 True 参数:

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

【讨论】:

    【解决方案2】:

    django-softdelete 是一个为 Django 实现软删除的库,具有级联功能。它还存储变更集,并允许恢复删除(例如恢复整个级联)。

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

    【讨论】:

    • 如何使用 django-softdelete 取消删除和查询已删除的项目?
    • 我不确定,我从未使用过它。通过快速查看代码,我假设您可以执行MyModel.objects.all_with_deleted().filter(foo='bar').undelete()MyModel.objects.get(pk=12).undelete() 之类的操作(.get() 专门用于pk.get(id=12) 显然不起作用)。在此处查看 SoftDeleteQuerySetSoftDeleteManager 类:github.com/scoursen/django-softdelete/blob/master/softdelete/…
    • 如果我想为所有模型全部删除它怎么办?
    • 您似乎可以使用MyModel.objects.deleted_set() 获取任何模型的已删除对象。我想您可以在 SoftDeleteRecord 上进行一些查询以获取所有表中的所有已删除对象,但我真的不明白您为什么要这样做而不是查询特定模型。
    • 我想在 mac 或 windows 上制作一个像 trashtrash bin 一样的垃圾应用程序。 django-softdelete 中没有这样的 SoftDeleteRecord
    猜你喜欢
    • 2019-02-21
    • 1970-01-01
    • 1970-01-01
    • 2011-03-08
    • 2023-02-14
    • 2012-02-26
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    相关资源
    最近更新 更多