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

发布于 2024-08-25 23:03:46 字数 851 浏览 6 评论 0原文

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

但是,我正在努力寻找在不合适的情况下覆盖此行为的最佳方法。 ,例如在以下场景中:

  • ON DELETE RESTRICT(即阻止删除具有子记录的对象)

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

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

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

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

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

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

  • 等待 Django 支持它(并忍受错误直到那时...)

似乎第一个选项是唯一可行的,但它很丑陋,抛出宝贝和洗澡水一起出去,并且在添加新模型/关系时存在丢失某些东西的风险。

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

Django models generally handle the ON DELETE CASCADE behaviour quite adequately (in a way that works on databases that don't support it natively.)

However, I'm struggling to discover what is the best way to override this behaviour where it is not appropriate, in the following scenarios for example:

  • ON DELETE RESTRICT (i.e. prevent deleting an object if it has child records)

  • ON DELETE SET NULL (i.e. don't delete a child record, but set it's parent key to NULL instead to break the relationship)

  • Update other related data when a record is deleted (e.g. deleting an uploaded image file)

The following are the potential ways to achieve these that I am aware of:

  • Override the model's delete() method. While this sort of works, it is sidestepped when the records are deleted via a QuerySet. Also, every model's delete() must be overridden to make sure Django's code is never called and super() can't be called as it may use a QuerySet to delete child objects.

  • Use signals. This seems to be ideal as they are called when directly deleting the model or deleting via a QuerySet. However, there is no possibility to prevent a child object from being deleted so it is not usable to implement ON CASCADE RESTRICT or SET NULL.

  • Use a database engine that handles this properly (what does Django do in this case?)

  • Wait until Django supports it (and live with bugs until then...)

It seems like the first option is the only viable one, but it's ugly, throws the baby out with the bath water, and risks missing something when a new model/relation is added.

Am I missing something? Any recommendations?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一笑百媚生 2024-09-01 23:03:46

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

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

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

on_delete=models.SET_NULL

Just a note for those who run into this issue as well, there is now an built-in solution in Django 1.3.

See the details in the documentation django.db.models.ForeignKey.on_delete Thanks for editor of Fragments of Code site to point it out.

The simplest possible scenario just add in your model FK field definition:

on_delete=models.SET_NULL
素衣风尘叹 2024-09-01 23:03:46

Django 仅模拟 CASCADE 行为。

根据 Django 用户组中的讨论,最多足够的解决方案是:

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

Django only emulates CASCADE behaviour.

According to discussion in Django Users Group the most adequate solutions are:

  • To repeat ON DELETE SET NULL scenario - manually do obj.rel_set.clear() (for every related model) before obj.delete().
  • To repeat ON DELETE RESTRICT scenario - manually check is obj.rel_set empty before obj.delete().
_蜘蛛 2024-09-01 23:03:46

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

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

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 信号中的任何数据(例如模拟 ON DELETE SET NULL),因为在发送信号之前 Django 已经生成了要删除的对象列表。 Django 这样做是为了避免陷入循环引用并防止不必要地多次向对象发出信号。

确保可以执行删除现在是调用代码的责任。为了帮助实现这一点,每个模型都有一个 prepare_delete() 方法,负责通过 self.lated_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)

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

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

Ok, the following is the solution I've settled on, though it's far from satisfying.

I've added an abstract base class for all my models:

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

    def pre_delete_handler(self):
        pass

A signal handler catches any pre_delete events for subclasses of this model:

def pre_delete_handler(sender, instance, **kwargs):
    if isinstance(instance, MyModel):
        instance.pre_delete_handler()
models.signals.pre_delete.connect(pre_delete_handler)

In each of my models, I simulate any "ON DELETE RESTRICT" relations by throwing an exception from the pre_delete_handler method if a child record exists.

class RelatedRecordsExist(Exception): pass

class SomeModel(MyModel):
    ...
    def pre_delete_handler(self):
        if children.count(): 
            raise RelatedRecordsExist("SomeModel has child records!")

This aborts the delete before any data is modified.

Unfortunately, it is not possible to update any data in the pre_delete signal (e.g. to emulate ON DELETE SET NULL) as the list of objects to delete has already been generated by Django before the signals are sent. Django does this to avoid getting stuck on circular references and to prevent signaling an object multiple times unnecessarily.

Ensuring a delete can be performed is now the responsibility of the calling code. To assist with this, each model has a prepare_delete() method that takes care of setting keys to NULL via self.related_set.clear() or similar:

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

To avoid having to change too much code in my views.py and models.py, the delete() method is overridden on MyModel to call prepare_delete():

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

This means that any deletes explicitly called via obj.delete() will work as expected, but if a delete has cascaded from a related object or is done via a queryset.delete() and the calling code hasn't ensured that all links are broken where necessary, then the pre_delete_handler will throw an exception.

And lastly, I've added a similar post_delete_handler method to the models that gets called on the post_delete signal and lets the model clear up any other data (for example deleting files for 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)

I hope that helps someone and that the code can be re-threaded back into something more useable without too much trouble.

Any suggestions on how to improve this are more than welcome.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文