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

68

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支持它(并且在那之前容忍错误...)

第一种选项似乎是唯一可行的方法,但它很丑陋,存在风险,并且在添加新模型/关系时会有遗漏的可能性。

我是否遗漏了什么?有什么建议吗?

3个回答

99

对于那些遇到这个问题的人来说,现在Django 1.3中有一个内置的解决方案。

请查看文档中的详细信息django.db.models.ForeignKey.on_delete感谢Fragments of Code网站的编辑指出这一点。

最简单的情况就是在您的模型FK字段定义中添加:

on_delete=models.SET_NULL

13
根据文档,Django 2.0中设置ForeignKey字段的'on_delete'参数是必须的。+1 - whusterj

7

Django仅模拟级联行为。

根据Django用户组的讨论,最合适的解决方案有:

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

6

好的,以下是我已经解决的方案,尽管它远非令人满意。

我为我所有的模型添加了一个抽象基类:

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)

在我的每个模型中,我通过在pre_delete_handler方法中抛出异常来模拟任何"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将抛出异常。
最后,我已经添加了类似的post_delete_handler方法到模型中,在post_delete信号上调用,让模型清除任何其他数据(例如删除ImageField的文件)。
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)

我希望这能对某些人有所帮助,而且这段代码可以轻松地重新编排成更易用的形式。

如果有任何改进建议,欢迎提出。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接