Django 过滤器 queryset__ in 针对列表中的 * every * 条目

假设我有以下模型

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


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

在一个视图中,我有一个名为 类别的带有活动过滤器的列表。 我想过滤照片对象的所有标签出现在 类别

我试过:

Photo.objects.filter(tags__name__in=categories)

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

因此,如果类别将是[’假日’,’夏天’]我希望照片的两个假日和夏天标签。

这能实现吗?

138093 次浏览

这也可以通过使用 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 运算符将它们组合成一个 QuerySet。例如,对于你的例子,它等于

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

摘要:

一个选项是,正如 jpicand 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 )

另一种可行的方法是使用 django.contrib.postgres.fields.ArrayField,尽管只能使用 PostgreSQL:

医生复制的例子:

>>> 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有一些更强大的功能,如 重叠索引变换

如果我们想要动态地完成这项工作,可以参照下面的例子:

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

我使用了一个小函数,它对给定操作符的列表和列名进行迭代过滤:

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

它还可以处理列表中的任何类和更多标记; 运算符可以是任何类型的,如‘ iPrecision’、‘ in’、‘ include’、‘ ne’... ..。

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

如果你和我一样在这个问题上苦苦挣扎,没有任何东西能帮到你,也许这个能解决你的问题

与链式过滤器不同,在某些情况下,只存储前一个过滤器的 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:

我的解决办法是: 这么说吧 Author 是需要匹配列表中所有项目的元素列表,因此:

        for a in author:
queryset = queryset.filter(authors__author_first_name=a)
if not queryset:
break
for category in categories:
query = Photo.objects.filter(tags_name=category)

这段代码,过滤你的照片,其中有所有的标签名称来自类别。