1

我有一个一对多的关系,我想在多方的最后一个引用对象被删除后自动删除一方。也就是说,我要进行垃圾回收,或者做一种反向级联操作。

我试图通过使用 Django 的 post_delete 信号来解决这个问题。这是我正在尝试做的一个简化示例:

模型.py

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它会严重破坏。

测试.py

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)

这工作得很好。

tests.py 继续

    def test_queryset_post_delete(self):
        Foo.objects.all().delete()
        self.assertEqual(Foo.objects.count(), 0)
        self.assertEqual(Bar.objects.count(), 0)

这会在第二次发出信号时中断,因为正如Django 的文档所说,QuerySet.delete它会立即应用,并且instance.bar.foo_set.count() == 0在第一次发出信号时就已经是真的了。仍然从文档中读取,将为每个已删除的对象QuerySet.delete发出信号,并在被删除后被调用。post_deletegarbage_collectBar

那么问题来了:

  1. 有没有更好的垃圾收集一对多关系一侧的方法?
  2. 如果没有,我应该改变什么才能使用 QuerySet.delete?
4

2 回答 2

2

通过检查delete()里面的代码django/db/models/deletion.py,我发现QuerySet.delete删除了批量收集的实例,然后触发post_delete那些删除的实例。如果您Bar()在第一次post_delete调用中删除第一个删除的Foo()实例,则后面post_deleteFoo()实例将失败,因为Bar()它们指向的实例已被删除。

这里的关键是Foo()s 具有相同的 bar 并不指向同一个Bar()实例,并且 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()
    
  • garbage_collect中,记录删除计划Bar()而不是删除,到一些 ref-count 标志或排队的任务。

于 2012-04-19T17:12:29.360 回答
0

我问你可以覆盖模型的删除方法,找到相关的对象并删除它们。

于 2012-04-19T07:55:19.077 回答