Reputation: 5492
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?
Upvotes: 9
Views: 3580
Reputation: 35649
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"!
Upvotes: 5
Reputation: 318
Another hacky solution that's worked for me, without adding more information to the database:
letters = Parent.objects.filter(foo=bar)
for letter in letters:
if type(letter) == C:
letters.exclude(id=c.id)
Or, alternatively, if you add information to the models without adding fields:
class Parent(models.Model):
# stuff
class A(Parent):
code = 'A'
# stuff
class B(Parent):
code = 'B'
# stuff
class C(Parent):
code = 'C'
# stuff
And then...
letters = Parent.objects.filter(foo=bar)
for letter in letters:
if letter.child().code == 'C':
letters.exclude(id=c.id)
Works for my purposes, though still a hack...
Upvotes: 0
Reputation: 27871
Its usually not considered the best to setup inheritable like that because then each SQL query will have to do a join. That can make your performance much slower. To increase performance, you can use abstract
Meta value:
class Parent(models.Model):
# stuff
class Meta:
abstract = True
This way each table is independent and hence performance will be faster.
If that is no applicable to you, I don't think its possible to do something like that in a single query because the fields within the table/model do not contain any information in what table are they located. In that case you will most likely have to somehow subclass the InheritanceManager
however I'm not sure what to do there. If you do, using content_types then might be of some help.
If that is too much work, then you can always do a simple hack (more of a monkey patching...). I know its not pretty but it will work:
class Parent(models.Model):
# stuff
table = models.CharField(max_length=8, default='parent')
class A(Parent):
# stuff
table = models.CharField(max_length=8, default='a')
class B(Parent):
# stuff
table = models.CharField(max_length=8, default='b')
# and then something like
# please note that I've never used django-model-utils
# so don't know the correct syntax
Parent.objects.filter(foo=bar, table__in=['parent', 'a']).select_subclasses()
Upvotes: 0