86 votes

Comment accéder aux classes enfant 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édez normalement à une classe enfant par le biais de 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 liés dans le sens parent->enfant sans connaître le nom de la classe enfant ?

80 votes

@S.Lott Ce genre de réponses devient vraiment vieux. Ce n'est pas parce que vous ne pensez pas à un cas d'utilisation que l'auteur de la question n'en a pas. Si vous utilisez la sous-classification pour tout type de comportement polymorphe (vous savez, l'un des principaux avantages supposés de la POO ?), cette question est une nécessité très naturelle et évidente.

82 votes

@S.Lott Dans ce cas, n'hésitez pas à pratiquer des versions non impolies, telles que "Je ne suis pas sûr de comprendre le contexte". Pourriez-vous expliquer votre cas d'utilisation ?"

89voto

Carl Meyer Points 30736

( Mise à jour : Pour Django 1.2 et les versions plus récentes, qui peuvent suivre les requêtes select_related à travers les relations inverses OneToOneField (et donc dans les hiérarchies d'héritage), il existe une meilleure technique qui ne nécessite pas l'ajout de l'outil de gestion de l'héritage. real_type sur le modèle parent. Il est disponible en tant que InheritanceManager en el django-model-utils projet.)

La façon habituelle de procéder est d'ajouter une clé étrangère à ContentType sur le modèle parent qui stocke le type de contenu de la classe "feuille" appropriée. Sans cela, vous pouvez avoir à faire un certain nombre de requêtes sur les tables enfant pour trouver l'instance, selon 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 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

Elle est implémentée en tant que classe de base abstraite afin de la rendre réutilisable ; vous pourriez également placer ces méthodes et le FK directement sur la classe parent dans votre hiérarchie d'héritage particulière.

Cette solution ne fonctionnera pas si vous n'êtes pas en mesure de modifier le modèle parent. Dans ce cas, vous devrez vérifier manuellement toutes les sous-classes.

0 votes

Merci. C'est magnifique et ça m'a fait gagner du temps.

0 votes

Cela a été très utile, mais je me demande pourquoi vous voulez définir le FK avec null=True. Nous avons copié le code plus ou moins tel quel, et avons été victimes d'un bug qui aurait été détecté et résolu facilement si le FK était obligatoire (notez également que la méthode cast() le traite comme obligatoire).

1 votes

@ShaiBerger Excellente question. Trois ans plus tard, je ne sais pas pourquoi c'était comme ça à l'origine :-) Modification pour supprimer le null=True.

24voto

Alex Martelli Points 330805

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 faire cela efficacement en Python, il suffit de demander !).

Une fois que vous avez identifié d'une manière ou d'une autre une classe enfant intéressante (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 juste comme accéder parentclass.foo_set -- ni plus, ni moins). Encore une fois, si vous avez besoin de précisions ou d'exemples, n'hésitez pas à demander !

1 votes

C'est une excellente information (je ne connaissais pas les Sous-classes ), mais je pense que la question est en fait beaucoup plus spécifique à la façon dont les modèles Django mettent en œuvre l'héritage. Si vous interrogez la table "Parent", vous obtiendrez une instance de Parent. Elle peut en fait est une instance de SomeChild, mais Django ne le calcule pas automatiquement pour vous (ce qui pourrait être coûteux). Vous pouvez accéder à l'instance de SomeChild par le biais d'un attribut de l'instance Parent, mais seulement si vous savez déjà que c'est SomeChild que vous voulez, et non une autre sous-classe de Parent.

2 votes

Désolé, ce n'est pas clair quand je dis " peut ". en fait être une instance de SomeChild". L'objet que vous avez est une instance de Parent en Python, mais il peut avoir une entrée connexe dans la table SomeChild, ce qui signifie que vous pouvez préférer travailler avec lui comme une instance SomeChild.

0 votes

C'est drôle... Je n'avais pas besoin de cette information spécifique à ce moment-là, mais j'étais en train de réfléchir à un autre problème et c'est devenu exactement ce dont j'avais besoin, alors merci encore !

5voto

Gabriel Hurley Points 17079

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

Héritage de modèle avec type de contenu et gestionnaire conscient de l'héritage

Cela a parfaitement fonctionné pour moi. Merci à tous les autres, cependant. J'ai beaucoup appris en lisant vos réponses !

5voto

Paul McMillan Points 11723

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

Elle utilise une fonction de _meta, dont la stabilité n'est pas garantie au fur et à mesure que django évolue, mais elle fait l'affaire et peut être utilisée à la volée si nécessaire.

2voto

cerberos Points 2000

Voici ma solution, qui utilise à nouveau _meta il n'est donc pas garanti qu'il soit 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X