7

I am still a bit confused about the relation of Proxy models to their Superclasses in django. My question now is how do I get a instance of a Proxy model from an already retrieved instance of the Superclass?

So, lets say I have:

class Animal(models.Model):
   type = models.CharField(max_length=20)
   name = models.CharField(max_length=40)

class Dog(Animal):  
   class Meta:
       proxy = True

   def make_noise(self):  
       print "Woof Woof"  

Class Cat(Animal):  
   class Meta:
       proxy = True

   def make_noise(self):  
       print "Meow Meow"

animals = Animal.objects.all()
for animal in animals:
   if (animal.type == "cat"):
      animal_proxy = # make me a cat
   elif (animal.type == "dog"):
      animal_proxy = # make me a dog
   animal_proxy.make_noise()

OK. So.. What goes into "# make me a cat" that doesn't require a query back to the database such as:

animal_proxy = Cat.objects.get(id=animal.id)

Is there a simple way to create an instance of Cat from an instance of Animal that I know is a cat?

Bubba Raskin
  • 338
  • 4
  • 13

2 Answers2

7

You are trying to implement persistence for an inheritance hierarchy. Using one concrete table and a type switch is a nice way to do this. However I think that your implementation, specifically:

for animal in animals:
   if (animal.type == "cat"): 
      animal_proxy = # make me a cat

is going against the grain of Django. The switching on type shouldn't be extraneous to the proxy (or model) class.

If I were you, I'd do the following:

First, add a "type aware" manager to the proxy models. This will ensure that Dog.objects will always fetch Animal instances with type="dog" and Cat.objects will fetch Animal instances with type="cat".

class TypeAwareManager(models.Manager):
    def __init__(self, type, *args, **kwargs):
        super(TypeAwareManager, self).__init__(*args, **kwargs)
        self.type = type

    def get_query_set(self):
        return super(TypeAwareManager, self).get_query_set().filter(
              type = self.type)

class Dog(Animal):
    objects = TypeAwareManager('dog')
    ...

class Cat(Animal):
    objects = TypeAwareManager('cat')
    ...

Second, fetch subclass instances separately. You can then combine them before operating on them. I've used itertools.chain to combine two Querysets.

from itertools import chain
q1 = Cat.objects.all() # [<Cat: Daisy [cat]>]

q2 = Dog.objects.all() # [<Dog: Bruno [dog]>]

for each in chain(q1, q2): 
    each.make_noise() 

# Meow Meow
# Woof Woof
Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
  • I am aware that I am going against the grain of Django. I do this because Django does not let me do what I want, which is get a list of objects that are stored in the same table but which have different properties without actually chaining together results. I built a type aware manager, but at the superclass level and now I just need to 'cast' returned superclass object instances to be the proxy class objects. Is there any way to do this? – Bubba Raskin Oct 13 '10 at 05:56
  • Actually, I have already done this, but I am currently doing a call back to the database as per: animal_proxy = Cat.objects.get(id=animal.id) I want something like animal_proxy = (Cat) animal . i know there has to be python trickery that can do this for me. – Bubba Raskin Oct 13 '10 at 06:01
  • @Bubba: See this question. Answers might be of interest to you. http://stackoverflow.com/questions/2218867/right-way-to-return-proxy-model-instance-from-a-base-model-instance-in-django – Manoj Govindan Oct 13 '10 at 06:09
  • I saw that question. I based my example off that question. – Bubba Raskin Oct 13 '10 at 07:33
  • 1
    I am already getting back mixed Cat+Dog results by hacking queryset on the Animal class. I just want to avoid making the calls to the database when instantiating Cat+Dog from an Animal instance returned by the queryset. – Bubba Raskin Oct 13 '10 at 07:36
3

I would do:

def reklass_model(model_instance, model_subklass):

    fields = model_instance._meta.get_all_field_names()
    kwargs = {}
    for field_name in fields:
        try:
           kwargs[field_name] = getattr(model_instance, field_name)
        except ValueError as e: 
           #needed for ManyToManyField for not already saved instances
           pass

    return model_subklass(**kwargs)

animals = Animal.objects.all()
for animal in animals:
   if (animal.type == "cat"):
      animal_proxy = reklass_model(animal, Cat)
   elif (animal.type == "dog"):
      animal_proxy = reklass_model(animal, Cat)
   animal_proxy.make_noise()

# Meow Meow
# Woof Woof

I have not tested it with "the zoo" ;) but with my own models seems to work.

fero
  • 498
  • 4
  • 11