覆盖django的model delete方法进行批量删除

Posted

技术标签:

【中文标题】覆盖django的model delete方法进行批量删除【英文标题】:Override django's model delete method for bulk deletion 【发布时间】:2015-05-07 21:46:57 【问题描述】:

我正在重写 Django 的模型删除方法,以便删除磁盘中图像字段的孤立文件,如下所示:

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)

当我从管理员中删除单个对象时,这工作正常,但是当我选择多个对象并删除它们时,这似乎没有被调用。我已经在谷歌上搜索了一段时间,但没有找到正确的关键字来获得答案,官方文档似乎也没有谈论这个主题。

【问题讨论】:

【参考方案1】:

查询集的删除方法直接作用于数据库。它不调用Model.delete() 方法。来自docs:

请记住,只要有可能,这将纯粹在 SQL 中执行,因此在此过程中不一定会调用单个对象实例的 delete() 方法。如果您在模型类上提供了自定义 delete() 方法并希望确保调用它,则需要“手动”删除该模型的实例(例如,通过迭代 QuerySet 并在每个对象单独)而不是使用 QuerySet 的批量 delete() 方法。

如果你想覆盖 Django 管理界面的默认行为,你可以编写一个自定义的delete 动作:

https://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/

另一种方法是覆盖post_delete(或pre_delete)信号而不是delete方法:

https://docs.djangoproject.com/en/dev/ref/signals/#django.db.models.signals.post_delete

类似于 pre_delete,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送。

【讨论】:

感谢您的解决方案。我会试一试,但我将 GwynBleidD 的答案标记为已接受,因为我更喜欢他的方法(看起来更干净)。【参考方案2】:

It does:

delete() 方法执行批量删除,并且不会在模型上调用任何 delete() 方法。但是,它会为所有已删除的对象(包括级联删除)发出 pre_delete 和 post_delete 信号。

为此,您可以覆盖QuerySet 上的删除方法,然后将该QuerySet 应用为经理:

class ImageQuerySet(models.QuerySet):

    def delete(self, *args, **kwargs):
        for obj in self:
            obj.img.delete()
        super(ImageQuerySet, self).delete(*args, **kwargs)

class Image(models.Model):
    objects = ImageQuerySet.as_manager()
    img = models.ImageField(upload_to=get_image_path)
    ...
    def delete(self, *args, **kwargs):
        self.img.delete()
        super(Image, self).delete(*args, **kwargs)

【讨论】:

我认为我在文档中阅读了该内容,但不知道它适用于管理员的批量删除。无论如何,我喜欢你的解决方案,非常感谢! 这种方法有一个注意事项,现在你可以这样做:Image.objects.delete(),这会毁掉你的桌子。【参考方案3】:

我相信这个问题在docs中得到了解决

上面写着:

在批量操作中不调用被覆盖的模型方法

请注意,当使用 QuerySet 批量删除对象或作为级联删除的结果时,不一定会调用对象的 delete() 方法。为确保执行自定义删除逻辑,您可以使用 pre_delete 和/或 post_delete 信号。

不幸的是,在批量创建或更新对象时没有解决方法,因为没有调用 save()、pre_save 和 post_save。

正如上述文档中所建议的,我认为更好的解决方案是使用post_delete 信号,如下所示:

from django.db.models.signals import post_delete
from django.dispatch import receiver

class Image(models.Model):
    img = models.ImageField(upload_to=get_image_path)
    ...

@receiver(post_delete, sender=Image)
def delete_image_hook(sender, instance, using, **kwargs):
    instance.img.delete()

与覆盖delete 方法不同,delete_image_hook 函数也应在批量删除和级联删除时调用。以下是有关使用 Django 信号的更多信息:https://docs.djangoproject.com/en/1.11/topics/signals/#connecting-to-signals-sent-by-specific-senders

注意以前的答案: 一些较早的帖子建议覆盖 QuerySet 的 delete 方法,这可能会影响性能和其他意外行为。也许这些答案是在 Django 的 Signals 实现之前编写的,但我认为使用 Signals 是一种更清洁的方法。

【讨论】:

信号永远不会让我失望。我一直更喜欢这个解决方案。

以上是关于覆盖django的model delete方法进行批量删除的主要内容,如果未能解决你的问题,请参考以下文章

如何以 django 模型形式覆盖保存方法

此覆盖保存方法的 Django IntegrityError

Django Admin Cookbook-40如何为Django Admin覆盖保存操作

Django Admin 覆盖显示的字段值

Django 将 self 传递给 models.SET on_delete

Django 信号与覆盖保存方法