12

I'm developing django application, and I have such a model structure

class Animal(models.Model):
    aul = models.ForeignKey(Aul)
    age = models.IntegerField()

    def __unicode__(self):
        return u'Animal'

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

    def __unicode__(self):
        return u'Sheep'

And I pass animal_set to template and output every object like this {{ animal }}. It outputs Animal, but I created objects of sheep type and want to use __unicode__ method of sheep not of animal.

Do polymorphism work in Django models? I have found several answers, but there are snippets of code to write inside models, but I'm interested in native solutions.

Braiam
  • 1
  • 11
  • 47
  • 78
ardakshalkar
  • 625
  • 2
  • 7
  • 21
  • Check out [django-polymorphic](https://github.com/chrisglass/django_polymorphic), which is designed for this use case. It also works when the model is fetched via foreignkeys, ManyToManyFields, etc.. – vdboor May 21 '13 at 14:15
  • Don't do it! :P See my take below: http://stackoverflow.com/a/20353347/539490 – AJP Dec 03 '13 at 14:11

5 Answers5

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'
>>> 
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
tuscias
  • 500
  • 2
  • 10
  • 1
    This mainly what djanogo-polymorphic-models does, but it provides the functionality a little bit more generic.... – Bernhard Vallant Mar 19 '11 at 15:16
  • I think the as_leaf_class method will hit the db every time. What I did is to rather set the class directly using self.__class__ = model – Kritz Oct 13 '16 at 19:08
  • @johan don't change code. In 2011, on Django pre 1.6, `get_query_set` was the right thing. If you want to inform visitors that there has been a rename, do so in a comment. – Emile Bergeron Oct 14 '16 at 02:05
  • @Emile I've added a comment next to the change, but I would argue code snippets should use the latest Django syntax. The question was posted over 5 years ago and in all likelihood somebody will copy and paste the code using Django 1.8+ and not bother to read the comments below – Kritz Oct 14 '16 at 06:03
  • @Johan changing the code goes against the author's intent and SO is not a code snippet service. If a person copy paste old code that he don't understand, that's he's problem. I've changed the edit to go the other way around, informing of the rename in a comment rather than changing the code. I also added a disclaimer. – Emile Bergeron Oct 14 '16 at 15:10
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.

Tomasz Zieliński
  • 16,136
  • 7
  • 59
  • 83
  • Don't know why was this answer downvoted since it's accurate regarding the problem. However a bit incomplete, but still accurate. – Luis Masuelli Jun 03 '14 at 14:12
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
  • 26,547
  • 23
  • 88
  • 127
  • you're suggesting in-parent (i.e. Single Table) Inheritance (in contrast to the desired Joined Table Inheritance), but you don't know if the actual model is that or only an example. there's an academic debate between STI and JTI. You should help him about efficient way to develop a JTI unless the contrary is said. – Luis Masuelli Jun 03 '14 at 14:25
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!

Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
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

Community
  • 1
  • 1
Filipe Correia
  • 5,415
  • 6
  • 32
  • 47