A django application that provides a simple way to retrieve models type casted
to their original
>>> pip install polymodels
'polymodels' are in
INSTALLED_APPS += ('django.contrib.contenttypes', 'polymodels')
PolymorphicModel, an abstract model class.
from django.db import models from polymodels.models import PolymorphicModel class Animal(PolymorphicModel): name = models.CharField(max_length=255) def __unicode__(self): return self.name class Mammal(Animal): pass class Dog(Mammal): pass class Reptile(Animal): pass class Snake(Reptile): class Meta: proxy = True
Objects are created the same way as usual and their associated
is saved automatically:
>>> animal = Animal.objects.create(name='animal') >>> mammal = Mammal.objects.create(name='mammal') >>> reptile = Reptile.objects.create(name='reptile') >>> snake = Snake.objects.create(name='snake')
To retreive type casted instances from the
Animal.objects manager you just
have to use the
>>> Animal.objects.select_subclasses() [<Animal: animal>, <Mammal: mammal>, <Reptile: reptile>, <Snake: snake>]
You can also retreive a subset of the subclasses by passing them as arguments to
>>> Animal.objects.select_subclasses(Reptile) [<Reptile: reptile>, <Snake: snake>]
Or directly from subclasses managers.
>>> Reptile.objects.select_subclasses(Snake) [<Snake: snake>]
Note that you can also retrieve original results by avoiding the
>>> Animal.objects.all() [<Animal: animal>, <Animal: mammal>, <Animal: reptile>, <Animal: snake>]
It's also possible to select only instances of the model to which the
manager is attached by using the
>>> Mammal.objects.all() [<Mammal: mammal>]
Each instance of
PolymorphicModel has a
type_cast method that knows how
to convert itself to the correct
>>> animal_snake = Animal.objects.get(pk=snake.pk) <Animal: snake> >>> animal_snake.type_cast() <Snake: snake> >>> animal_snake.type_cast(Reptile) <Reptile: snake>
PolymorphicModel.content_type fields conflicts with one of your
existing fields you just have to subclass
polymodels.models.BasePolymorphicModel and specify which field polymodels
should use instead by defining a
CONTENT_TYPE_FIELD attribute on your model.
This field must be a
from django.contrib.contenttypes.models import ContentType from django.db import models from polymodels.models import BasePolymorphicModel class MyModel(BasePolymorphicModel): CONTENT_TYPE_FIELD = 'polymorphic_ct' polymorphic_ct = models.ForeignKey(ContentType)
How it works
Under the hood
seleted_related to avoid
unnecessary queries and
filter if you pass some classes to it. On queryset
iteration, the fetched instanced are converted to their correct type by calling
BasePolymorphicModel.type_cast. Note that those lookups are cached on class
creation to avoid computing them on every single query.
Prior to Django 1.6 it was not possible to issue a
multiple one-to-one relationships. For example, given the models defined
Animal.objects.select_related('mammal__dog') would throw a strange
TypeError. To work around this issue,
select_subclasses limits such
lookups to one level deep if you're using a version of django with this issue.
Note of the author
I'm aware there's already plenty of existing projects tackling the whole
model-inheritance-type-casting-thing such as django-polymorphic. However
I wanted to implement this feature in a lightweight way: no
__init__ overrides while using django's public API as
much as possible. In the end, this was really just an extraction of
django-mutant's own mecanism of handling this since I needed it as a
standalone app for another project.
- Inspired by a post of Jeff Elmores.