I'd like to filter objects on a ManyToManyField using two attributes of the objects that is pointed to by ManyToManyFeild. See the model below, this is a classical user-items relationship
from django.db import models
class Person(models.Model):
# user = models.OneToOneField(User)
name = models.CharField(max_length=50, blank=False, null=False, default=None, unique=True)
ratings = models.ManyToManyField('Rating',
related_name="persons"
)
class Movie(models.Model):
title = models.CharField(max_length=50, blank=False, null=False, default=None, unique=True)
def __str__(self):
return f"{self.title}"
class Rating(models.Model):
movie = models.ForeignKey(Movie, models.CASCADE)
value = models.SmallIntegerField(blank=False, null=False, default=None)
def __str__(self):
return f"{self.movie}:{self.value}"
So, I'd like to find all users who gave above certain rating to Movie1 AND above certain rating to Movie2.
I can do this with cascading filters:
>>> Person.objects\
.filter(ratings__movie__title='Movie1', ratings__value__gte=5)\
.filter(ratings__movie__title='Movie2', ratings__value__gte=1)\
.all()
[[A<QuerySet [<Person: Person object (1)>]>
But I am looking for a way to filter on any arbitrary number of ratings, not just 2.
I have tried with Q (in the case single filter()
for all cases), but the following doesn't work:
>>> Person.objects.filter(
Q(ratings__movie__title='Movie1') & Q(ratings__value__gte=5),\
Q(ratings__movie__title='Movie2') & Q(ratings__value__gte=1)
).all()
<QuerySet []>
Update:
I figured out, that since filters are lazy, I could cascade a required number of them them in a loop and then call .all()
in the end, like this:
my_filters = [("Movie1", 2), ("Movie2", 3)]
f = Person.objects
for m,r in my_filters:
f = f.filter(ratings__movie__title=m, ratings__value__gte=r)
selected_persons = f.all()
The question of course remains, how efficient is that query... So the question remains on how to do this with the best efficiency.
Thanks for help!