Как запросить объекты на основе абстрактного класса в Django?
Скажем, у меня есть абстрактный базовый класс, который выглядит так:
class StellarObject(BaseModel):
title = models.CharField(max_length=255)
description = models.TextField()
slug = models.SlugField(blank=True, null=True)
class Meta:
abstract = True
Теперь скажем, что у меня есть два реальных класса базы данных, которые наследуются от StellarObject
class Planet(StellarObject):
type = models.CharField(max_length=50)
size = models.IntegerField(max_length=10)
class Star(StellarObject):
mass = models.IntegerField(max_length=10)
До сих пор так хорошо. Если я хочу получить Планеты или Звезды, все, что я делаю, это:
Thing.objects.all() #or
Thing.objects.filter() #or count(), etc...
Но что, если я хочу получить ВСЕ StellarObjects? Если я это сделаю:
StellarObject.objects.all()
Он, конечно, возвращает ошибку, потому что абстрактный класс не является фактическим объектом базы данных и поэтому не может быть запрошен. Все, что я прочитал, говорит, что мне нужно сделать два запроса, по одному на планетах и звездах, а затем объединить их. Это кажется ужасно неэффективным. Это единственный способ?
Ответы
Ответ 1
В своем корне это часть несоответствия между объектами и реляционными базами данных. ORM отлично справляется с абстрагированием различий, но иногда вы просто сталкиваетесь с ними в любом случае.
В принципе, вам нужно выбирать между абстрактным наследованием, и в этом случае отношения между базами данных между двумя классами или наследование на основе нескольких таблиц не поддерживаются, что связывает базу данных с ценой эффективности (дополнительное объединение базы данных) для каждого запрос.
Ответ 2
Вы не можете запрашивать абстрактные базовые классы. Для наследования с несколькими таблицами вы можете использовать django-model-utils
и InheritanceManager
, который расширяет стандартный QuerySet
с помощью метода select_subclasses()
, который делает то, что вам нужно: он лево-присоединяет все наследуемые таблицы и возвращает соответствующий экземпляр типа для каждого строки.
Ответ 3
Не используйте абстрактный базовый класс, если вам нужно запросить базу. Вместо этого используйте конкретный базовый класс.
Ответ 4
В этом случае я думаю, что нет другого пути.
Для оптимизации вы можете избежать наследования от абстрактного StellarObject
и использовать его как отдельную таблицу, подключенную через FK к объектам Star
и Planet
.
Таким образом, оба они имели бы то же самое. star.stellar_info.description
.
Другим способом было бы добавить дополнительную модель для обработки информации и использовать StellarObject как through
во многих отношениях.
Ответ 5
Я бы подумал о том, чтобы отказаться от абстрактного шаблона наследования или конкретного базового шаблона, если вы хотите привязать различные подклассы к объектам на основе их соответствующего дочернего класса.
При запросе через родительский класс, который звучит так, как вы хотите, Django обрабатывает полученные объекты как объекты родительского класса, поэтому доступ к методам на уровне дочернего класса требует повторного включения объектов в их "правильный" класс "на лету", чтобы они могли видеть эти методы... в этот момент ряд утверждений if, зависящих от метода на уровне родительского класса, возможно, будет более чистым подходом.
Если описанное выше поведение подкласса не является проблемой, вы можете подумать о настраиваемом диспетчере, прикрепленном к абстрактному базовому классу, который сшивает модели вместе с помощью необработанного SQL.
Если вам интересно в основном назначить дискретный набор идентичных полей данных для кучи объектов, я бы связал их с внешним ключом, как предполагает bx2.
Ответ 6
Это кажется ужасно неэффективным. Это единственный способ?
Насколько я знаю, это единственный способ с Django ORM. Поскольку реализованные в настоящее время абстрактные классы - удобный механизм для абстрагирования общих атрибутов классов к суперклассам. ORM не предоставляет аналогичную абстракцию для запросов.
Вам будет лучше использовать другой механизм для реализации иерархии в базе данных. Один из способов сделать это - использовать одну таблицу и "тегировать" строки с использованием типа. Или вы можете реализовать общий внешний ключ для другой модели, которая содержит свойства (последнее не звучит правильно даже для меня).
Ответ 7
Это пример полиморфизма в ваших моделях (полиморф - много форм одного).
Вариант 1 - Если есть только одно место, вы имеете дело с этим:
Для небольшого кода if-else в одном или двух местах, просто обработайте его вручную - это, вероятно, будет быстрее и с точки зрения производительности базы данных, и времени кодирования.
Вариант 2 - Если вы делаете это довольно много или действительно требуете элегантности в синтаксисе запроса:
К счастью, существует библиотека для обработки полиморфизма в django, django-polymorphic - эти документы покажут вам, как это сделать точно. Вероятно, это "правильный ответ" для прямого запроса, как вы описали, особенно если вы хотите сделать наследование модели во многих местах.
Вариант 3 - Если вы хотите получить половину дома:
Этот вид имеет недостатки обоих вышеизложенных, но я использовал его в прошлом для того, чтобы автоматически делать все скрепления из нескольких наборов запросов, сохраняя при этом преимущества наличия одного объекта набора заданий, содержащего оба типа моделей.
Отметьте django-querysetsequence, который управляет объединением нескольких наборов запросов вместе.
Он не поддерживается так же стабильно, как django-polymorphic
, но стоит упомянуть его.