This is a different take on polymorphic inheritance, inspired by SQLAlchemy's approach to the problem.
The common Django approach (e.g. snippets 1031 & 1034, django_polymorphic) is to use a foreign key to ContentType
on the parent model and override save()
to set the right content type automatically. That works fine but it might not always be possible or desirable, for example if there is another field that determines the "real type" of an instance.
In contrast this snippet (which is actually posted and maintained at gist.github) allows the user to explicitly specify the field that determines the real type of an instance. The basic idea and the terminology (polymorphic_on
field, polymorphic_identity
value) are taken from SQLAlchemy.
Some other features:
-
It works for proxy child models too, with almost no overhead compared to non-polymorphic managers (since there's no need to hit another DB table as for multi-table inheritance).
-
It does not override the default (or any other) model Manager. Regular (non-polymorphic) managers and querysets are still available if desired.
-
It does not require extending a custom Model base class, using a custom metaclass, monkeypatching the models or any kind of magic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | # just a demo example
# the actual code is at http://gist.github.com/608595
from django.db import models
from polymorphic import polymorphic_manager
class Player(models.Model):
hitpoints = models.PositiveIntegerField(default=100)
# polymorphic_on field
race = models.SmallIntegerField(choices=enumerate(['Elf', 'Troll', 'Human']))
# keep the default (non-polymorphic) manager
objects = models.Manager()
# a new manager polymorphic on Player.race
objects_by_race = polymorphic_manager(on=race)
def __unicode__(self):
return u'Player(%s)' % self.pk
class Elf(Player):
bows = models.PositiveIntegerField(default=0)
# polymorphic manager for race=0
objects = Player.objects_by_race.polymorphic_identity(0)
def __unicode__(self):
return u'Elf(%s)' % self.pk
class Troll(Player):
axes = models.PositiveIntegerField(default=0)
# polymorphic manager for race=1
objects = Player.objects_by_race.polymorphic_identity(1)
def __unicode__(self):
return u'Troll(%s)' % self.pk
class Human(Player):
# polymorphic manager for race=2
objects = Player.objects_by_race.polymorphic_identity(2)
class Meta:
proxy = True
def __unicode__(self):
return u'Human(%s)' % self.pk
def test():
from random import choice
Player.objects.all().delete()
# create a bunch of random type players
for i in xrange(10):
choice([Elf, Troll, Human]).objects.create()
# retrieval through the polymorphic manager returns instances of the right class
print "Automatically downcast players:", Player.objects_by_race.all()
# retrieval through default Player manager returns Player instances as usual
players = Player.objects.all()
print "Non-downcast players:", players
# but they can be explicitly downcast to the right class
print "Explicitly downcast players:", map(Player.objects_by_race.downcast, players)
# retrieving the instances of a specific class works as expected
print "Elfs:", Elf.objects.all()
print "Trolls:", Troll.objects.all()
print "Humans:", Human.objects.all()
test()
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.