覆盖Django的模型删除方法以进行批量删除

30

我正在重写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)

当我从管理员中删除单个对象时,这很好运行,但是当我选择多个对象并将它们删除时,似乎不会调用此函数。我已经搜索了一段时间,但没有找到正确的关键字来获取答案,也没有官方文档涉及该主题。

4个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
52

这个方法会执行:

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

要使此操作生效,您可以在 QuerySet 上覆盖 delete 方法,然后将该 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)

我想我在文档中读到过,但不知道它适用于管理员的批量删除。无论如何,我很喜欢你的解决方案,非常感谢! - Kilian Perdomo Curbelo
这种方法有一个警告,现在你可以执行:Image.objects.delete(),这将清空你的表。 - HTF

8

Queryset的Delete方法直接在数据库上操作,不会调用 Model.delete() 方法。参考 docs

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

如果您想覆盖 Django 管理界面的默认行为,则可以编写自定义的 delete 操作:

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

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

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

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

1
谢谢你的解决方案。我会尝试一下,但是我会将GwynBleidD的答案标记为已接受,因为我更喜欢他的方法(看起来更简洁)。 - Kilian Perdomo Curbelo

8
我认为这个问题在文档中已经得到解决。 其中提到: 重写的模型方法不会在批量操作中被调用。 需要注意的是,当使用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实现Signal之前编写的,但是我认为使用Signal是更清晰的方法。

信号从不让我失望。我总是更喜欢这种解决方案。 - Ebram Shehata

0

被接受的答案可能并不适用于每个人。我在Django 3.2上无法使其工作,但这可能仅是因为我已经有了自定义管理器,并且不确定是否可以将我的自定义与models.Manager的自定义组合到一起。

我发现在模型的管理员上覆盖delete_queryset(在Django 2.1+中可用,如用户Kushan Gunasekera在另一个相关的SO问题中所述的this thorough and fully-illustrated answer)非常快速和简便。


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