8

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?

2 Answers 2

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
Sign up to request clarification or add additional context in comments.

5 Comments

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?
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: See this question. Answers might be of interest to you. stackoverflow.com/questions/2218867/…
I saw that question. I based my example off that question.
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.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.