Django: select_related with multi table inheritance

Viewed 1452

I have the following model structure:

class A(models.Model):
    prop_a = models.CharField(max_length=255)

class B(A):
    prop_b = models.CharField(max_length=255)

class C(A):
    prop_c = models.CharField(max_length=255)

class D(models.Model):
    fk = models.ForeignKey('A')

So essentially, I have a model (D) which has a foreign key to an 'abstract' model (A) which is subclassed by B and C.

Now, when I run D.objects.all().select_related(), only the properties of Aare queried. I assume this is because at query time, Django does not know which child class the fk is an instance of (and neither do I in my current structure).

Is there any way to query the properties of the child class without changing the model structure?

I also tried prefetch_related and tried using an InheritanceManager from django-model-utils, both to no avail.

Edit: To clarify, I am looking for a way to iterate over objects of D, accessing the concrete values of fk (which are either B or C objects) while only hitting the database once.

Running D.objects.all().select_related() produces the query

'SELECT "mapping_d"."id", "mapping_d"."fk_id", "mapping_a"."id", "mapping_a"."prop_a" FROM "mapping_d" INNER JOIN "mapping_a" ON ("mapping_d"."fk_id" = "mapping_a"."id")'

Let's say one of those is an object d of type D whose property fk is of type C.

Now, when I access the value d.fk.c, Django runs an additional query to get the properties of C:

'SELECT "mapping_a"."id", "mapping_a"."prop_a", "mapping_c"."a_ptr_id", "mapping_c"."prop_c" FROM "mapping_c" INNER JOIN "mapping_a" ON ("mapping_c"."a_ptr_id" = "mapping_a"."id") WHERE "mapping_c"."a_ptr_id" = 3'

I want to avoid this additional query per object of D. Using an InheritanceManager in the proposed way does not seem to do that.

1 Answers

I use Inheritance Manager usualy and runs as expected.

1) Remember to set the inheritance manager on A:

class A(models.Model):
    # ...
    objects = InheritanceManager()

2) Make the right query:

some_d_object = D.objects.get( pk = 1 )
related = some_d_object.fk.all().select_related()

Noticte than, your query D.objects.all().select_related() will returns D objects. Nobody inherits from D, then, no subclasses, just D objects.

3) Enjoy inheritance.

Edited I edit this quetion becase the OP comment.

With this pattern, Django performs an additional DB query when I access the related object. Since I have many D objects, this gets quite expensive. Do you know if there is any way to fetch them all at once? –

Ok, le'ts try to write an answer, because I really don't understand exactely witch objects OP is looking for. Question is fine because he has written almost a Minimal, Complete, and Verifiable example but I don't know expected values, Let's try with some samples.

1) If you want all objects, as easy as:

all_objects = A.objects.all().select_subclasses()

2) If you want to filter, then, do it. I write a sample:

filtered_objects = A.objects.filter( d__pk = 1 ).select_subclasses()

3) Get all subclasses, and also, D class, in a single query:

Unfortunatelly you can not apply prefetch_related (may be you can but I don't know how to do it). Then you need to massage data with itertools.

all_objects_and_D = list(
                     A
                     .objects
                     .select_related('D')
                     .select_subclasses()
                     .order_by( 'D__pk' )
                     )
#at this point use itertools to massage your data
keyfunc = lambda x: x.D
data = [  { k, list(g) } 
          for k, g in 
          itertools.groupby(all_objects_and_D, key=keyfunc )
       ]
Related