Andrew Louis
Andrew Louis

Reputation: 390

Django filter by many to many with exact same query

Is there any way in django to filter objects with many to many relation by query set or ids list. Get query with exactly same values in many to many. model

class Parent(models.Model):
    name = models.CharField(max_length=1000)
    children = models.ManyToManyField(Child, blank=True)

views

def filter_parents(request):
    children = Child.objects.filter(id__in=[1,2,3])
    parents = Parent.objects.filter(child=child)
    return parents

expected: I am looking for filtered parents with exact same children in many to many field

Upvotes: 1

Views: 3915

Answers (2)

Sergey Pugach
Sergey Pugach

Reputation: 5669

You can use chain filtering for that case.

from django.db.models import Count

children_id_list = [1, 2, 3]
parents = Parent.objects.annotate(count=Count('children')).filter(count=len(children_id_list))

for child_id in children_id_list:
    parents = parents.filter(children__id=child_id)

Or you can use lambda filtering:

c_id_list = [1, 2, 3]
parents = Parent.objects.annotate(count=Count('children')).filter(count=len(children_id_list))
parents = reduce(lambda p, id: parents.filter(child=id), c_id_list, parents)

Or you can use Q() query:

from django.db.models import Count, Q

children_id_list = [1, 2, 3]
parents = Parent.objects.annotate(count=Count('children')).filter(count=len(children_id_list))

query = Q()
for child_id in children_id_list:
    query &= Q(children__id=child_id)
parents = parents.filter(query)

As a result you will get only Parent objects who have all those children in your list of ids only no less no more.

Upvotes: 6

Saad Aleem
Saad Aleem

Reputation: 1745

I think this is what you're looking for:

parents = Parent.objects.filter(children__id__in=[1,2,3])

UPDATE:

I think you need to unpack the child Ids and then do the following.

parents = Parent.objects.annotate(child_id=F('children__id'))
                        .filter(child__id__in=[1,2,3])
                        .order_by('email').distinct('email')

Please note that the order_by is necessary here as not having it would break the distinct operation. Note that you should replace the email with the field that's unique in your User model.

This should accomplish exactly what you're looking for.

Upvotes: 1

Related Questions