djangodjango-models

How to go from a Model base to derived class in Django?


Assuming a simple set of inherited Model classes, like this:

class BaseObject(models.Model): 
    some_field = models.SomeField(...)

class AwesomeObject(BaseObject): 
    awesome_field = models.AwesomeField(...)

class ExcellentObject(BaseObject): 
    excellent_field = models.ExcellentField(...)

and a query that looks like this:

found_objects = BaseObject.objects.filter(some_field='bogus')

What's the best way to take each found object and turn it back into it's derived class? The code I'm using now is like this:

for found in found_objects:
    if hasattr(found, 'awesomeobject'): 
        ProcessAwesome(found.awesomeobject)
    elif hasattr(found, 'excellentobject'): 
        ProcessExcellent(found.excellentobject): 

But, it feels like this is an abuse of "hasattr". Is there a better way to do this without creating an explicit "type" field on the base class?


Solution

  • That's the best way that I know of. Unfortunately, inheritance is a little clunky in this regard. Multiple table inheritance is basically just a one-to-one relationship between the parent model and the extra fields the child adds, which is why that hasattr trick works. You can think of each of those as a OneToOneField attribute on your parent model. When you think of it that way, Django has no way of knowing which child to return or even if to return a child, so you have to handle that logic yourself:

    I tend to create a method on the parent such as get_child, which simply cycles through the attributes and returns the one that pops:

    class BaseObject(models.Model):
        some_field = models.SomeField(...)
    
        def get_child(self):
            if hasattr(self, 'awesomeobject'): 
                return ProcessAwesome(found.awesomeobject)
            elif hasattr(self, 'excellentobject'): 
                return ProcessExcellent(found.excellentobject):
            else:
                return None
    

    At least then, you can just call found.get_child(), and maybe forget about the hackery that gets you there.