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

StackOverflow https://stackoverflow.com/questions/929029

  •  06-09-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

Обычный способ сделать это - добавить 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 непосредственно в родительский класс в вашей конкретной иерархии наследования.

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

Другие советы

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

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

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

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, но она делает свое дело и при необходимости может быть использована "на лету".

Оказывается, что мне действительно было нужно вот это:

Наследование модели с помощью content type и менеджера с учетом наследования

У меня это сработало идеально.Но все же спасибо всем остальным.Я многому научился, просто читая ваши ответы!

Вы можете использовать django-полиморфный за это.

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

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

Вот мое решение, и снова оно использует _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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top