覆盖Django的级联删除行为有哪些选择?

时间:2010-03-19 05:56:07

标签: django django-signals cascading-deletes

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 ...)

似乎第一种选择是唯一可行的选择,但它很难看,用洗澡水将宝宝扔掉,并且在添加新模型/关系时可能会遗漏某些东西。

我错过了什么吗?有什么建议吗?

3 个答案:

答案 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,最合适的解决方案是:

  • 重复ON DELETE SET NULL场景 - 在obj.delete()之前手动执行obj.rel_set.clear()(对于每个相关模型)。
  • 重复ON DELETE RESTRICT场景 - 在obj.delete()之前手动检查obj.rel_set为空。

答案 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.pymodels.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

我希望能帮助某人,并且可以将代码重新线程化为更有用的东西,而不会有太多麻烦。

有关如何改善这一点的任何建议都非常受欢迎。