垃圾收集Django中的对象

时间:2012-04-19 07:48:41

标签: python django garbage-collection django-queryset django-signals

我有一个一对多关系,我想在很多上的最后一个引用对象之后自动删除一个端em> side已被删除。也就是说,我想执行垃圾收集,或者进行一种反向级联操作。

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

models.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时会出现可怕的情况。

tests.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's documentation所说,QuerySet.delete会立即应用,并且instance.bar.foo_set.count() == 0在第一次发出信号时已经为真。仍在阅读docsQuerySet.delete会为每个已删除的对象发出post_delete信号,并在garbage_collect被删除后调用Bar

然后问题:

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

2 个答案:

答案 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)

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