2008-12-08 17 views
11

Estoy tratando de encontrar la clase real de un objeto django-model, cuando uso la herencia del modelo.¿Cómo puedo encontrar la "clase concreta" de una clase base del modelo django?

algo de código para describir el problema:

class Base(models.model): 
    def basemethod(self): 
     ... 

class Child_1(Base): 
    pass 

class Child_2(Base): 
    pass 

Si creo diversos objetos de las dos clases de niño y el crear un conjunto de consultas que contiene a todos ellos:

Child_1().save() 
Child_2().save() 
(o1, o2) = Base.objects.all() 

quiero para determinar si el objeto es de tipo Child_1 o Child_2 en basemethod; puedo acceder al objeto hijo mediante o1.child_1 y o2.child_2 pero eso reconquista el conocimiento sobre las clases de niños en la clase base.

he llegado con el siguiente código:

def concrete_instance(self): 
    instance = None 
    for subclass in self._meta.get_all_related_objects(): 
     acc_name = subclass.get_accessor_name() 
     try: 
      instance = self.__getattribute__(acc_name) 
      return instance 
     except Exception, e: 
      pass 

pero se siente frágil y no estoy seguro de lo que sucede cuando si heredo en más niveles.

Respuesta

13

Django implementa la herencia del modelo con un OneToOneField entre la tabla del modelo padre y la tabla del modelo hijo. Cuando lo haces Base.object.all(), Django solo consulta la tabla Base, por lo que no tiene forma de saber qué es la tabla secundaria. Por lo tanto, desafortunadamente, no es posible acceder directamente a la instancia del modelo hijo sin consultas adicionales.

Este snippet muestra un método habitual de añadir un campo ContentType para el modelo base:

from django.contrib.contenttypes.models import ContentType 

class Base(models.Model): 
    content_type = models.ForeignKey(ContentType,editable=False,null=True) 

    def save(self): 
     if(not self.content_type): 
      self.content_type = ContentType.objects.get_for_model(self.__class__) 
     self.save_base() 

    def as_leaf_class(self): 
     content_type = self.content_type 
     model = content_type.model_class() 
     if(model == Base): 
      return self 
     return model.objects.get(id=self.id) 

entonces se puede decir if Base.content_type.model_class() para determinar el tipo.

Here es otro fragmento que agrega un administrador personalizado en la mezcla.

Como puede ver, estas dos soluciones tienen el potencial de ser extremadamente costosas. Si tiene una gran cantidad de instancias, usar el método as_leaf_class() requerirá una consulta en cada elemento.

En su lugar, si tiene un conjunto conocido de modelos secundarios, simplemente consulte cada modelo por separado y agregue las instancias en una lista.

+0

Gracias, eso resolvió mi problema –

+0

No sé si es porque estoy en django 1.4, pero esto no funcionó para mí hasta que usé 'if (no self.content_type_id)' en lugar de self.content_type. aparentemente Django estaba intentando cargar self.content_type, lo que resultó en 'DoesNotExist' –

+0

AFAIK esto es exactamente lo que hace este paquete: https://django-polymorphic.readthedocs.org/en/latest/ Devuelve la clase de niño concreta incluso si utiliza BaseClass.objects.filter() – guettli

-2

Se siente quebradizo porque lo es. (Esta es una reimpresión de una respuesta en un contexto diferente. See C++ casting programmatically : can it be done ?)

Lea sobre el polimorfismo. Casi todas las situaciones de "lanzamiento dinámico" son un ejemplo de polimorfismo que lucha por implementarse.

Cualquier decisión que esté tomando en el lanzamiento dinámico ya se ha realizado. Simplemente delegue el trabajo real a las subclases.

Olvidaste la parte más importante de tu ejemplo. El trabajo útil, polimórfico.

Cuando dijo "Quiero determinar si el objeto es del tipo Child_1 o Child_2 ..." omitió el "para que pueda hacer que el objeto haga aMethod() de una manera que sea única para cada subclase". Ese método es el trabajo útil, y debería ser simplemente un método de ambas subclases.

class Base(models.model): 
    def aMethod(self): 
     # base class implementation. 

class Child_1(Base): 
    def aMethod(self): 
     # Child_1 override of base class behavior. 

class Child_2(Base): 
    def aMethod(self): 
     supert(Child_2, self).aMethod() # Invoke the base class version 
     # Child_2 extension to base class behavior. 

Mismo método, múltiples implementaciones. Nunca es necesario "identificar el tipo de ejecución" o determinar la clase concreta.

+0

Pero él no tiene una referencia a la instancia de un modelo de niño. El administrador de Base.objects solo devuelve instancias de Base. –

+0

@Daniel: generalmente procesamos Child_1.objects.all() y Child_2.objects.all(); casi no hay un llamado para la unión de ambos subtipos de niños. –

+0

En general, pero esa no era la pregunta. –

0

Bueno ... Mi problema era. En una vista, tenía este modelo principal, digamos "Big_Model" y había algunos "Small_Model" relacionados con "Big_Model". Entonces cuando quise recuperar todo el "Small_Model" relacionado con una cierta instancia de "Big_Model", hice eso ** _ set.all(). Pero el punto es que Small_Model tiene Child Classes y yo quería, en views.py, obtener a qué clase hija pertenecían cada una de las instancias de Small_Model. Mi truco fue definir los métodos booleanos en el modelo Small_Model como is_child_1() e is_child_2(). Y cuando es cierto, aplica el puntero de niño real en lugar del puntero de Small_Model.

Ok ... Eso no es lo suficientemente claro, todavía no tengo mucho tiempo para escribir un buen ejemplo, así que sólo voy a copiar y pegar mi caso aquí:

class Cache(models.Model): 
    valor = models.DecimalField(max_digits=9, decimal_places=2, blank= True, null= True) 
    evento=models.ForeignKey(Evento) 
    def __unicode__(self): 
    return u'%s: %s' % (self.evento, self.valor) 
    class Meta: 
    verbose_name='Cachê' 
    verbose_name_plural='Cachês' 
    def is_cb(self): 
    try: 
     self.cache_bilheteria 
     return True 
    except self.DoesNotExist: 
     return False 
    def is_co(self): 
    try: 
     self.cache_outro 
     return True 
    except self.DoesNotExist: 
     return False 
5

Tenga una mirada en InheritanceManager en django-model-utils - lo conecta a un modelo que da las clases hijas concretos (por lo menos en el primer nivel):

from model_utils.managers import InheritanceManager 

class Base(models.Model): 
    objects = InheritanceManager() 

# ... 

Base.objects.all().select_subclasses() # returns instances of child classes 

modelo de utilidades requiere Django 1.2 o superior.

0

ligeramente modificada versión de what Daniel Naab proposed:

from django.contrib.contenttypes.models import ContentType 
from django.db import models 

def ParentClass(models.Model): 
    superclass = models.CharField(max_length = 255, blank = True) 

    def save(self, *args, **kwargs): 
     if not self.superclass: 
      self.superclass = ContentType.objects.get_for_model(self.__class__) 

     super(ParentClass, self).save(*args, **kwargs) 

    def getChild(self): 
     s = getattr(self, self.superclass) 
     if hasattr(s, 'pk'): 
      return s 
     else: 
      return None 

class Child1(ParentClass): 
    pass 

class Child2(ParentClass): 
    pass 
Cuestiones relacionadas