- October 3, 2010
- inheritance polymorphic
- 3 (after 3 ratings)
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_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
- Custom Django manager that excludes subclasses by sciyoshi 7 years, 9 months ago
- Ordered items in the database - alternative by Leonidas 8 years, 12 months ago
- Ordered items in the database by Leonidas 9 years ago
- models.py with django_dag models for parts hierarchy by j_syk 4 years, 10 months ago
- Complex Formsets by smagala 7 years, 4 months ago