Django multi-table-inheritance + django-model-utils query optimization
I have some troubles to optimize ManyToManyField
in child model with concrete inheritance.
Assume I have models:
class CoreModel(models.Model):
parent = ForeignKey(Parent)
class Parent(models.Model):
pass
@cached_property
def child(self):
return Parent.objects.get_subclass(pk=self.pk)
class ChildA(Parent):
many = ManyToManyField(SomeModel)
class ChildB(Parent):
many = ManyToManyField(SomeModel)
In template I make a call:
{% for m in parent.child.many.all %}
{{ m.title }}
{% endfor %}
And this performs extra query for each instance. In my view I've tried to optimize in this way :
core_instance = CoreModel.objects.prefetch_related(
Prefetch(
"parent",
queryset=Parent.objects.select_subclasses()\
.prefetch_related("many")
)
However I'm getting error:
ValueError: Cannot query ChildAObject(some_pk). Must be "ChildB" instance.
As I understand select_subclasses()
"converts" parent class into child class. Both child classes have same field but still getting error.
What I'm missing?
So, finally, after bunch of experiments and code review I found a simple solution:
queryset_to_optimize = CoreModel.objects.prefetch_related(
Prefetch(
"parent",
queryset=Parent.objects.prefetch_related("many")\
.select_subclasses()
)
)
And voila no more extra/similart queries related to queryset_to_optimize
call.
In template I would perform for loop in
without child call but directly acces to prefetched objects:
{% for obj in queryset_to_optimize %}
...some code related to CoreModel...
<!-- Direct access to parent.many objects -->
{% for m in obj.parent.many.objects.all %}
... some code related to many field ...
{% endfor %}
{% endfor %}
Result: query count dropped from 1000+ to 13 queries with 300+ objects on page and each of objects has multiple relations including M2M and FK involving Multi-Table Inherited models.
P.S. Honestly, I did't inspect django-model-utils
deeply and, particularly, select_subclasses()
method to say exactly how it manages such joins.