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.


Posted

in

by

Tags:

Comments

Leave a Reply

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