2011-06-10 3 views
7

Suponiendo que un simple conjunto de clases modelo heredado, así:¿Cómo pasar de una base de Modelo a una clase derivada en Django?

class BaseObject(models.Model): 
    some_field = models.SomeField(...) 

class AwesomeObject(BaseObject): 
    awesome_field = models.AwesomeField(...) 

class ExcellentObject(BaseObject): 
    excellent_field = models.ExcellentField(...) 

y una consulta que tiene el siguiente aspecto:

found_objects = BaseObject.objects.filter(some_field='bogus') 

¿Cuál es la mejor manera de tomar cada found objeto y convertirlo de nuevo en es clase derivada? El código que estoy usando ahora es así:

for found in found_objects: 
    if hasattr(found, 'awesomeobject'): 
     ProcessAwesome(found.awesomeobject) 
    elif hasattr(found, 'excellentobject'): 
     ProcessExcellent(found.excellentobject): 

embargo, se siente como esto es un abuso de "hasattr". ¿Hay una mejor manera de hacerlo sin crear un campo explícito de "tipo" en la clase base?

Respuesta

2

Esa es la mejor manera que conozco. Desafortunadamente, la herencia es un poco torpe en este sentido. La herencia de múltiples tablas es básicamente una relación uno a uno entre el modelo padre y los campos adicionales que el niño agrega, razón por la cual funciona el truco hasattr. Puede pensar en cada uno de ellos como un atributo OneToOneField en su modelo principal. Cuando piensas en eso de esa manera, Django no tiene forma de saber qué niño devolver o incluso devolver un niño, por lo que debes manejar esa lógica tú mismo:

Tiendo a crear un método en el padre como get_child, que simplemente gira a través de los atributos y devuelve el que aparece:

class BaseObject(models.Model): 
    some_field = models.SomeField(...) 

    def get_child(self): 
     if hasattr(self, 'awesomeobject'): 
      return ProcessAwesome(found.awesomeobject) 
     elif hasattr(self, 'excellentobject'): 
      return ProcessExcellent(found.excellentobject): 
     else: 
      return None 

Por lo menos entonces, sólo puede llamar a found.get_child(), y tal vez se olvide de la hackery que te lleva allí.

+1

Nah ... Mejor mira django-polymorphic ;-) – vdboor

2

Uso la introspección;

class Base(models.Model): 
[ we have some unique 'key' attribute ] 
class_name = models.CharField(..., editable=False) 

def get_base(self): 
    if self.__class__ == Base: 
     return self 
    # if we are not an instance of Base we 'go up' 
    return Base.objects.get(key=self.key) 

def get_specific(self): 
    if self.__class__ != Base: 
     return self 
    # if we are an instance of Base we find the specific class 
    class_type = getattr(sys.modules["project.app.models"], 
     self.class_name) 
    return class_type.objects.get(key=self.key) 

necesitas algo de fábrica para crear las clases específicas por lo que está seguro de guardar correctamente str (auto. clase) en class_name

+0

Sí, almacenar self.class_name es lo que trato de evitar ... – slacy

3

Pasar de una clase base a una clase derivada es generalmente un signo de mal diseño en un programa. El método que propone, usando hasattr, puede ser un problema grave. Te voy a mostrar:

# defined in some open source library 
class MyObject(object): 
    def what_is_derived(self): 
     if hasattr(self, 'derived1'): 
      return 'derived1' 
     elif hasattr(self, 'derived2'): 
      return 'derived2' 
     else: 
      return 'base' 

Vamos a suponer que las clases Derived1Derived2 y se definen en la misma biblioteca. Ahora, desea utilizar las funciones de MyObject, por lo que deriva de ella en su propio código.

# defined in your own code 
class MyBetterObject(MyObject): 
    pass 

better_object = MyBetterObject() 
better_object.what_is_derived() # prints 'base' 

El objetivo del polimorfismo es que puede tener muchas clases derivadas sin tener que cambiar la clase base. Al hacer que la clase base sea consciente de todas sus clases derivadas, se reduce drásticamente la utilidad de dicha clase. No puede crear una clase derivada sin cambiar la clase base.

Cualquiera que desee trabajar con una clase derivada, o que no les importa lo que la clase específica es y todo lo que necesita son las propiedades/métodos de la clase base. Es lo mismo en todos los lenguajes de programación orientada a objetos. Hay instalaciones para descubrir cuál es la clase derivada, pero generalmente es una mala idea.

Desde una perspectiva de modelos de Django, por lo general utilizar la herencia de tal manera:

class Address(models.Model): 
    # fields... 

class Person(Address): 
    # fields... 

class Business(Address): 
    # fields... 

Address.objects.all() # find all addresses for whatever reason 
Person.objects.all() # im only interested in people 
Business.objects.all() # need to work with businesses 

# need to show all addresses in a postcode, and what type of address they are? 
businesses = Business.objects.filter(postcode='90210') 
people = Person.objects.filter(postcode='90210') 
# use the address properties on both 

cadenas de herencia profundamente anidadas con modelos de Django son torpes. También son bastante innecesarios en la mayoría de los casos. En lugar de contaminar su clase base con las comprobaciones hasattr, defina un método auxiliar que sea capaz de consultar las clases derivadas requeridas, si tal cosa es necesaria. Simplemente no lo defina en la clase Base.

+1

"Yendo de una clase base para una clase derivada es generalmente un signo de mal diseño en un programa ". Qué absurdo. Pasar de una clase base a una clase derivada se llama polimorfismo y es fundamental para la orientación a objetos. Es solo que en este caso específico no funciona sin un truco. – jwg

+1

@jwg ¿leyó el resto de lo que escribí, o solo se enfocó en esa oración en particular? El polimorfismo funciona cuando tienes métodos/atributos comunes en todo el árbol. Si necesita atributos derivados, entonces debería estar trabajando con el modelo derivado, no con el modelo base. La herencia del modelo multi-tabla de Django es una pobre aproximación del polimorfismo, y en mi opinión, (casi) toda la herencia de django debe hacerse con bases abstractas. –

+1

Y .. solo para que quede claro. "Pasar de una clase base a una clase derivada generalmente es un signo de mal diseño en un programa". - cuando downcasting a una clase derivada. El uso de polimorfismo para el envío dinámico a un método o atributo de subclase está bien. –

4

Para este problema específico, existe django-polymorphic. Funciona al usar el marco de tipos de contenido en Django para almacenar el ID del modelo al que apunta la tabla derivada. Cuando evalúa el conjunto de preguntas, cambiará todos los modelos a su tipo específico.

Usted obtendrá:

>>> BaseProject.objects.all() 
[ <AwesomeObject>, <ExcellentObject>, <BaseObject>, <AwesomeObject> ] 
+0

Ok Veo que puede usar los métodos de administrador para obtener instancias de clase derivada. ¿Qué hay de la pregunta del autor sobre pasar de una instancia de modelo padre a una instancia de modelo derivado? ¿Django-polymorphic ofrece una solución para eso también? – eugene

+1

Sí lo hace, el modelo individual también tiene un método 'get_real_instance()'. La mayoría de las veces no lo necesita, porque el gerente ya le da los modelos derivados. Además, también evita el problema de consulta N al obtener múltiples objetos del mismo tipo en una sola consulta. – vdboor

0

También puede utilizar InheritanceQuerySet de django-model-utils en caso de que desee establecer explícitamente que consulta a afectar, de esta manera:

from model_utils.managers import InheritanceQuerySet 

class UserManager([...]): 

    def get_queryset(self): 
     return InheritanceQuerySet(self.model).select_subclasses() 

(código de https://stackoverflow.com/a/25108201)

Cuestiones relacionadas