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

Override django's model delete method for bulk deletion

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

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

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

Keep in mind that this will, whenever possible, be executed purely in SQL, and so the delete() methods of individual object instances will not necessarily be called during the process. If you’ve provided a custom delete() method on a model class and want to ensure that it is called, you will need to “manually” delete instances of that model (e.g., by iterating over a QuerySet and calling delete() on each object individually) rather than using the bulk delete() method of a QuerySet.

如果您想覆盖 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

Like pre_delete, but sent at the end of a model’s delete() method and a queryset’s delete() method.

It does:

The delete() method does a bulk delete and does not call any delete() methods on your models. It does, however, emit the pre_delete and post_delete signals for all deleted objects (including cascaded deletions).

要使其生效,您可以重写 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)

我相信此问题已在 docs

中得到解决

上面写着:

Overridden model methods are not called on bulk operations

Note that the delete() method for an object is not necessarily called when deleting objects in bulk using a QuerySet or as a result of a cascading delete. To ensure customized delete logic gets executed, you can use pre_delete and/or post_delete signals.

Unfortunately, there isn’t a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.

正如上面文档中所建议的,我认为更好的解决方案是使用 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 是一种更简洁的方法。