Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ORM inheritance with ManyToMany field

Let's say I have following ORM classes (fields removed to simplify):

class Animal(models.Model):
    say = "?"

    def say_something(self):
        return self.say

class Cat(Animal):
    self.say = "I'm a cat: miaow"

class Dog(Animal):
    self.say = "I'm a dog: wuff"

class Animals(models.Model):
    my_zoo = models.ManyToManyField("Animal")

When I add some animals to my zoo:

cat = Cat()
cat.save()
dog = Dog()
dog.save()

animals.my_zoo.add(cat)
animals.my_zoo.add(dog)

for animal in animals.my_zoo.all():
    print animal.say_something()

... I would expect following result:

I'm a cat: miaow, I'm a dog: wuff

but instead, all I've got is the instances of general Animal object, unable to say anything but "?".

How to achieve the true object inheritance and later polymorphism when the object is retreived from db?

like image 487
edkirin Avatar asked Nov 10 '10 18:11

edkirin


1 Answers

Model inheritance in django does not add any type information to the base class. So it is not really possible to down-cast objects down from Animal() to their appropriate forms.

Inheritance is used only to map fields on inherited model back to parent models. So if Animal has field name, the same field will exist on Cat and when you save Cat, the animal will be updated.

Inheritance works by adding a OneToOne relation:

class Animal(Model):
    name = CharField()

class Cat(Model):
    animal_ptr = OneToOneField(Animal)

Cat(name='Murky').save()

print repr(list(Animals.objects.all()))

: [Animal(name='Murky')]

Technically in your situation it is even possible for Animal() to be both Dog() and Cat() at the same time:

animal = Animal()
animal.save()

Cat(animal_ptr=animal).save()
Dog(animal_ptr=animal).save()

The way to solve your problem would be to add a field subtype or similar to your Animal() object and implement downcasting function:

class Animal(Model):
    subtype = CharField()

    def downcast(self):
        if self.subtype == 'cat':
            return self.cat
            # The self.cat is a automatic reverse reference created
            # from OneToOne field with the name of the model

for animal in Animal.objects.all().select_related('dog', 'cat', ...)]:
    animal.downcast().say_something()

A few useful reads on stack overflow with similar topics: Generic many-to-many relationships he hood. How to do Inheritance Modeling in Relational Databases?

like image 66
Ski Avatar answered Sep 21 '22 15:09

Ski