Django过滤查询集__in为列表中的* every *项

时间:2011-12-23 16:01:46

标签: python django filter django-queryset

假设我有以下型号

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

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

在视图中,我有一个名为 categories 的活动过滤器列表。 我想过滤具有类别中所有标签的Photo对象。

我试过了:

Photo.objects.filter(tags__name__in=categories)

但这会匹配类别中的任何项目,而不是所有项目。

因此,如果类别是['假日','夏天'],我希望Photo's带有假日和夏季标签。

这可以实现吗?

8 个答案:

答案 0 :(得分:108)

<强>要点:

正如评论中的jpic和sgallen所建议的那样,一个选项是为每个类别添加.filter()。每增加一个filter添加更多联接,对于一小组类别来说这不应该是一个问题。

aggregation approach。对于大量类别,此查询会更短,也许更快。

您还可以选择使用custom queries


一些例子

测试设置:

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>]

使用chained filters方法:

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

使用annotation approach

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

AND ed 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 )

答案 1 :(得分:8)

另一种有效的方法,虽然仅使用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

答案 2 :(得分:3)

这也可以通过使用Django ORM和一些Python魔法进行动态查询生成来完成:)

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运算符将它们组合到一个QuerySet中。例如。对于你的例子,它等于

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

答案 3 :(得分:1)

我使用了一个小函数,该函数为给定运算符的列名迭代列表上的过滤器:

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”,...之类的任何人。

答案 4 :(得分:1)

如果你像我一样在这个问题上苦苦挣扎,但没有提到的任何内容对你有帮助,也许这个可以解决你的问题

在某些情况下,最好只存储前一个过滤器的 id,而不是链接过滤器

tags = [1, 2]
for tag in tags:
    ids = list(queryset.filter(tags__id=tag).values_list("id", flat=True))
    queryset = queryset.filter(id__in=ids)

使用这种方法将帮助您避免在 SQL 查询中堆积 JOIN

答案 5 :(得分:0)

queryset = Photo.objects.filter(tags__name="vacaciones") | Photo.objects.filter(tags__name="verano")

答案 6 :(得分:0)

我的解决方案: 让说 author 是需要匹配列表中所有项目的元素列表,所以:

        for a in author:
            queryset = queryset.filter(authors__author_first_name=a)
                if not queryset:
                    break

答案 7 :(得分:-1)

如果我们想动态地执行此操作,请按照示例进行操作:

tag_ids = [t1.id, t2.id]
qs = Photo.objects.all()

for tag_id in tag_ids:
    qs = qs.filter(tag__id=tag_id)    

print qs