Django模型通常可以充分处理ON DELETE CASCADE行为(以适用于本机不支持它的数据库的方式。)
但是,我在努力发现在不适合的情况下覆盖此行为的最佳方法是什么,例如在以下方案中:
ON DELETE RESTRICT(即,如果对象有子记录,则阻止删除)
ON DELETE SET NULL(即不删除子记录,但将其父键设置为NULL而不是断开关系)
删除记录时更新其他相关数据(例如删除上传的图像文件)
以下是我所知道的实现这些目标的潜在方法:
覆盖模型的delete()
方法。虽然这种方法有效,但是当通过QuerySet
删除记录时,它会被回避。此外,必须重写每个模型的delete()
以确保永远不会调用Django的代码,并且无法调用super()
,因为它可能使用QuerySet
来删除子对象。
使用信号。这似乎是理想的,因为在直接删除模型或通过QuerySet删除时会调用它们。但是,不可能阻止删除子对象,因此无法实现ON CASCADE RESTRICT或SET NULL。
使用正确处理此问题的数据库引擎(在这种情况下Django会做什么?)
等到Django支持它(直到那时还有bug ...)
似乎第一种选择是唯一可行的选择,但它很难看,用洗澡水将宝宝扔掉,并且在添加新模型/关系时可能会遗漏某些东西。
我错过了什么吗?有什么建议吗?
答案 0 :(得分:76)
对于那些遇到此问题的人来说,现在还有一个内置的Django 1.3解决方案。
请参阅文档中的详细信息django.db.models.ForeignKey.on_delete感谢Code of Fragments of code网站的编辑指出。
最简单的场景只需添加到模型FK字段定义中:
on_delete=models.SET_NULL
答案 1 :(得分:6)
Django只模拟CASCADE行为。
根据Django Users Group的discussion,最合适的解决方案是:
答案 2 :(得分:4)
好的,以下是我已经解决的解决方案,尽管它远非令人满意。
我为我的所有模型添加了一个抽象基类:
class MyModel(models.Model):
class Meta:
abstract = True
def pre_delete_handler(self):
pass
信号处理程序捕获此模型的子类的所有pre_delete
事件:
def pre_delete_handler(sender, instance, **kwargs):
if isinstance(instance, MyModel):
instance.pre_delete_handler()
models.signals.pre_delete.connect(pre_delete_handler)
在我的每个模型中,如果存在子记录,我会通过从ON DELETE RESTRICT
方法抛出异常来模拟任何“pre_delete_handler
”关系。
class RelatedRecordsExist(Exception): pass
class SomeModel(MyModel):
...
def pre_delete_handler(self):
if children.count():
raise RelatedRecordsExist("SomeModel has child records!")
这会在修改任何数据之前中止删除。
不幸的是,由于在发送信号之前Django已经生成了要删除的对象列表,因此无法更新pre_delete信号中的任何数据(例如,模拟ON DELETE SET NULL
)。 Django这样做是为了避免陷入循环引用,并防止不必要地多次发出对象信号。
确保可以执行删除现在是调用代码的责任。为了帮助解决这个问题,每个模型都有一个prepare_delete()
方法,负责通过NULL
或类似工具设置self.related_set.clear()
的密钥:
class MyModel(models.Model):
...
def prepare_delete(self):
pass
为避免在views.py
和models.py
中更改过多代码,delete()
上的MyModel
方法会被覆盖,以致电prepare_delete()
:
class MyModel(models.Model):
...
def delete(self):
self.prepare_delete()
super(MyModel, self).delete()
这意味着通过obj.delete()
显式调用的任何删除都将按预期工作,但如果删除已从相关对象级联或通过queryset.delete()
完成,并且调用代码无法确保所有链接都在必要时被破坏,然后pre_delete_handler
将抛出异常。
最后,我在post_delete_handler
信号上调用的模型中添加了一个类似的post_delete
方法,让模型清除任何其他数据(例如删除{{1}的文件}}峰)
ImageField
我希望能帮助某人,并且可以将代码重新线程化为更有用的东西,而不会有太多麻烦。
有关如何改善这一点的任何建议都非常受欢迎。