在Django中,如何删除特定实例时所有相关对象?

2 投票
3 回答
13767 浏览
提问于 2025-04-16 22:51

我最开始尝试重写 delete() 方法,但这对 QuerySet 的批量删除方法不起作用。这个问题应该和 pre_delete 信号有关,但我搞不清楚。我的代码如下:

def _pre_delete_problem(sender, instance, **kwargs):
    instance.context.delete()
    instance.stat.delete()

但是这个方法似乎被无限调用,导致程序陷入死循环。有人能帮帮我吗?

3 个回答

1

如果你想快速删除一个实例以及它所有相关的对象,还有那些相关对象的对象,等等,而不需要更改数据库的结构,你可以这样做 -

def recursive_delete(to_del):
    """Recursively delete an object, all of its protected related
    instances, those instances' protected instances, and so on.
    """
    from django.db.models import ProtectedError
    while True:
        try:
            to_del_pk = to_del.pk
            if to_del_pk is None:
                return  # unsaved object
            to_del.delete()
            print(f"Deleted {to_del.__class__.__name__} with pk {to_del_pk}: {to_del}")
        except ProtectedError as e:
            for protected_ob in e.protected_objects:
                recursive_delete(protected_ob)

不过要小心哦!

我建议只在调试时使用这个方法,比如在一次性的脚本中(或者在命令行里),并且是在我不介意清空的测试数据库上。因为关系并不总是很明显,如果某些东西被保护了,可能是有原因的。

1

在你的情况下,使用post_delete信号而不是pre_delete可以解决无限循环的问题。因为外键的默认删除方式是级联删除(cascade),这样用pre_delete的逻辑就会导致instance.context对象调用instance的删除,这样又会再调用instance.context,结果就一直循环下去了。

使用这种方法:

def _post_delete_problem(sender, instance, **kwargs):
    instance.context.delete()
    instance.stat.delete()

post_delete.connect(_post_delete_problem, sender=Foo)

可以完成你想要的清理工作。

7

如果一个类里面有外键(或者说相关的对象),默认情况下这些外键会被一起删除,就像数据库里的 DELETE CASCADE 操作一样。

你可以在定义 ForeignKey 的时候,通过 on_delete 参数来改变这个默认行为,但默认设置是 CASCADE。你可以在 这里 查看相关文档。

现在,pre_delete 信号是可以工作的,但如果你使用批量删除,它不会调用 delete() 方法,因为批量删除不是逐个对象删除的。

撰写回答