[Solved]-Polymorphism in Django models

9đź‘Ť

At the time of writing, Django latest version was 1.2

But it needs some additional elements to work.

You need to assign a custom models.Manager object for each animal model which will call its own custom QuerySet object.

Basically, instead of returning Animal instances (this is what you get), SubclassingQuerySet calls as_leaf_class() method to check if item’s model is Animal or not – if it is, then just return it, otherwise perform search in its model context. Thats it.

#models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet


class SubclassingQuerySet(QuerySet):
    def __getitem__(self, k):
        result = super(SubclassingQuerySet, self).__getitem__(k)
        if isinstance(result, models.Model):
            return result.as_leaf_class()
        return result

    def __iter__(self):
        for item in super(SubclassingQuerySet, self).__iter__():
            yield item.as_leaf_class()


class AnimalManager(models.Manager):
    def get_query_set(self):  # Use get_queryset for Django >= 1.6
        return SubclassingQuerySet(self.model)


class Animal(models.Model):
    name = models.CharField(max_length=100)
    content_type = models.ForeignKey(ContentType, editable=False, null=True)
    objects = AnimalManager()

    def __unicode__(self):
        return "Animal: %s" % (self.name)

    def save(self, *args, **kwargs):
        if not self.content_type:
            self.content_type = ContentType.objects.get_for_model(self.__class__)
        super(Animal, self).save(*args, **kwargs)

    def as_leaf_class(self):
        content_type = self.content_type
        model = content_type.model_class()
        if model == Animal:
            return self
        return model.objects.get(id=self.id)


class Sheep(Animal):
    wool = models.IntegerField()
    objects = AnimalManager()

    def __unicode__(self):
        return 'Sheep: %s' % (self.name)

Testing:

>>> from animals.models import *
>>> Animal.objects.all()
[<Sheep: Sheep: White sheep>, <Animal: Animal: Dog>]
>>> s, d = Animal.objects.all()
>>> str(s)
'Sheep: White sheep'
>>> str(d)
'Animal: Dog'
>>> 
👤tuscias

3đź‘Ť

You might be successful by accessing {{ animal.sheep }} – the model inheritance is not what you would think, there is a heavy metaclass machinery under the cover that “converts” such inheritance into an implicit OneToOneField relationship.

1đź‘Ť

There’s a very simple django app called django-polymorphic-models that helps you with that. It will provide you with a downcast() method on the model itself that will return your “child” object, as well as a special queryset class to deal with these problems!

It can also be very useful to know that using select_related() on the base model’s queryset will also get the child objects, that are referenced through a OneToOneField, which can be a nice performance boost sometimes!

1đź‘Ť

I would recommend using Django proxy models, e.g. if you have the base model Animal which is subclassed by Sheep and Horse you would use:

class Animal(models.Model):
    pass

class Horse(Animal):
    class Meta(Animal.Meta):
        proxy = True

class Sheep(Animal):
    class Meta(Animal.Meta):
        proxy = True

This is not what Proxy models are intended for but I wouldn’t recommend using Django polymorphism unless you need the benefits of storing model specific data in separate tables. If you have a hundred horse specific attributes that all have default values stored in the database, and then only have 2 horse objects, but have a million sheep, you have a million rows, each with a hundred horse specific values you don’t care about, but again this is only really relevant if you don’t have enough disk space, which is unlikely. When polymorphism works well it’s fine, but when it doesn’t it’s a pain.

👤AJP

0đź‘Ť

You should check this answer: https://stackoverflow.com/a/929982/684253

The solution it proposes is similar to using django-polymorphic-models, that was already mentioned by @lazerscience. But I’d say django-model-utils is a little bit better documented than django-polymorphic, and the library is easier to use. Check the readme under “Inheritance Manager”: https://github.com/carljm/django-model-utils/#readme

Leave a comment