我有一个一对多关系,我想在很多上的最后一个引用对象之后自动删除一个端em> side已被删除。也就是说,我想执行垃圾收集,或者进行一种反向级联操作。
我试图通过使用Django的post_delete信号来解决这个问题。以下是我尝试做的简化示例:
class Bar(models.Model):
j = models.IntegerField()
# implicit foo_set
class Foo(models.Model):
i = models.IntegerField()
bar = models.ForeignKey(Bar)
def garbage_collect(sender, instance, **kwargs):
# Bar should be deleted after the last Foo.
if instance.bar.foo_set.count() == 0:
instance.bar.delete()
post_delete.connect(garbage_collect, Foo)
这在使用Model.delete
时有效,但在使用QuerySet.delete
时会出现可怕的情况。
class TestGarbageCollect(TestCase):
# Bar(j=1)
# Foo(bar=bar, i=1)
# Foo(bar=bar, i=2)
# Foo(bar=bar, i=3)
fixtures = ['db.json']
def test_separate_post_delete(self):
for foo in Foo.objects.all():
foo.delete()
self.assertEqual(Foo.objects.count(), 0)
self.assertEqual(Bar.objects.count(), 0)
这很好用。
def test_queryset_post_delete(self):
Foo.objects.all().delete()
self.assertEqual(Foo.objects.count(), 0)
self.assertEqual(Bar.objects.count(), 0)
这会在第二次发出信号时中断,因为正如Django's documentation所说,QuerySet.delete
会立即应用,并且instance.bar.foo_set.count() == 0
在第一次发出信号时已经为真。仍在阅读docs,QuerySet.delete
会为每个已删除的对象发出post_delete
信号,并在garbage_collect
被删除后调用Bar
。
然后问题:
答案 0 :(得分:2)
通过检查delete()
内的django/db/models/deletion.py
中的代码,我发现QuerySet.delete
批量删除了收集的实例, 那么 触发{ {1}}用于那些已删除的实例。如果您在第一个post_delete
调用第一个已删除的Bar()
实例时删除了post_delete
,则Foo()
实例的后续post_delete
将失败,因为{{1}他们指出的内容已被删除。
这里的关键是Foo()
具有相同的条形图并不指向同一个Bar()
实例,并且条形图会被过早删除。然后我们可以
直线Foo()
查询Bar()
try...except
为每个实例预加载instance.bar
以避免上述异常
def garbage_collect(sender, instance, **kwargs):
try:
if instance.bar.foo_set.exists():
instance.bar.delete()
except Bar.DoesNotExist:
pass
以上两种解决方案都会进行额外的Bar()
次查询。更优雅的方式可能是
如果可以,请在def test_queryset_post_delete(self):
Foo.objects.select_related('bar').delete()
def garbage_collect(sender, instance, **kwargs):
if instance.bar.foo_set.exists():
instance.bar.delete()
或稍后手动删除SELECT
:
Bar
在garbage_collect
中,记录Bar.objects.filter(foo__isnull=True).delete()
的删除计划,而不是删除某些ref-count标记或排队任务。
答案 1 :(得分:0)
我问你可以覆盖模型的方法删除,找到相关的对象并删除它们。