Как получить доступ к дочерним классам объекта в django, не зная имени дочернего класса?
В Django, когда у вас есть родительский класс и несколько дочерних классов, которые наследуют его, вы обычно получаете доступ к дочернему классу через parentclass.childclass1_set или parentclass.childclass2_set, но что если я не знаю имя конкретного дочернего класса, который я хочу?
Есть ли способ получить связанные объекты в направлении parent->child, не зная имени дочернего класса?
8 ответов
(Обновление: для 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 непосредственно в родительский класс в вашей конкретной иерархии наследования.
Это решение не будет работать, если вы не можете изменить родительскую модель. В этом случае вы в значительной степени застряли, проверяя все подклассы вручную.
В 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, но он выполняет свою функцию и может быть использована на лету, если это необходимо.
Оказывается, что мне действительно нужно было это:
Модель наследования с типом контента и менеджером, осведомленным о наследовании
Это отлично сработало для меня. Спасибо всем остальным, хотя. Я многому научился, просто читая ваши ответы!
Для этого вы можете использовать django-polymorphic.
Это позволяет автоматически приводить производные классы обратно к их фактическому типу. Он также обеспечивает поддержку администратора Django, более эффективную обработку запросов SQL, а также модель прокси, встроенные функции и поддержку набора форм.
Основной принцип, кажется, многократно переосмысливается (включая трясогузку .specific
или примеры, изложенные в этом посте). Однако требуется больше усилий, чтобы убедиться, что это не приводит к проблеме с N-запросами, или хорошо интегрируется с администратором, наборами форм / встроенными или сторонними приложениями.
Вот мое решение, опять же оно использует _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.fields.related.RelatedManager. Из вашего примера видно, что дочерние классы, о которых вы говорите, не являются подклассами. Правильно?