Как мне получить доступ к дочерним классам объекта в django, не зная имени дочернего класса?
-
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.поля.связанные.Связанный менеджер.Из вашего примера кажется, что дочерние классы, о которых вы говорите, не являются подклассами.Верно?
Альтернативный подход с использованием прокси-серверов можно найти в это сообщение в блоге.Как и у других решений, у него есть свои преимущества и недостатки, которые очень хорошо изложены в конце поста.