mjschuetze102
mjschuetze102

Reputation: 145

Django Queryset filtering pk list over Manytomany

I want to take a Queryset of Project.objects.all() and filter all Projects that contain a list of attributes chosen by the user.

models.py

class Attribute(models.Model):
    title = models.CharField(max_length=20)

class Project(models.Model):
    title = models.CharField(max_length=30)
    attributes = models.ManyToManyField(Attribute)

## For the sake of this example, there are two Projects and two Attributes
## Project1 is associated to attr1 and attr2
## Project2 is associated to attr1

I found this: Django Docs Complex Lookups with Q objects and through other stackoverflow answers got to here:

Non-working Code (self.multiselect is a list of pks)

query = reduce(operator.and_, (Q(attributes__pk=selection) for selection in self.multiselect))
return queryset.filter(query)

However this code provides an empty Queryset. I started doing some testing in the interactive shell to figure out what went wrong.

Tests

queryset = Project.objects.all()

########## & is not working
queryset.filter(Q(attributes__pk=1) & Q(attributes__pk=2))
# []

########## | has no issues
queryset.filter(Q(attributes__pk=1) | Q(attributes__pk=2))
# [<Project: Project1>, <Project: Project2>, <Project: Project1>]

########## & works on Project but not on Attribute
queryset.filter(Q(title__contains="Project") & Q(title__contains="1"))
# [<Project: Project1>]

It seems like & is not working on the ManytoMany relation between Project and Attribute. Is there a reason for this, is there a simple fix to the code that will make it work properly? Any help would be appreciated.


As a side note, the reduce function is returning exactly what it should

########## Reduce does not create an issue
Q(attributes__pk=1) & Q(attributes__pk=2)
# <Q: (AND: ('attributes__pk', 1), ('attributes__pk', 2))>
reduce(operator.and_, [Q(attributes__pk=selection) for selection in [1,2]])
# <Q: (AND: ('attributes__pk', 1), ('attributes__pk', 2))>

Upvotes: 1

Views: 1426

Answers (2)

gushitong
gushitong

Reputation: 2026

As you say:

########## & is not working
queryset.filter(Q(attributes__pk=1) & Q(attributes__pk=2))

It should be:

attrs = Attribute.objects.filter(pk__in=self.multiselect)
Photo.objects.filter(attributes__in=attrs).annotate(num_attr=Count('attributes')).filter(num_attr=len(attrs))

see this: Django filter queryset __in for *every* item in list

Upvotes: 2

Sachin
Sachin

Reputation: 3674

I don't understand why filter, Q and & works. I thought about negating the negation, which led me to this solution.

Project.objects.exclude(~(Q(attributes__pk=1) & Q(attributes__pk=2)))  

Using exclude and negating the original query filters with Q and & worked.

Upvotes: 1

Related Questions