Accessing the Child Class In Django Multi-Table Inheritance

Django has 3 different ways to deal with model inheritance:

The differences are described pretty well in the linked documentation so I am not going to recapitulate them here. I just want to talk about a specific pattern I have been playing with for Multi-table inheritance.

In Multi-table inheritance the base class has its own separate table. This makes it possible to query all the objects from all the subclasses at once.

So if we have:

class Animal(models.Model):
    def speak(self):
        print "Generic Animal Sound"
 
class Dog(Animal):
    def speak(self):
        print "Woof!"
 
class Cat(Animal):
    def speak(self):
        print "Meow!"

The Animals.objects.all() will find all the Cats and Dogs in the DB. The trouble is they will all be of type Animal and so if you run:

Dog.objects.create()
Dog.objects.create()
Cat.objects.create()
 
for animal in Animal.objects.all():
    animal.speak()

you get:

Generic Animal Sound
Generic Animal Sound
Generic Animal Sound

There is a way to access the child class specific information. animal.dog.speak() will produce Woof! if animal is actually a Dog. It will however toss an exception if it is a Cat. Worse is that testing this out generates some useless Db queries which becomes even more impractical as the number of subclasses increases. One approach is to store the name of the child class with the base class so you only have the one extra query needed to grab the data for the child object. If we create an Abstract base class like:

class KnowsChild(models.Model):
    # Make a place to store the class name of the child
    _my_subclass = models.CharField(max_length=200) 
 
    class Meta:
        abstract = True
 
    def as_child(self):
        return getattr(self, self._my_subclass)
 
    def save(self, *args, **kwargs):
        # save what kind we are.
        self._my_subclass = self.__class__.__name__.lower() 
        super(KnowsChild, self).save(*args, **kwargs)

And derive Animal from it.

class Animal(KnowsChild):
    def speak(self):
        print "Generic Animal Sound"

Then we can run:
Dog.objects.create()
Dog.objects.create()
Cat.objects.create()
for animal in Animal.objects.all():
    animal.as_child().speak()

and get:
Woof!
Woof!
Meow!

I am still honestly not sure if I like this or not but its working out well so far for a reporting system I am making where ReportItems can be one of several different types but sometimes I just want to work with the elements in the base class. A lot depends on how often you need to know the specifics of the child class.

3 Replies to “Accessing the Child Class In Django Multi-Table Inheritance”

  1. I believe if you use select_related with the original query you will avoid the multiple DB calls, since they are onetoone relations.
    Animal.objects.select_related(“cat”,”dog”).all()

    This would reduce the number of db calls to the one original one. But it doesn’t solve the messiness, since you have to check the specific classes (have to check hasattr(animal, ‘dog’)). Also if you have lots of child classes it would require many joins. In that case it may be better to just do a simpler query to get the animal and then another to get the specific child.

  2. Thanks! Using your solution, Works well, a bit hackish, but doing inheritance with sql tables is also ;-)…

    Just to contribute, if you want that calling some methods on your base class instance always tries to call the method from the child class, I wrote a decorator:

    
    def always_as_child(fn):
        """ Tries to run child model method if relevant
    
        should be applied on KnowsChild child class
        """
        def f(self, *args, **kwargs):
            child_self = self.as_child()
            f_parent = getattr(self.__class__, fn.__name__)
            f_child = getattr(child_self.__class__, fn.__name__)
    
            if f_parent != f_child:
                return f_child(child_self, *args, **kwargs)
            else:
                return fn(self, *args, **kwargs)
        return f
    

    You should declare it on relevant methods on parent class:

    
    class Animal(models.Model):
        @always_as_child
        def speak(self):
            print "Generic Animal Sound"
    

    There is no need to decorate child methods.

    Now if you do something like:

    
    for i in Animal.objects.all():
         i.speak()
    

    You will see your animals produce their very own sound. If you don’t redefined the speak() in child classes, it will just call the parent method.

Leave a Reply

Your email address will not be published. Required fields are marked *