web-dev-qa-db-ja.com

Djangoリスト内の*すべての*アイテムのクエリセット__inをフィルターする

私は次のモデルを持っているとしましょう

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50)

ビューには、categoriesと呼ばれるアクティブなフィルターのリストがあります。 categoriesにすべてのタグが存在するPhotoオブジェクトをフィルタリングしたい。

私は試した:

Photo.objects.filter(tags__name__in=categories)

ただし、これはallアイテムではなく、anyカテゴリ内のアイテムと一致します。

したがって、カテゴリが['holiday'、 'summer']の場合、休日と夏の両方のタグが付いた写真が必要です。

これは達成できますか?

87

要約:

1つのオプションは、コメントのjpicとsgallenで示唆されているように、各カテゴリに.filter()を追加することです。 filterを追加するたびに結合が追加されますが、これは小さなカテゴリのセットでは問題になりません。

集約アプローチ があります。このクエリは、大規模なカテゴリセットの場合は短くなり、おそらくより高速になります。

カスタムクエリ を使用するオプションもあります。


いくつかの例

テスト設定:

class Photo(models.Model):
    tags = models.ManyToManyField('Tag')

class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __unicode__(self):
        return self.name

In [2]: t1 = Tag.objects.create(name='holiday')
In [3]: t2 = Tag.objects.create(name='summer')
In [4]: p = Photo.objects.create()
In [5]: p.tags.add(t1)
In [6]: p.tags.add(t2)
In [7]: p.tags.all()
Out[7]: [<Tag: holiday>, <Tag: summer>]

連鎖フィルター アプローチの使用:

In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]

結果のクエリ:

In [17]: print Photo.objects.filter(tags=t1).filter(tags=t2).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_photo_tags" T4 ON ("test_photo"."id" = T4."photo_id")
WHERE ("test_photo_tags"."tag_id" = 3  AND T4."tag_id" = 4 )

filterがクエリにさらにJOINSを追加することに注意してください。

注釈アプローチ を使用:

In [29]: from Django.db.models import Count
In [30]: Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2)
Out[30]: [<Photo: Photo object>]

結果のクエリ:

In [32]: print Photo.objects.filter(tags__in=[t1, t2]).annotate(num_tags=Count('tags')).filter(num_tags=2).query
SELECT "test_photo"."id", COUNT("test_photo_tags"."tag_id") AS "num_tags"
FROM "test_photo"
LEFT OUTER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
WHERE ("test_photo_tags"."tag_id" IN (3, 4))
GROUP BY "test_photo"."id", "test_photo"."id"
HAVING COUNT("test_photo_tags"."tag_id") = 2

ANDed Qオブジェクトは機能しません。

In [9]: from Django.db.models import Q
In [10]: Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
Out[10]: []
In [11]: from operator import and_
In [12]: Photo.objects.filter(reduce(and_, [Q(tags__name='holiday'), Q(tags__name='summer')]))
Out[12]: []

結果のクエリ:

In [25]: print Photo.objects.filter(Q(tags__name='holiday') & Q(tags__name='summer')).query
SELECT "test_photo"."id"
FROM "test_photo"
INNER JOIN "test_photo_tags" ON ("test_photo"."id" = "test_photo_tags"."photo_id")
INNER JOIN "test_tag" ON ("test_photo_tags"."tag_id" = "test_tag"."id")
WHERE ("test_tag"."name" = holiday  AND "test_tag"."name" = summer )
109
Davor Lucic

動作する別のアプローチは、PostgreSQLのみですが、Django.contrib.postgres.fields.ArrayField

docs からコピーした例:

>>> Post.objects.create(name='First post', tags=['thoughts', 'Django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'Django'])

>>> Post.objects.filter(tags__contains=['thoughts'])
<QuerySet [<Post: First post>, <Post: Second post>]>

>>> Post.objects.filter(tags__contains=['Django'])
<QuerySet [<Post: First post>, <Post: Third post>]>

>>> Post.objects.filter(tags__contains=['Django', 'thoughts'])
<QuerySet [<Post: First post>]>

ArrayFieldには overlapindex transforms などのより強力な機能があります。

8

これは、Django ORMといくつかのPython magic :)を使用した動的クエリ生成によっても実行できます。

from operator import and_
from Django.db.models import Q

categories = ['holiday', 'summer']
res = Photo.filter(reduce(and_, [Q(tags__name=c) for c in categories]))

アイデアは、各カテゴリに適切なQオブジェクトを生成し、AND演算子を使用してそれらを1つのQuerySetに結合することです。例えば。あなたの例では、それは等しいだろう

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
3
demalexx

特定の演算子と列名のリストに対してフィルターを反復する小さな関数を使用します:

def exclusive_in (cls,column,operator,value_list):         
    myfilter = column + '__' + operator
    query = cls.objects
    for value in value_list:
        query=query.filter(**{myfilter:value})
    return query  

この関数は次のように呼び出すことができます。

exclusive_in(Photo,'tags__name','iexact',['holiday','summer'])

また、任意のクラスおよびリスト内のより多くのタグでも機能します。演算子は、「iexact」、「in」、「contains」、「ne」、...

1
David