Reputation: 1510
I have two simple models. Let's say Tags and Posts. They look like this (simplified):
class Tag(models.Model):
name = models.CharField(max_length=255, blank=False, unique=True)
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, default='')
tags = models.ManyToManyField(Tag)
So, each tags can be assigned to n posts and vice versa.
I'm trying to get a list of either all posts where ANY of the given tags are assigned, or where ALL of the given tags are assigned.
So basically I want an OR or an AND combination.
The OR is easy enough. In my view I do (assume "tags" is a list of Tag objects)
queryset = Post.objects.filter(tags__in=tags)
But I can't figure out how to do that for an AND combination. I either get nothing or the same as with OR. I tried a lot of different things, but nothing worked in my scenario, where I have a dynamic list of tags to filter by.
My most promising approach looked like this:
filter = [Q(tags=tag) for tag in tags if tag.enabled == True]
qs = Post.objects.all()
qs = qs.filter(reduce(operator.__and__, filter))
This still returns an empty list. And yes I am 100% sure that I have Post records, with both requested tags assigned to it.
What am I doing wrong?
Upvotes: 0
Views: 43
Reputation: 437
In order to achieve your desired filter, we can do something like this:
wanted_tags = [tag_1, tag_2, tag_3,...]
# To keep it as lean as possible, we first minimise our search space by filtering solely the posts with the amount of tags in the first place:
preprocessed_posts = Post.objects.annotate(c=Count('tags')).filter(c=len(wanted_tags))
# Now comes the somewhat pythonic part, which is simple but feels kinda hacky:
for tag in wanted_tags:
preprocessed_posts.filter(tags=tag)
# now preprocessed_posts contains the wanted entities
wanted_posts = preprocessed_posts
Upvotes: 0
Reputation: 1510
Okay, the answer is, as usual, relativly simple:
qs = Post.objects.all()
for tag in tags:
qs = qs.filter(tags=tag)
Upvotes: 1