Say I have models:
class Animal(models.Model): type = models.CharField(max_length=255) class Dog(Animal): def make_sound(self): print "Woof!" class Meta: proxy = True class Cat(Animal): def make_sound(self): print "Meow!" class Meta: proxy = True
Let's say I want to do:
animals = Animal.objects.all() for animal in animals: animal.make_sound()
I want to get back a series of Woofs and Meows. Clearly, I could just define a make_sound in the original model that forks based on animal_type, but then every time I add a new animal type (imagine they're in different apps), I'd have to go in and edit that make_sound function. I'd rather just define proxy models and have them define the behavior themselves. From what I can tell, there's no way of returning mixed Cat or Dog instances, but I figured maybe I could define a "get_proxy_model" method on the main class that returns a cat or a dog model.
Surely you could do this, and pass something like the primary key and then just do Cat.objects.get(pk = passed_in_primary_key). But that'd mean doing an extra query for data you already have which seems redundant. Is there any way to turn an animal into a cat or a dog instance in an efficient way? What's the right way to do what I want to achieve?
def str(self): is a python method which is called when we use print/str to convert object into a string. It is predefined , however can be customised.
The __str__ method in Python represents the class objects as a string – it can be used for classes. The __str__ method should be defined in a way that is easy to read and outputs all the members of the class. This method is also used as a debugging tool when the members of a class need to be checked.
The main Usage of a proxy model is to override the main functionality of existing Model. It is a type of model inheritance without creating a new table in Database. It always query on original model with overridden methods or managers.
To create a new instance of a model, instantiate it like any other Python class: class Model (**kwargs) The keyword arguments are the names of the fields you've defined on your model. Note that instantiating a model in no way touches your database; for that, you need to save() .
The Metaclass approach proposed by thedk is indeed a very powerful way to go, however, I had to combine it with an answer to the question here to have the query return a proxy model instance. The simplified version of the code adapted to the previous example would be:
from django.db.models.base import ModelBase class InheritanceMetaclass(ModelBase): def __call__(cls, *args, **kwargs): obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) return obj.get_object() class Animal(models.Model): __metaclass__ = InheritanceMetaclass type = models.CharField(max_length=255) object_class = models.CharField(max_length=20) def save(self, *args, **kwargs): if not self.object_class: self.object_class = self._meta.module_name super(Animal, self).save( *args, **kwargs) def get_object(self): if self.object_class in SUBCLASSES_OF_ANIMAL: self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class] return self class Dog(Animal): class Meta: proxy = True def make_sound(self): print "Woof!" class Cat(Animal): class Meta: proxy = True def make_sound(self): print "Meow!" SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])
The advantage of this proxy approach is that no db migration is required upon creation of new subclasses. The drawback is that no specific fields can be added to the subclasses.
I would be happy to have feedback on this approach.
the only way known to the human kind is to use Metaclass programming.
Here is short answer:
from django.db.models.base import ModelBase class InheritanceMetaclass(ModelBase): def __call__(cls, *args, **kwargs): obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) return obj.get_object() class Animal(models.Model): __metaclass__ = InheritanceMetaclass type = models.CharField(max_length=255) object_class = models.CharField(max_length=20) def save(self, *args, **kwargs): if not self.object_class: self.object_class = self._meta.module_name super(Animal, self).save( *args, **kwargs) def get_object(self): if not self.object_class or self._meta.module_name == self.object_class: return self else: return getattr(self, self.object_class) class Dog(Animal): def make_sound(self): print "Woof!" class Cat(Animal): def make_sound(self): print "Meow!"
and the desired result:
shell$ ./manage.py shell_plus From 'models' autoload: Animal, Dog, Cat Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) [GCC 4.4.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> dog1=Dog(type="Ozzie").save() >>> cat1=Cat(type="Kitty").save() >>> dog2=Dog(type="Dozzie").save() >>> cat2=Cat(type="Kinnie").save() >>> Animal.objects.all() [<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>] >>> for a in Animal.objects.all(): ... print a.type, a.make_sound() ... Ozzie Woof! None Kitty Meow! None Dozzie Woof! None Kinnie Meow! None >>>
How does it work?
More information about Metaclass in Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With