Как запросить объекты на основе абстрактного класса в Django? - PullRequest
43 голосов
/ 26 сентября 2010

Допустим, у меня есть абстрактный базовый класс, который выглядит следующим образом:

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

Конечно, он возвращает ошибку, потому что абстрактный класс не является фактическим объектом базы данных и поэтому не может быть запрошен. Все, что я прочитал, говорит, что мне нужно сделать два запроса, по одному на Планеты и Звезды, а затем объединить их. Это кажется ужасно неэффективным. Это единственный способ?

Ответы [ 7 ]

33 голосов
/ 26 сентября 2010

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

По сути, вам приходится выбирать между абстрактным наследованием, и в этом случае связь между базами данных между двумя классами отсутствует, или многостольным наследованием, которое поддерживает связь с базой данных за счет эффективности (дополнительное объединение базы данных) для каждого запрос.

10 голосов
/ 02 апреля 2012

Вы не можете запрашивать абстрактные базовые классы.Для многостолового наследования вы можете использовать django-model-utils и InheritanceManager, что расширяет стандартный QuerySet с помощью метода select_subclasses(), который делает то, что вам нужно: он объединяет все унаследованные таблицы слева и возвращает соответствующий экземпляр типа для каждогостроки.

5 голосов
/ 26 сентября 2010

Не используйте абстрактный базовый класс, если вам нужно выполнить запрос по базе. Вместо этого используйте конкретный базовый класс.

4 голосов
/ 13 декабря 2017

Это пример полиморфизма в ваших моделях (полиморф - много форм одного).

Вариант 1 - Если есть только одно место, с которым вы имеете дело:

Ради небольшого количества кода if-else в одном или двух местах, просто разберитесь с ним вручную - это, вероятно, будет намного быстрее и понятнее с точки зрения разработки / обслуживания (то есть, может быть, стоит, если эти запросы несерьезно ударить по вашей базе данных - это ваше суждение и зависит от обстоятельств).

Вариант 2 - Если вы делаете это совсем немного или действительно требует элегантности в синтаксисе вашего запроса:

К счастью, естьбиблиотека для работы с полиморфизмом в django, django-polymorphic - эти документы покажут вам, как это сделать точно.Это, вероятно, «правильный ответ» для прямого запроса, как вы описали, особенно если вы хотите выполнять наследование моделей во многих местах.

Вариант 3 - Если вы хотите получить дом на полпути:

Этот вид имеет недостатки обоих вышеперечисленных, но в прошлом я успешно использовал его для автоматического объединения всех нескольких наборов запросов, сохраняя при этом преимущества одного объекта набора запросов, содержащего оба типа:модели.

Проверьте django-querysetsequence , который управляет объединением нескольких наборов запросов.

Он не так хорошо поддерживается и не так стабилен, как django-polymorphic, но стоитупоминание тем не менее.

1 голос
/ 26 сентября 2010

Я хотел бы отойти от абстрактного шаблона наследования или от конкретного базового шаблона, если вы хотите связать различные поведения подкласса с объектами на основе их соответствующего дочернего класса.

Когда вы выполняете запрос через родительский класс - что звучит так, как вы хотите - Django рассматривает полученные объекты как объекты родительского класса, поэтому доступ к методам уровня дочернего класса требует повторного приведения объектов в их «правильный» дочерний класс на лету, чтобы они могли видеть эти методы ... в этот момент последовательность операторов if, висящих над методом уровня родительского класса, возможно, будет более чистым подходом.

Если поведение подкласса, описанное выше, не является проблемой, вы можете рассмотреть возможность создания собственного менеджера, присоединенного к абстрактному базовому классу, соединяющего модели вместе с помощью необработанного SQL.

Если вы заинтересованы в основном в назначении дискретного набора идентичных полей данных для группы объектов, я бы связал их по внешнему ключу, как предполагает bx2.

1 голос
/ 26 сентября 2010

В этом случае я думаю, что нет другого пути.

Для оптимизации вы можете избежать наследования от абстрактного StellarObject и использовать его как отдельную таблицу, соединенную через FK с Star и Planet объектами.

Таким образом, они оба будут иметьто есть.star.stellar_info.description.

Другой способ - добавить дополнительную модель для обработки информации и использовать StellarObject в качестве through во многих отношениях.

0 голосов
/ 26 сентября 2010

Это кажется ужасно неэффективным.Это единственный способ?

Насколько я знаю, это единственный путь с ORM Джанго.Реализуемые в настоящее время абстрактные классы являются удобным механизмом абстрагирования общих атрибутов классов в суперклассы.ORM не предоставляет подобную абстракцию для запросов.

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

...