Подтвердить что ты не робот

Как получить доступ к дочерним классам объекта в django, не зная имя дочернего класса?

В Django, когда у вас есть родительский класс и несколько дочерних классов, которые наследуют его, вы обычно обращаетесь к потомку через parentclass.childclass1_set или parentclass.childclass2_set, но что, если я не знаю имя конкретного дочернего класса Я хочу?

Есть ли способ получить связанные объекты в направлении parent- > child, не зная имя дочернего класса?

4b9b3361

Ответ 1

(Обновление: для Django 1.2 и более поздних версий, которые могут следовать запросу select_related через обратные отношения OneToOneField (и, следовательно, вниз иерархии наследования), существует более совершенная техническая возможность, которая не требует добавленного поля real_type в родительской модели. Она доступна как InheritanceManager в проект django-model-utils.)

Обычный способ сделать это - добавить ForeignKey в ContentType в родительскую модель, в которой хранится тип содержимого соответствующего класса "лист". Без этого вам может потребоваться выполнить довольно много запросов на дочерние таблицы, чтобы найти экземпляр, в зависимости от того, насколько велико ваше дерево наследования. Вот как я это сделал в одном проекте:

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

Это реализовано как абстрактный базовый класс, чтобы сделать его многоразовым; вы также можете поместить эти методы и FK непосредственно в родительский класс в свою конкретную иерархию наследования.

Это решение не будет работать, если вы не сможете изменить родительскую модель. В этом случае вы почти полностью проверяете все подклассы вручную.

Ответ 2

В Python, учитывая класс ( "новый стиль" ), вы можете получить его (прямые) подклассы с помощью X.__subclasses__(), который возвращает список объектов класса. (Если вам нужны "последующие потомки", вам также придется вызывать __subclasses__ для каждого из прямых подклассов и т.д. И т.д. - если вам нужна помощь в том, как это сделать на Python, просто спросите!).

Как только вы каким-то образом определили интересующий класс (возможно, все из них, если вы хотите экземпляры всех дочерних подклассов и т.д.), getattr(parentclass,'%s_set' % childclass.__name__) должен помочь (если дочернее имя класса 'foo', это просто например, доступ к parentclass.foo_set - не более, не менее). Опять же, если вам нужны пояснения или примеры, пожалуйста, спросите!

Ответ 3

Решение Carl является хорошим, здесь один из способов сделать это вручную, если есть несколько связанных дочерних классов:

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

Он использует функцию из _meta, которая не гарантируется быть стабильной по мере развития django, но она делает трюк и может быть использована "на лету", если это необходимо.

Ответ 5

Здесь мое решение, опять же оно использует _meta, поэтому не гарантируется стабильность.

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

Ответ 6

Вы можете использовать django-polymorphic для этого.

Позволяет автоматически переводить производные классы обратно в их фактический тип. Он также предоставляет поддержку администратора Django, более эффективную обработку запросов SQL и поддержку прокси-модели, встроенных и поддерживаемых форм.

Основной принцип, кажется, многократно изобретается (включая Wagtail .specific или примеры, описанные в этом сообщении). Однако требуется больше усилий, чтобы убедиться, что это не приводит к проблеме N-запроса или прекрасно сочетается с администратором, приложениями форм/встроенных приложений или сторонними приложениями.

Ответ 7

Вы можете добиться этого, ища все поля родителя, которые являются экземпляром django.db.models.fields.related.RelatedManager. Из вашего примера кажется, что дочерние классы, о которых вы говорите, не являются подклассами. Правильно?

Ответ 8

Альтернативный подход с использованием прокси-серверов можно найти в этом сообщении в блоге. Как и другие решения, у него есть свои преимущества и обязательства, которые очень хорошо поставлены в конце сообщения.