Search code examples
pythondjangoobjectoop

When using Generics types, how can I call an Attribute of an inherited class on the Base class instance with Python / Django?


Let's say I have a base Generic class called person to group all other persons that extend it:

class Person(models.Model):
    name = models.CharField(...)

And the class Worker, that extends the Person:

class Worker(Person):
    card_id = models.CharField(...)

When I access all Person objects I also see the Workers, but lets say that from there I want to do this:

worker = Worker(name='Example', card_id='1234')

person = Person.objects.all().first()

Let's say that for some reason I want to use the Person class and call an Attribute o Worker:

[in] person.card_id
# Somehow expecting this result:
[out] '1234

Is it possible, is it viable and how do I do it?


Solution

  • I'm afraid this is not possible, even when you can guarantee that the Person object you retrieved (ie. Person.objects.all().first()) is also a Worker.

    There three styles of inheritance possible in Django: Abstract Base classes, Proxy Models, and Multi-Table Inheritance

    In this case, you are using Multi-Table Inheritance. As the name suggest, each model will receive its own table in the database and can therefore also be queried.

    When querying for Person objects, what you will end up with are Person instances, not Worker instances or any other child class instance. Since the card_id field is not defined in the Person model, but rather in one of its child models, you will get an error when trying to retrieve the card_id value of Person instance, even though the instance is also a Worker.

    However, if you know that your Person instance is also a Worker, you can retrieve it via:

    person = Person.objects.all().first()
    worker = person.worker
    

    This works because the inheritance relationship creates a OneToOneField between the child and each of its parents. However, note that this will give an error in case the person instance is not actually a Worker.

    One library that might help is the InheritanceManager from django-model-utils.

    By attaching the InheritanceManager to your base class model such as:

    from model_utils.managers import InheritanceManager
    
    class Person(models.Model):
        objects = InheritanceManager()
        # ...
    

    You can then use the .select_subclasses filter to filter a queryset of the Parent model to only include the objects of a particular child class. For example to only select the persons that are also workers, you can do the following query:

    persons_worker = Person.objects.select_subclasses(Worker)
    

    and you should never run into troubles when trying to do retrieve the card_id for any of the instances in persons_worker.