1

私は1対多の関係にあり、側の最後の参照オブジェクトが削除された後、片側自動的に削除したいと思います。つまり、ガベージコレクションを実行したい、または一種の逆カスケード操作を実行したいのです。

私は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のドキュメントに記載されているように、これは信号が2回目に発信されたときに中断し、QuerySet.delete即座に適用されinstance.bar.foo_set.count() == 0、信号が最初に発信されたときにすでに当てはまります。引き続きドキュメントを読んでいると、削除されたすべてのオブジェクトに対してシグナルQuerySet.deleteが送信され、削除された後に呼び出されます。post_deletegarbage_collectBar

質問に:

  1. 1対多の関係の片側をガベージコレクションするより良い方法はありますか?
  2. そうでない場合、QuerySet.deleteを使用できるようにするには何を変更する必要がありますか?
4

2 に答える 2

2

delete()内部のコードをチェックすることで、バッチで収集されたインスタンスを削除し、それらの削除されたインスタンスのTHENトリガーdjango/db/models/deletion.pyを見つけました。最初に削除されたインスタンスの最初の呼び出しで削除すると、それらが指している が既に削除されているため、後の インスタンスは失敗します。QuerySet.deletepost_deleteBar()post_deleteFoo()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クエリを実行します。より優雅な方法は

  • Bar可能な場合は、常にgarbage_collectまたは後で手動で削除してください。

    Bar.objects.filter(foo__isnull=True).delete()
    
  • では、 ref-count フラグまたはキューに入れられたタスクを削除する代わりにgarbage_collect、削除計画を記録します。Bar()

于 2012-04-19T17:12:29.360 に答える
0

モデルのメソッド delete をオーバーライドし、関連するオブジェクトを見つけて削除することもできます。

于 2012-04-19T07:55:19.077 に答える