2010-02-07 14 views
24

Decir que tengo modelos:Forma correcta de devolver la instancia del modelo de proxy desde una instancia de modelo base en Django?

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 

Digamos que quiero hacer:

animals = Animal.objects.all() 
for animal in animals: 
    animal.make_sound() 

Quiero volver a una serie de ladridos y maullidos. Claramente, podría definir un make_sound en el modelo original que se basa en animal_type, pero cada vez que agrego un nuevo tipo de animal (imagino que están en diferentes aplicaciones), tendría que entrar y editar esa función make_sound . Preferiría simplemente definir modelos de proxy y hacer que ellos mismos definan el comportamiento. Por lo que puedo decir, no hay forma de devolver instancias mixtas de Cat o Dog, pero pensé que tal vez podría definir un método "get_proxy_model" en la clase principal que devuelve un modelo de gato o de perro.

Seguramente usted podría hacer esto, y pasar algo así como la clave principal y luego simplemente hacer Cat.objects.get (pk = passed_in_primary_key). Pero eso significaría hacer una consulta adicional para los datos que ya tiene que parecen redundantes. ¿Hay alguna manera de convertir a un animal en una instancia de gato o perro de manera eficiente? ¿Cuál es la forma correcta de hacer lo que quiero lograr?

+0

Puede aplicar make_sound al modelo Animal y agregarle un sonido = models.charField() también. – monkut

+2

Mi ejemplo es muy simple: lo que estoy tratando de hacer requiere un montón de trabajo que depende del tipo y no se puede almacenar con el modelo. – sotangochips

Respuesta

1

Quizás pueda hacer que los modelos Django sean polimórficos utilizando el enfoque descrito here. Ese código está en las primeras etapas de desarrollo, creo, pero vale la pena investigarlo.

4

la única forma conocida para el ser humano es usar la programación de Metaclass.

Aquí está la respuesta corta:

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!" 

y el resultado deseado:

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 
>>> 

¿Cómo funciona?

  1. Almacenar información acerca de la clase nombre del animal - que utilizamos object_class para que
  2. Quitar "proxy" atributo meta - necesitamos relación inversa en Django (la mala lado de esto creamos adicional DB mesa para todos los modelos de niños y residuos dB adicionales para ese golpe, el lado bueno que se puede añadir un poco de modelo infantil campos dependientes)
  3. Personalizar save() para Animal para guardar la clase nombre en object_class del objeto que invoca save.
  4. El método get_object es necesario para hacer referencia a mediante relación inversa en Django con el modelo con el nombre almacenado en object_class.
  5. Haga esto .get_object() "fundición" automáticamente cada vez que se instancia Animal por instancia al redefiniendo la Metaclass of Animal modelo . Metaclass es algo así como una plantilla para una clase (al igual que una clase es una plantilla para un objeto).

Más información sobre MetaClass en Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html

0

Esta respuesta puede ser la pregunta un tanto eludiendo lado, ya que no utiliza modelos de proxy. Sin embargo, ya que la pregunta se refiere, no deje que uno escribe lo siguiente (y sin tener que actualizar la clase Animal si se añaden nuevos tipos) -

animals = Animal.objects.all() 
for animal in animals: 
    animal.make_sound() 

Para evitar la programación metaclase, se podría utilizar composition over inheritance. Por ejemplo-

class Animal(models.Model): 

    type = models.CharField(max_length=255) 

    @property 
    def type_instance(self): 
     """Return a Dog or Cat object, etc.""" 
     return globals()[self.type]() 

    def make_sound(self): 
     return self.type_instance.make_sound() 

class Dog(object): 
    def make_sound(self): 
     print "Woof!" 

class Cat(object): 
    def make_sound(self): 
     print "Meow!" 

Si los Dog y Cat clases necesitan tener acceso a la Animal ejemplo, también se puede ajustar el método anterior para pasar type_instance() lo que necesita para el constructor de la clase (por ejemplo self).

7

El enfoque propuesto por MetaClass thedk es de hecho una forma muy poderosa para ir, sin embargo, he tenido que combinarlo con una respuesta a la pregunta here tener la consulta devuelve una instancia de proxy modelo. La versión simplificada del código adaptado al ejemplo anterior sería:

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__()]) 

La ventaja de este enfoque proxy es que no se requiere la migración db momento de la creación de nuevas subclases. El inconveniente es que no se pueden agregar campos específicos a las subclases.

Estaría encantado de recibir comentarios sobre este enfoque.

+0

Gran trabajo, me pregunto si esto se compara con las pruebas de la vida real. – eugene

+1

Creo que sería conveniente mover el registro SUBCLASSES_OF_ANIMAL a la metaclase '__init__' – eugene

Cuestiones relacionadas