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

Tom*_*Tom 57 django django-signals cascading-deletes

Django模型通常可以非常充分地处理ON DELETE CASCADE行为(以适用于本机不支持它的数据库的方式).

但是,我在努力发现在不适合的情况下覆盖此行为的最佳方法是什么,例如在以下场景中:

  • ON DELETE RESTRICT(即如果有子记录,则阻止删除对象)

  • ON DELETE SET NULL(即不删除子记录,但将其父键设置为NULL而不是断开关系)

  • 删除记录时更新其他相关数据(例如删除上传的图像文件)

以下是我所知道的实现这些目标的潜在方法:

  • 覆盖模型的delete()方法.虽然这种方法有效,但是当通过a删除记录时,它会被回避QuerySet.此外,delete()必须重写每个模型,以确保Django的代码永远不会被调用,super()并且无法调用,因为它可能使用a QuerySet来删除子对象.

  • 使用信号.这似乎是理想的,因为在直接删除模型或通过QuerySet删除时会调用它们.但是,不可能阻止删除子对象,因此无法实现ON CASCADE RESTRICT或SET NULL.

  • 使用正确处理此问题的数据库引擎(在这种情况下Django会做什么?)

  • 等到Django支持它(直到那时还有bug ...)

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

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

小智 79

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

请参阅文档中的详细信息django.db.models.ForeignKey.on_delete感谢Fragments of Code网站的编辑指出.

最简单的场景只需添加到模型FK字段定义中:

on_delete=models.SET_NULL
Run Code Online (Sandbox Code Playgroud)

  • +1 - 根据文档,在Django 2.0中也需要为ForeignKey字段设置`on_delete`. (12认同)

Leo*_*kov 6

Django只模拟CASCADE行为.

根据Django Users Group的讨论,最合适的解决方案是:

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


Tom*_*Tom 5

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

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

class MyModel(models.Model):
    class Meta:
        abstract = True

    def pre_delete_handler(self):
        pass
Run Code Online (Sandbox Code Playgroud)

信号处理程序捕获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)
Run Code Online (Sandbox Code Playgroud)

在我的每个模型中,如果存在子记录,我会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!")
Run Code Online (Sandbox Code Playgroud)

这会在修改任何数据之前中止删除。

不幸的是,无法更新 pre_delete 信号中的任何数据(例如 to emulate ON DELETE SET NULL),因为在发送信号之前,Django 已经生成了要删除的对象列表。Django 这样做是为了避免陷入循环引用并防止不必要地多次向对象发出信号。

确保可以执行删除现在是调用代码的责任。为了帮助实现这一点,每个模型都有一个prepare_delete()方法来处理将键设置为NULLviaself.related_set.clear()或类似的:

class MyModel(models.Model):
    ...
    def prepare_delete(self):
        pass
Run Code Online (Sandbox Code Playgroud)

为了避免在 myviews.py和 中更改太多代码models.py,该delete()方法被覆盖MyModel以调用prepare_delete()

class MyModel(models.Model):
    ...
    def delete(self):
        self.prepare_delete()
        super(MyModel, self).delete()
Run Code Online (Sandbox Code Playgroud)

这意味着通过显式调用的任何删除obj.delete()都将按预期工作,但如果删除已从相关对象级联或通过 a 完成,queryset.delete()并且调用代码未确保所有链接在必要时断开,pre_delete_handler则将抛出异常.

最后,我post_delete_handler为在post_delete信号上调用的模型添加了一个类似的方法,并让模型清除任何其他数据(例如删除ImageFields 的文件。)

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)
Run Code Online (Sandbox Code Playgroud)

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

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