Models:
class Tag(models.Model):
name = models.CharField(max_length=20)
class Post(models.Model):
tags = models.ManyToManyField(Tag)
title = models.CharField(max_length=20)
Data:
a_post=Post.objects.create(title='a')
ab_post=Post.objects.create(title='ab')
ac_post=Post.objects.create(title='ac')
a_tag=a_post.tags.create(name='a')
ab_post.tags.add(a_tag)
b_tag=ab_post.tags.create(name='b')
ac_post.tags.add(a_tag)
ac_post=ac_post.tags.create(name='c')
I want to query all Posts with only tags in list. Posts with other tags should not be returned. Code like this:
Post.objects.filter(tags__name__only_in=['a','b']).all()
should return [a_post, ab_post].
I've already read this. Chained filters will not help. Any ideas?
You can check if the number of Tag
s that satisfy the predicate is the same as the total number of tags:
from django.db.models import Count, Q
Post.objects.annotate(
ntags=Count('tags')
).filter(
ntags=Count('tags', filter=Q(tags__name__in=['a', 'b'])),
ntags__gt=0
)
The first filter thus checks if the total number of related tags is the same as the number of tags with as name 'a'
or 'b'
. If the number is different, then we know that there is a tag with a different name.
The second filter checks if the number of tags is greater than zero, to exclude matches with no tags at all.