重写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.pymodels.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议都是值得欢迎的。