【问题标题】:What are the options for overriding Django's cascading delete behaviour?覆盖 Django 的级联删除行为的选项有哪些?
【发布时间】:2011-01-29 08:02:58
【问题描述】:

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

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

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

【问题讨论】:

    标签: django django-signals cascading-deletes


    【解决方案1】:

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

    详见文档django.db.models.ForeignKey.on_delete 感谢 Fragments of Code 网站的编辑指出。

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

    on_delete=models.SET_NULL
    

    【讨论】:

    • +1 - 根据文档,在 Django 2.0 中也需要为 ForeignKey 字段设置 on_delete
    【解决方案2】:

    Django 只模拟 CASCADE 行为。

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

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

    【讨论】:

      【解决方案3】:

      好的,以下是我确定的解决方案,虽然还远远不能令人满意。

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

      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 中更改太多代码,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 信号上被调用,并让模型清除任何其他数据(例如删除 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)
      

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

      我们非常欢迎任何有关如何改进这一点的建议。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-27
        • 2011-04-20
        • 1970-01-01
        • 2015-07-21
        • 1970-01-01
        • 2021-06-27
        • 1970-01-01
        相关资源
        最近更新 更多