Reputation: 1981
I have two models Parent, Child
class Parent(models.Model):
id = models.IntegerField(...)
class Child(models.Model)
id = models.IntegerField(...)
parent = models.ForeignKey(Parent, ...)
wanted = models.CharField(default="yes")
I want to filter all Parent objects where all the children present with that parent will have 'wanted' as 'yes'
My code:
def containsYes(self):
yes_ids = []
qs = self.get_queryset()
for q in qs:
children = Child.objects.filter(parent_id = q.id)
count = children .count()
if children.filter(wanted = 'yes').count() == count
yes_ids.append(q.id)
return qs.filter(id__contains = yes_ids)
I know that this code is dead inefficient, and want a better solution using only querys
PS: Im new to django
Upvotes: 0
Views: 1646
Reputation: 238
parent = models.ForeignKey(Parent, ..., related_name=“children”)
Parent.objects.filter(children__wanted=True).exclude(children__wanted =False)
Or you can call Parent.objects.exclude(children__wanted=False) case this boolean is not nullable field.
Upvotes: 0
Reputation: 477608
We can exclude Parent
s where there exists a child that is not wanted, so we can work with:
from django.db.models import F, Count, Q
Parent.objects.annotate(
nchild=Count('child')
nchild_wanted=Count('child', filter=Q(child__wanted=True))
).filter(
nchild=F('nchild_wanted')
)
We thus first count the number of related Child
s, and also the number of related Child
s with wanted
is set to True
. Then we filter and only retain Parent
objects where these two annotations are the same.
Since django-3.2, one can make use of .alias(…)
[Django-doc] to prevent counting both in the SELECT
clause and the HAVING
clause:
from django.db.models import F, Count, Q
Parent.objects.alias(
nchild=Count('child')
nchild_wanted=Count('child', filter=Q(child__wanted=True))
).filter(
nchild=F('nchild_wanted')
)
Upvotes: 1