Lets consider follwoing example:
class Animal(models.Model): name = models.CharField(max_length=15) def __unicode__(self): return self.name def make_sound(self): print "Some sound"
I would like to store different types of Animals here: a dog, a cat, a cow. All I am interested with is theirs name, so I don't want to make a separate models for them. Also I would like to make querysets regardless of a kind of an animal - just from all of the animals I have.
>>> cat = Animal(name="Cattie").save() >>> dog = Animal(name="Doggie").save() >>> cow = Animal(name="Cowwie").save() >>> Animal.objects.all() [<Animal: Cattie>, <Animal: Doggie>, <Animal: Cowwie>] >>> for a in Animal.objects.all(): ... a.make_sound() ... Some sound Some sound Some sound
Let's do some metaclass magic python programming.
class InheritanceMetaclass(ModelBase): def __call__(cls, *args, **kwargs): obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) obj.__class__ = obj._get_class() return obj class Animal(models.Model): __metaclass__ = InheritanceMetaclass TYPES=( ('AnimalDog', 'Dog'), ('AnimalCat', 'Cat'), ('AnimalCow', 'Cow')) name = models.CharField(max_length=15) type = models.CharField(max_length=15, choices=TYPES) def __unicode__(self): return self.name def make_sound(self): print "Some sound" def _get_class(self): if not self.type: return self.__class__ else: return getattr(sys.modules[self.__module__], self.type) class AnimalDog(Animal): def make_sound(self): print "Hauu!" class Meta: proxy = True class AnimalCat(Animal): def make_sound(self): print "Meow!" class Meta: proxy = True class AnimalCow(Animal): def make_sound(self): print "Mooo!" class Meta: proxy = True
Drop all from the database for previous example and create new models. Then try this:
>>> Animal(name='Cattie', type='AnimalCat').save() >>> Animal(name='Doggie', type='AnimalDog').save() >>> Animal(name='Cowwie', type='AnimalCow').save() >>> Animal.objects.all() [<AnimalCat: Cattie>, <AnimalDog: Doggie>, <AnimalCow: Cowwie>] >>> for a in Animal.objects.all(): ... a.make_sound() ... Meow! Hauu! Mooo!
Background notes:
- because we create proxy models, there is only one database table for the Animal,
- every time you query for Animal object you get an object casted to appropriate class (not the Animal), but in this same time this will work flawless with django, because of duck typing. AnimalDog will behave exactly the same as parent Animal class in case of using it in django admin and so on,
- we store in the attribute Animal.type a name of django class model name,
- we can easily extend our Animal types by adding new values to Animal.TYPES,
- if you need real model inheritance (e.g. add custom model fields depending on a model type) use django model inheritance - this is not described here case,
- you can also store type as Integer field, but then it needs some further hacking in _get_class() method, see also how to avoid hardcoding with Perfect choice.
No comments:
Post a Comment