Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Right way to return proxy model instance from a base model instance in Django?

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?

like image 389
sotangochips Avatar asked Feb 07 '10 23:02

sotangochips


People also ask

What is def __ str __( self in Django?

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.

What is __ Str__ in Django model?

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.

What is proxy model inheritance in Django?

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.

How do I create an instance of a Django model?

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() .


2 Answers

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.

like image 115
Samuel Avatar answered Sep 22 '22 19:09

Samuel


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?

  1. Store information about class name of the animal - we use object_class for that
  2. Remove "proxy" meta attribute - we need to reverse relation in Django (the bad side of this we create extra DB table for every child model and waste additional DB hit for that, the good side we can add some child model dependent fields)
  3. Customize save() for Animal to save the class name in object_class of the object that invoke save.
  4. Method get_object is needed for referencing through reverse relation in Django to the Model with name cached in object_class.
  5. Do this .get_object() "casting" automatically every time Animal is instantiate by redefining Metaclass of Animal model. Metaclass is something like a template for a class (just like a class is a template for an object).

More information about Metaclass in Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html

like image 41
thedk Avatar answered Sep 23 '22 19:09

thedk