68

Django 模型通常可以很好地处理 ON DELETE CASCADE 行为(以一种适用于本机不支持它的数据库的方式。)

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

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

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

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

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

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

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

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

  • 等到 Django 支持它(并在此之前与错误一起生活......)

似乎第一个选项是唯一可行的选项,但它很丑陋,会把婴儿和洗澡水一起扔出去,并且在添加新模型/关系时可能会丢失一些东西。

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

4

3 回答 3

97

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

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

最简单的可能场景只需在您的模型 FK 字段定义中添加:

on_delete=models.SET_NULL
于 2010-12-08T21:04:08.143 回答
7

Django 只模拟 CASCADE 行为。

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

  • 要重复 ON DELETE SET NULL 场景 - 在 obj.delete() 之前手动执行 obj.rel_set.clear()(对于每个相关模型)。
  • 重复 ON DELETE RESTRICT 场景 - 在 obj.delete() 之前手动检查 obj.rel_set 是否为空。
于 2010-03-19T06:35:39.413 回答
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)

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

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

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

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

class MyModel(models.Model):
    ...
    def prepare_delete(self):
        pass

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

class MyModel(models.Model):
    ...
    def delete(self):
        self.prepare_delete()
        super(MyModel, self).delete()

这意味着任何显式调用 viaobj.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)

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

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

于 2010-04-01T02:24:29.337 回答