web-dev-qa-db-ja.com

Django filter()on関連モデルのフィールド

Djangoのfilter()メソッドがどのように機能するかについて、非常に基本的で基本的なものが欠けていると思います。

次のモデルを使用します。

class Collection(models.Model): 
    pass

class Item(models.Model):
    flag = models.BooleanField()
    collection =  models.ForeignKey(Collection)

質問の下部にあるpopulate()関数を呼び出して提供されたデータを使用して、。/ manage.pyシェルで次のコマンドを実行してみてください。

len(Collection.objects.filter(item__flag=True))

私の予想では、これは「2」を出力します。これは、flag = Trueのアイテムが少なくとも1つあるコレクションの数です。この予想は、 https://docs.djangoproject.com/en/1.5/topics/db/queries/#lookups-that-span-relationships のドキュメントに基づいており、「この例では、「BeatlesBlog」という名前のブログを持つすべてのEntryオブジェクトを取得します。

ただし、上記の呼び出しでは、実際には「6」が出力されます。これは、flag = Trueを持つアイテムレコードの数です。ただし、返される実際のオブジェクトはコレクションオブジェクトです。 flag = Trueの対応するItemレコードごとに1回ずつ、同じCollectionオブジェクトを複数回返しているようです。これは次の方法で確認できます。

queryset = Collection.objects.filter(item__flag=True)
queryset[0] == queryset[1]

trueを出力します。

これは正しい動作ですか?もしそうなら、その理由は何ですか?それが期待されるものである場合、ドキュメントは厳密に正しいと解釈される可能性がありますが、各オブジェクトが複数回返される可能性があるとは述べていません。

これは関連する例ですが、これは非常に驚くべき(または単に間違った)動作のようです。カスタムモデルマネージャーによってexclude()呼び出しが追加されていて、呼び出し元がfilter()を追加している場合に、私は気づきました。

from Django.db.models import Count    
[coll.count for coll in Collection.objects.filter(item__flag=True).annotate(count=Count("item"))]
[coll.count for coll in Collection.objects.exclude(item=None).filter(item__flag=True).annotate(count=Count("item"))]

最初のケースは「[2,4]」を出力しますが、2番目のケースは「[8,16]」を出力します!!!

ポピュレート機能:

def populate():
    Collection.objects.all().delete()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
15
Tom

これには2つの部分があることがわかりました。 1つ目はdistinct()メソッドで、ドキュメントには次のように書かれています。

デフォルトでは、QuerySetは重複する行を削除しません。 Blog.objects.all()などの単純なクエリでは結果行が重複する可能性がないため、実際には、これが問題になることはめったにありません。ただし、クエリが複数のテーブルにまたがっている場合、QuerySetの評価時に重複する結果が得られる可能性があります。そのとき、distinct()を使用します。

次の出力は期待どおり「2」です。

len(Collection.objects.filter(item__flag=True).distinct())

ただし、これは、annotate()を使用して示したより複雑な例では役に立ちません。これは既知の問題のインスタンスであることが判明しました: https://code.djangoproject.com/ticket/1006

14
Tom