Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Filter Base Class by Child Class Names

I have the following model inheritance structure in Django:

class Parent(models.Model):
    # stuff

class A(Parent):
    # stuff

class B(Parent):
    # stuff

class C(Parent):
    # stuff

and the list goes on.

I am using InheritanceManager of django-model-utils to filter objects like:

Parent.objects.filter(foo=bar).select_subclasses()

This works well when I want to filter all subclasses. What I want to do is to filter A and B objects, but not C objects. I want to do this with a single query like

Parent.objects.filter(foo=bar, __class__.__name__=A, __class__.__name__=B).select_subclasses()

Is it possible to do such a filtering operation, and if possible how?

like image 924
Ozgur Akcali Avatar asked Nov 12 '12 16:11

Ozgur Akcali


1 Answers

It certainly is possible to do this in a single query!

It all comes from the way Django builds up fields to refer to the relationships between the parent and child models. Each child has a parent-ref of some sort, which has a related_name. You can query on these.

You'll probably find it easiest to start up a ./manage.py shell, and import your parent model class, and then perform a nonsense query:

Parent.objects.filter(foo='bar')

This should show you the available fields for querying: you can then work out how to build the query:

Parent.objects.filter(is_active=True).filter(
    models.Q(a__isnull=False) |
    models.Q(b__isnull=False)
).select_subclasses()

This will perform a single query that will fetch all objects of A, all objects of B, that have is_active set to True, and downcast them.

The thing it is worth pointing out is that .select_subclasses() is unable to detect which models are going to be included, so it joins in all subclasses.

However... you can pass values to select_subclasses so it only joins (and downcasts) to those subclasses:

Parent.objects.filter(is_active=True).filter(
    models.Q(a__isnull=False) |
    models.Q(b__isnull=False)
).select_subclasses('a', 'b')

Now, we are no longer joining to tables "c" through "z"!

like image 185
Matthew Schinckel Avatar answered Nov 14 '22 13:11

Matthew Schinckel