覆盖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 IntegrityError
Django Admin Cookbook-40如何为Django Admin覆盖保存操作