有哪些选项可以覆盖 Django 的级联删除行为?
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 aQuerySet
. Also, every model'sdelete()
must be overridden to make sure Django's code is never called andsuper()
can't be called as it may use aQuerySet
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
对于那些也遇到这个问题的人来说,请注意,Django 1.3 中现在有一个内置的解决方案。
请参阅文档 django 中的详细信息。 db.models.ForeignKey.on_delete 感谢 Fragments of Code 网站的编辑指出。
最简单的可能场景只需在模型 FK 字段定义中添加:
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:
Django 仅模拟 CASCADE 行为。
根据 Django 用户组中的讨论,最多足够的解决方案是:
Django only emulates CASCADE behaviour.
According to discussion in Django Users Group the most adequate solutions are:
好吧,以下是我已经确定的解决方案,尽管还远远不能令人满意。
我为所有模型添加了一个抽象基类:
信号处理程序捕获此模型子类的任何
pre_delete
事件:在我的每个模型中,我模拟任何“
ON DELETE RESTRICT<如果存在子记录,则通过从
pre_delete_handler
方法抛出异常来建立关系。这会在修改任何数据之前中止删除。
不幸的是,不可能更新 pre_delete 信号中的任何数据(例如模拟 ON DELETE SET NULL),因为在发送信号之前 Django 已经生成了要删除的对象列表。 Django 这样做是为了避免陷入循环引用并防止不必要地多次向对象发出信号。
确保可以执行删除现在是调用代码的责任。为了帮助实现这一点,每个模型都有一个
prepare_delete()
方法,负责通过self.lated_set.clear()
将键设置为NULL
或类似:为了避免在我的
views.py
和models.py
中更改太多代码,delete()
方法被重写MyModel
调用prepare_delete()
:这意味着通过
obj.delete()
显式调用的任何删除都将按预期工作,但如果删除已从相关对象级联或通过queryset.delete()
完成,并且调用代码未确保在必要时断开所有链接,则pre_delete_handler
将抛出异常。最后,我向模型添加了一个类似的
post_delete_handler
方法,该方法在post_delete
信号上调用,并让模型清除任何其他数据(例如删除以下文件)ImageField
s。)我希望对某人有所帮助,并且代码可以重新线程化回更可用的东西,而不会遇到太多麻烦。
任何有关如何改进这一点的建议都非常受欢迎。
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:
A signal handler catches any
pre_delete
events for subclasses of this model:In each of my models, I simulate any "
ON DELETE RESTRICT
" relations by throwing an exception from thepre_delete_handler
method if a child record exists.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 toNULL
viaself.related_set.clear()
or similar:To avoid having to change too much code in my
views.py
andmodels.py
, thedelete()
method is overridden onMyModel
to callprepare_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 aqueryset.delete()
and the calling code hasn't ensured that all links are broken where necessary, then thepre_delete_handler
will throw an exception.And lastly, I've added a similar
post_delete_handler
method to the models that gets called on thepost_delete
signal and lets the model clear up any other data (for example deleting files forImageField
s.)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.