En Django, cuando tiene una clase principal y varias clases secundarias que heredan de ella, normalmente accedería a un niño a través de parentclass.childclass1_set o parentclass.childclass2_set, pero ¿qué pasa si no sé el nombre de la clase secundaria específica que quiero?
¿Hay alguna manera de obtener los objetos relacionados en la dirección padre-> hijo sin saber el nombre de la clase secundaria?
python
django
many-to-many
Gabriel Hurley
fuente
fuente
Respuestas:
( Actualización : para Django 1.2 y versiones posteriores, que pueden seguir consultas select_related en relaciones OneToOneField inversas (y, por lo tanto, jerarquías de herencia descendentes), hay una técnica mejor disponible que no requiere el
real_type
campo agregado en el modelo principal. 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 principal que almacena el tipo de contenido de la clase "hoja" adecuada. Sin esto, es posible que tenga que hacer una gran cantidad de consultas en tablas secundarias para encontrar la instancia, dependiendo de qué tan grande sea 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 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 que sea reutilizable; también puede 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 principal. En ese caso, está prácticamente atascado comprobando todas las subclases manualmente.
fuente
self.child_object()
usando elreal_type
campo (que está en el código anterior comocast()
método), y eso solo tomará una consulta para una instancia. Pero si tiene un conjunto de consultas lleno de instancias, eso se convierte en N consultas. La única forma de obtener los datos de subclase para un conjunto de consultas completo de objetos en una sola consulta es usar las uniones que hace InheritanceManager.En Python, dada una clase X ("nuevo estilo"), puede obtener sus subclases (directas) con
X.__subclasses__()
, que devuelve una lista de objetos de clase. (Si desea "más descendientes", también tendrá que llamar__subclasses__
a cada una de las subclases directas, etc., etc., si necesita ayuda sobre cómo hacerlo de manera efectiva en Python, ¡solo pregunte!).Una vez que haya identificado de alguna manera una clase secundaria de interés (tal vez todas, si desea instancias de todas las subclases secundarias, etc.),
getattr(parentclass,'%s_set' % childclass.__name__)
debería ayudar (si el nombre de la clase secundaria es'foo'
, esto es como accederparentclass.foo_set
, ni más, ni menos ). Nuevamente, si necesita aclaraciones o ejemplos, ¡pregunte!fuente
La solución de Carl es buena, aquí hay una forma 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.
fuente
Resulta que lo que realmente necesitaba era esto:
Modelo de herencia con tipo de contenido y administrador consciente de la herencia
Eso me ha funcionado perfectamente. Sin embargo, gracias a todos los demás. ¡Aprendí mucho leyendo tus respuestas!
fuente
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 modelo de proxy, soporte en línea y conjuntos de formularios.
El principio básico parece reinventarse muchas veces (incluido el de Wagtail
.specific
o los ejemplos descritos en esta publicación). 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.fuente
Aquí está mi solución, nuevamente se usa,
_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
fuente
Puede lograr esto buscando todos los campos en el padre que son una instancia de django.db.models.fields.related.RelatedManager. De su ejemplo, parece que las clases secundarias de las que está hablando no son subclases. ¿Correcto?
fuente
En esta publicación de blog se puede encontrar un enfoque alternativo que usa proxies . Como las otras soluciones, tiene sus beneficios y pasivos, que están muy bien puestos al final del post.
fuente