Search code examples
django-querysetdjango-filter

Django filter queryset __in for items only in list


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?


Solution

  • You can check if the number of Tags 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.