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

San*_*wen 89 python django filter django-queryset

假设我有以下型号

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

class Tag(models.Model):
    name = models.CharField(max_length=50)
Run Code Online (Sandbox Code Playgroud)

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

我试过了:

Photo.objects.filter(tags__name__in=categories)
Run Code Online (Sandbox Code Playgroud)

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

因此,如果类别是['假日','夏天'],我希望照片有假日和夏季标签.

这可以实现吗?

Dav*_*cic 113

摘要:

正如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>]
Run Code Online (Sandbox Code Playgroud)

使用链式过滤器方法:

In [8]: Photo.objects.filter(tags=t1).filter(tags=t2)
Out[8]: [<Photo: Photo object>]
Run Code Online (Sandbox Code Playgroud)

结果查询:

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 )
Run Code Online (Sandbox Code Playgroud)

请注意,每个都会为查询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>]
Run Code Online (Sandbox Code Playgroud)

结果查询:

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
Run Code Online (Sandbox Code Playgroud)

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]: []
Run Code Online (Sandbox Code Playgroud)

结果查询:

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 )
Run Code Online (Sandbox Code Playgroud)

  • 是否有自定义查找的解决方案?https://docs.djangoproject.com/en/1.10/howto/custom-lookups/将"__in"切换为"__all"并让它创建正确的sql查询会很酷. (6认同)
  • @tbm它仍然无法正常工作。Photo.objects.filter(tags__in = tags)匹配具有任何标签的照片,而不仅仅是具有全部标签的照片。其中一些仅具有所需标签之一的标签,可能恰好具有您要查找的标签数量,而某些具有所有所需标签的标签中的一些标签可能还具有其他标签。 (3认同)
  • @beruic注释仅计算查询返回的标签,因此如果(查询返回的标签数)==(搜索的标签数)则包含该行;不会搜索“额外”标签,因此不会被计算在内。我已经在我自己的应用程序中验证了这一点。 (3认同)
  • @beruic我认为这个想法是你可以用 num_tags=len(tags) 替换 num_tags=2; 我希望硬编码 2 只是为了举例。 (2认同)

San*_*wen 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>]>
Run Code Online (Sandbox Code Playgroud)

ArrayField有一些更强大的功能,如重叠索引转换.


dem*_*exx 5

这也可以通过使用 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]))
Run Code Online (Sandbox Code Playgroud)

这个想法是为每个类别生成适当的 Q 对象,然后使用 AND 运算符将它们组合成一个 QuerySet。例如,对于您的示例,它等于

res = Photo.filter(Q(tags__name='holiday') & Q(tags__name='summer'))
Run Code Online (Sandbox Code Playgroud)

  • 这行不通。您的查询示例不会为相关模型返回任何内容。 (4认同)