【问题标题】:Garbage collecting objects in DjangoDjango中的垃圾收集对象
【发布时间】:2012-04-19 07:48:40
【问题描述】:

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

我试图通过使用 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 在第一次发出信号时就已经为真。仍然从 docs 读取,QuerySet.delete 将为每个已删除的对象发出 post_delete 信号,garbage_collectBar 被删除后被调用。

接下来的问题:

  1. 有没有更好的方法来收集 一对多 关系的 one 端?
  2. 如果不是,我应该更改什么才能使用 QuerySet.delete?

【问题讨论】:

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


    【解决方案1】:

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

    这里的关键是Foo()s 有相同的条不指向相同的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 查询。更优雅的方式可能是

    • 始终在garbage_collect 中删除Bar 或稍后手动删除,如果可以的话:

      Bar.objects.filter(foo__isnull=True).delete()
      
    • garbage_collect中,记录Bar()的删除计划,而不是删除,到一些引用计数标志或排队任务。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 我想我应该把这个包含在问题中。使用QuerySet.delete时不会调用Model.delete
      • 是的,因为此方法会批量删除对象,而不会实际从数据库中获取它们。您可以尝试编写自己的管理器实现,如果您确实需要,它将覆盖查询集的批量删除方法以发送信号。
      • 请在评论前正确阅读问题和早期的 cmets。 QuerySet.delete 发出 pre/post_delete 信号。但是,它不会调用Model.delete。这是两个完全不同的东西,我不希望通过使其一次删除一个对象来破坏QuerySet.delete 的好处。
      猜你喜欢
      • 2010-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-09
      • 2011-10-12
      相关资源
      最近更新 更多