Django级联删除反向外键
Posted
技术标签:
【中文标题】Django级联删除反向外键【英文标题】:Django cascade delete on reverse foreign keys 【发布时间】:2012-09-21 05:51:18 【问题描述】:Django 展示了如何在文档中使用外键设置或覆盖级联删除。
model = models.ForeignKey(MyModel, null = True, on_delete = models.SET_NULL)
但是,如果我们反过来想要这种效果呢?如果我们希望删除 fk 模型导致该模型被删除怎么办?
谢谢
【问题讨论】:
【参考方案1】:有一个非常微妙的实现点,我想我应该添加到这个讨论中。
假设我们有两个模型,其中一个通过外键引用另一个,如下所示:
class A(models.Model):
x = models.IntegerField()
class B(models.Model):
a = models.ForeignKey(A, null=True, blank=True)
现在如果我们删除 A 的一个条目,级联行为将导致 B 中的引用也被删除。
到目前为止,一切都很好。现在我们要扭转这种行为。人们提到的明显方法是使用删除期间发出的信号,所以我们去:
def delete_reverse(sender, **kwargs):
if kwargs['instance'].a:
kwargs['instance'].a.delete()
post_delete.connect(delete_reverse, sender=B)
这似乎是完美的。它甚至可以工作!如果我们删除一个 B 条目,对应的 A 也会被删除。
问题是这有一个循环行为导致异常:如果我们删除A的一个项目,由于默认的级联行为(我们想要保留),B的相应项目也将被删除,这将导致 delete_reverse 被调用,它会尝试删除已删除的项目!
诀窍是,您需要异常处理才能正确实现反向级联:
def delete_reverse(sender, **kwargs):
try:
if kwargs['instance'].a:
kwargs['instance'].a.delete()
except:
pass
此代码将适用于任何一种方式。我希望它可以帮助一些人。
【讨论】:
顺便说一句,这可能不会导致无限递归,对吧? 为什么不首先使用 pre_delete 信号?【参考方案2】:我不认为您正在查看的功能是 ORM 或数据库概念。您只想在删除某些内容时执行回调。
所以使用post_delete
signal 并在那里添加你的回调处理程序
from django.db.models.signals import post_delete
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(post_delete, sender=MyModel)
def my_post_delete_callback(sender, **kwargs):
#Sender is the model which when deleted should trigger this action
#Do stuff like delete other things you want to delete
#The object just deleted can be accessed as kwargs[instance]
【讨论】:
@claytond 虽然您对delete
没有被调用是正确的,正如文档中引用的那样,但我找不到提到不能保证调用 post_delete
信号。事实上,我只是在 django 2.0.4 中运行了一个简单的测试,显示 MyModel.delete 确实没有被调用,但是我的 pre_delete
和 post_delete
信号回调被调用了
@Andy 你是对的。我遇到了批量update
的问题(“update() 在 SQL 级别进行更新,因此不会在模型上调用任何 save() 方法,也不会发出 pre_save 或 post_save 信号”)。我没有意识到批量删除的实现方式不同。我正在删除原始评论以确保它不会混淆。以上是关于Django级联删除反向外键的主要内容,如果未能解决你的问题,请参考以下文章