¿Cómo puedo acceder a las clases secundarias de un objeto en django sin saber el nombre de la clase secundaria?


En Django, cuando tiene una clase padre y varias clases hijas que heredan de ella, normalmente accedería a una hija a través de parentclass.childclass1_set o parentclass.childclass2_set, pero ¿qué pasa si no sé el nombre de la clase hija específica que quiero?

¿Hay alguna manera de obtener los objetos relacionados en la dirección padre->hijo sin conocer el nombre de la clase hijo?

Author: Gabriel Hurley, 2009-05-30

8 answers

(Actualización: Para Django 1.2 y posteriores, que pueden seguir consultas select_related a través de relaciones OneToOneField inversas (y por lo tanto jerarquías de herencia), hay una mejor técnica disponible que no requiere el campo real_type añadido en el modelo padre. Está disponible como InheritanceManager en el proyecto django-model-utils.)

La forma habitual de hacer esto es agregar una ForeignKey a ContentType en el modelo padre que almacena el tipo de contenido del clase "hoja" apropiada. Sin esto, es posible que tenga que hacer un buen número de consultas en las tablas secundarias para encontrar la instancia, dependiendo del tamaño de su árbol de herencia. Así es como lo hice en un proyecto:

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

class InheritanceCastModel(models.Model):
    """
    An abstract base class that provides a ``real_type`` FK to ContentType.

    For use in trees of inherited models, to be able to downcast
    parent instances to their child types.

    """
    real_type = models.ForeignKey(ContentType, editable=False)

    def save(self, *args, **kwargs):
        if not self._state.adding:
            self.real_type = self._get_real_type()
        super(InheritanceCastModel, self).save(*args, **kwargs)

    def _get_real_type(self):
        return ContentType.objects.get_for_model(type(self))

    def cast(self):
        return self.real_type.get_object_for_this_type(pk=self.pk)

    class Meta:
        abstract = True

Esto se implementa como una clase base abstracta para hacerla reutilizable; también podría poner estos métodos y el FK directamente en la clase padre en su jerarquía de herencia particular.

Esta solución no funcionará si no puede modificar el modelo padre. En eso en caso de que estés prácticamente atascado revisando todas las subclases manualmente.

 78
Author: Carl Meyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-04 18:29:16

En Python, dada una clase X ("new-style"), puede obtener sus subclases (directas) con X.__subclasses__(), que devuelve una lista de objetos de clase. (Si quieres "más descendientes", también tendrás que llamar a __subclasses__ en cada una de las subclases directas, etc etc etc si necesitas ayuda sobre cómo hacerlo de manera efectiva en Python, ¡solo pregunta!).

Una vez que haya identificado de alguna manera una clase secundaria de interés (tal vez todas ellas, si desea instancias de todas las subclases secundarias, etc.), getattr(parentclass,'%s_set' % childclass.__name__) debería ayudar (si la clase el nombre de la clase es 'foo', esto es como acceder parentclass.foo_set no ni más, ni menos). Una vez más, si necesita aclaraciones o ejemplos, por favor pregunte!

 21
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-30 06:02:24

La solución de Carl es buena, aquí hay una manera de hacerlo manualmente si hay varias clases secundarias relacionadas:

def get_children(self):
    rel_objs = self._meta.get_all_related_objects()
    return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]

Utiliza una función fuera de _meta, que no se garantiza que sea estable a medida que django evoluciona, pero hace el truco y se puede usar sobre la marcha si es necesario.

 5
Author: Paul McMillan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-05 17:39:46

Resulta que lo que realmente necesitaba era esto:

Modelo de herencia con content type y inheritance-aware manager

Eso ha funcionado perfectamente para mí. Gracias a todos los demás, sin embargo. ¡Aprendí mucho leyendo tus respuestas!

 5
Author: Gabriel Hurley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-09 22:20:31

Aquí está mi solución, de nuevo utiliza _meta por lo que no se garantiza que sea estable.

class Animal(models.model):
    name = models.CharField()
    number_legs = models.IntegerField()
    ...

    def get_child_animal(self):
        child_animal = None
        for r in self._meta.get_all_related_objects():
            if r.field.name == 'animal_ptr':
                child_animal = getattr(self, r.get_accessor_name())
        if not child_animal:
            raise Exception("No subclass, you shouldn't create Animals directly")
        return child_animal

class Dog(Animal):
    ...

for a in Animal.objects.all():
    a.get_child_animal() # returns the dog (or whatever) instance
 2
Author: cerberos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-26 18:32:38

Puedes usar django-polymorphic para eso.

Permite convertir automáticamente las clases derivadas a su tipo real. También proporciona soporte de administración de Django, manejo de consultas SQL más eficiente, y soporte de modelos de proxy, líneas internas y conjuntos de formularios.

El principio básico parece ser reinventado muchas veces (incluyendo .specific de la lavandera, o los ejemplos descritos en este post). Sin embargo, se necesita más esfuerzo para asegurarse de que no resulte en un problema de N-query o se integre bien con el administrador, formsets / inlines o aplicaciones de terceros.

 2
Author: vdboor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-06 13:23:14

Puede lograr esto buscando todos los campos en el padre que son una instancia de django.db.modelo.campo.relacionados.Gestor relacionado. A partir de su ejemplo, parece que las clases secundarias de las que está hablando no son subclases. ¿Verdad?

 0
Author: Chirayu Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-30 08:50:36

Un enfoque alternativo usando proxies se puede encontrar en esta entrada de blog. Al igual que las otras soluciones, tiene sus beneficios y pasivos, que están muy bien puestos al final del post.

 0
Author: Filipe Correia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-14 13:47:42