Получение аннотации дочерней модели через запрос к родительской в Django?
У меня есть конкретная базовая модель, от которой наследуются другие модели (все модели в этом вопросе были обрезаны для краткости):
class Order(models.Model):
state = models.ForeignKey('OrderState')
Вот несколько примеров "дочерних" моделей:
class BorrowOrder(Order):
parts = models.ManyToManyField('Part', through='BorrowOrderPart')
class ReturnOrder(Order):
parts = models.ManyToManyField('Part', through='ReturnOrderPart')
Как видно из этих примеров, каждая дочерняя модель имеет отношение "многие-ко-многим" Parts через пользовательскую таблицу. Эти пользовательские сквозные таблицы выглядят примерно так:
class BorrowOrderPart(models.Model):
borrow_order = models.ForeignKey('BorrowOrder', related_name='borrowed_parts')
part = models.ForeignKey('Part')
qty_borrowed = models.PositiveIntegerField()
class ReturnOrderPart(models.Model):
return_order = models.ForeignKey('ReturnOrder', related_name='returned_parts')
part = models.ForeignKey('Part')
qty_returned = models.PositiveIntegerField()
Обратите внимание, что поле "количество" в каждой сквозной таблице имеет пользовательское имя (к сожалению): qty_borrowed или qty_returned. Я хотел бы иметь возможность запросить базовую таблицу (так, чтобы я искал по всем типам заказов), и включить аннотированное поле для каждого, которое суммирует эти поля количества:
# Not sure what I specify in the Sum() call here, given that the fields
# I'm interested in are different depending on the child's type.
qs = models.Order.objects.annotate(total_qty=Sum(???))
# For a single model, I would do something like:
qs = models.BorrowOrder.objects.annotate(
total_qty=Sum('borrowed_parts__qty_borrowed'))
У меня два связанных вопроса:
- Могу ли я аннотировать данные дочерней модели через запрос к родительской модели?
- Если да, то могу ли я условно указать поле, которое должно быть аннотировано, учитывая, что фактическое имя поля меняется в зависимости от рассматриваемой модели?
Мне кажется, что это место, где использование When()
и Case()
может быть полезным, но я не уверен, как я построю необходимую логику.
Проблема в том, что при запросе к базовой модели (при многотабличном наследовании) трудно выяснить, каким подклассом на самом деле является объект. См. Как узнать, какой дочерний класс модели.
Теоретически этот запрос может быть выполнен с помощью чего-то вроде
SELECT
CASE
WHEN child1.base_ptr_id IS NOT NULL THEN ...
WHEN child2.base_ptr_id IS NOT NULL THEN ...
END
FROM base
LEFT JOIN child1 ON child1.base_ptr_id = base.id
LEFT JOIN child2 ON child2.base_ptr_id = base.id
...
но я не знаю, как перевести это в Django, и думаю, что это будет слишком сложно. Это можно сделать, если не использовать raw
запросов.
Другим решением было бы добавить в базовый класс поле, определяющее, к какому фактическому подклассу относится каждый объект; в этом случае вам нужно будет сделать столько запросов, сколько существует подклассов, и объединить их. Это решение мне тоже не нравится.
Другая идея заключается в том, чтобы создать представление базы данных (с помощью CREATE VIEW
) на основе приведенного выше SQL-запроса, преобразовать его в модель Django с помощью managed = False
и запрашивать ее. Возможно, это несколько чище, чем другие решения, но это немного нестандартно.