重写Django的级联删除行为有什么select?
Django模型通常可以很好地处理ON DELETE CASCADE行为(以一种对本地不支持的数据库有效的方式)。
然而,我正在努力发现什么是最好的方式来覆盖这种行为是不适当的,在下面的情况下,例如:
-
ON DELETE RESTRICT(即防止删除一个对象,如果它有子logging)
-
ON DELETE SET NULL(即不要删除子logging,而是将其父键设置为NULL,而不是打破关系)
-
删除logging时更新其他相关数据(例如,删除上传的图像文件)
以下是我意识到的实现这些目标的潜在方法:
-
覆盖模型的
delete()
方法。 虽然这样的工作,当logging通过QuerySet
被删除时,它被回避。 此外,必须重写每个模型的delete()
,以确保Django的代码永远不会被调用,并且不能调用super()
因为它可能使用QuerySet
来删除子对象。 -
使用信号。 这似乎是理想的,因为它们在直接删除模型或通过QuerySet删除时被调用。 但是,不可能防止子对象被删除,因此不可用来实现ON CASCADE RESTRICT或SET NULL。
-
使用正确处理这个问题的数据库引擎(在这种情况下,Django做了什么?)
-
等到Django支持它(直到那时才和bug一起生活)
看起来第一个select是唯一可行的select,但它很丑,把婴儿抛出洗澡水,并且在添加新的模型/关系时冒险丢失一些东西。
我错过了什么吗? 任何build议?
对于那些遇到这个问题的人来说,现在Django 1.3中已经有了一个内置的解决scheme。
查看文档中的详细信息django.db.models.ForeignKey.on_delete感谢代码片段的编辑指出。
最简单的情况就是添加你的模型FK字段定义:
on_delete=models.SET_NULL
Django只模仿CASCADE行为。
根据Django用户组的讨论 ,最适合的解决scheme是:
- 重复ON DELETE SET NULLscheme – 在obj.delete()之前手动执行obj.rel_set.clear()(对于每个相关模型)。
- 重复ON DELETE RESTRICT场景 – 手动检查obj.rel_set是否在obj.delete()之前为空。
好吧,以下是我已经解决的解决scheme,尽pipe远非令人满意。
我为我的所有模型添加了一个抽象基类:
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)
在我的每个模型中,如果存在子logging,则通过从pre_delete_handler
方法抛出exception来模拟任何“ ON DELETE RESTRICT
”关系。
class RelatedRecordsExist(Exception): pass class SomeModel(MyModel): ... def pre_delete_handler(self): if children.count(): raise RelatedRecordsExist("SomeModel has child records!")
这会在任何数据被修改之前中止删除。
不幸的是,不能更新pre_delete信号中的任何数据(例如,模拟ON DELETE SET NULL
),因为在发送信号之前,Django已经生成了要删除的对象列表。 Django是这样做的,以避免卡住循环引用,并防止多次不必要地通知对象多次。
确保可以执行删除现在是调用代码的责任。 为了解决这个问题,每个模型都有一个prepare_delete()
方法,通过self.related_set.clear()
或类似的方法将键设置为NULL
。
class MyModel(models.Model): ... def prepare_delete(self): pass
为了避免在我的views.py
和models.py
改变太多的代码,在MyModel
上重写delete()
方法来调用prepare_delete()
:
class MyModel(models.Model): ... def delete(self): self.prepare_delete() super(MyModel, self).delete()
这意味着通过obj.delete()
显式调用的任何删除都将按预期工作,但如果删除已从相关对象级联或通过queryset.delete()
,并且调用代码未确保所有链接均为在必要的地方断开,那么pre_delete_handler
将会抛出一个exception。
最后,我为在post_delete
信号上调用的模型添加了一个类似的post_delete_handler
方法,并让模型清除其他任何数据(例如删除post_delete
文件)。
class MyModel(models.Model): ... def post_delete_handler(self): pass def post_delete_handler(sender, instance, **kwargs): if isinstance(instance, MyModel): instance.post_delete_handler() models.signals.post_delete.connect(post_delete_handler)
我希望能够帮助某人,并且可以将代码重新转换回更有用的东西,而不会有太多的麻烦。
有关如何改善这一点的任何build议都是值得欢迎的。