web-dev-qa-db-fra.com

Comment accéder aux classes enfants d'un objet dans Django sans connaître le nom de la classe enfant?

Dans Django, lorsque vous avez une classe parent et plusieurs classes enfants qui en héritent, vous accéderez normalement à un enfant via parentclass.childclass1_set ou parentclass.childclass2_set, mais que faire si je ne connais pas le nom de la classe enfant spécifique que je veux?

Existe-t-il un moyen d'obtenir les objets associés dans le sens parent-> enfant sans connaître le nom de la classe enfant?

77
Gabriel Hurley

( Mise à jour : pour Django 1.2 et plus récent, qui peut suivre les requêtes select_related à travers les relations inversées OneToOneField (et donc l'héritage vers le bas) hiérarchies), il existe une meilleure technique disponible qui ne nécessite pas l'ajout de real_type champ sur le modèle parent. Il est disponible sous la forme InheritanceManager dans le projet Django-model-utils .)

La manière habituelle de procéder consiste à ajouter une ForeignKey à ContentType sur le modèle Parent qui stocke le type de contenu de la classe "leaf" appropriée. Sans cela, vous devrez peut-être effectuer un certain nombre de requêtes sur les tables enfants pour trouver l'instance, en fonction de la taille de votre arbre d'héritage. Voici comment je l'ai fait dans un projet:

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

Ceci est implémenté comme une classe de base abstraite pour la rendre réutilisable; vous pouvez également placer ces méthodes et le FK directement sur la classe parente dans votre hiérarchie d'héritage particulière.

Cette solution ne fonctionnera pas si vous ne pouvez pas modifier le modèle parent. Dans ce cas, vous êtes pratiquement bloqué en vérifiant toutes les sous-classes manuellement.

80
Carl Meyer

En Python, étant donné une classe X ("nouveau style"), vous pouvez obtenir ses sous-classes (directes) avec X.__subclasses__(), qui renvoie une liste d'objets de classe. (Si vous voulez "d'autres descendants", vous devrez également appeler __subclasses__ Sur chacune des sous-classes directes, etc etc - si vous avez besoin d'aide sur la façon de le faire efficacement en Python, demandez simplement!) .

Une fois que vous avez identifié en quelque sorte une classe enfant d'intérêt (peut-être toutes, si vous voulez des instances de toutes les sous-classes enfants, etc.), getattr(parentclass,'%s_set' % childclass.__name__) devrait aider (si le nom de la classe enfant est 'foo' , c'est comme accéder à parentclass.foo_set - ni plus, ni moins). Encore une fois, si vous avez besoin d'éclaircissements ou d'exemples, veuillez demander!

21
Alex Martelli

La solution de Carl est bonne, voici une façon de le faire manuellement s'il y a plusieurs classes enfants liées:

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

Il utilise une fonction de _meta, qui n'est pas garantie d'être stable car Django évolue, mais il fait l'affaire et peut être utilisé à la volée si besoin est.

5
Paul McMillan

Il s'avère que ce dont j'avais vraiment besoin était la suivante:

Héritage de modèle avec type de contenu et gestionnaire prenant en compte l'héritage

Cela a parfaitement fonctionné pour moi. Merci à tout le monde, cependant. J'ai beaucoup appris en lisant vos réponses!

5
Gabriel Hurley

Vous pouvez utiliser Django-polymorphic pour cela.

Il permet de convertir automatiquement les classes dérivées à leur type réel. Il fournit également Django support admin, gestion des requêtes SQL plus efficace et support du modèle proxy, inlines et formset.

Le principe de base semble avoir été réinventé à plusieurs reprises (y compris le Wagtail .specific, ou les exemples présentés dans cet article). Cependant, il faut plus d'efforts pour s'assurer que cela n'entraîne pas de problème de requête N ou ne s'intègre pas bien avec les applications d'administration, de formulaires/en ligne ou de tiers.

3
vdboor

Voici ma solution, encore une fois, il utilise _meta il n'est donc pas garanti d'être stable.

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
cerberos

Vous pouvez y parvenir en recherchant tous les champs du parent qui sont une instance de Django.db.models.fields.related.RelatedManager. D'après votre exemple, il semble que les classes enfants dont vous parlez ne sont pas des sous-classes. Droite?

0
Chirayu Patel

Une approche alternative utilisant des procurations peut être trouvée dans cet article de blog . Comme les autres solutions, elle a ses avantages et ses inconvénients, qui sont très bien mis en fin de poste.

0
Filipe Correia