Получение аннотации дочерней модели через запрос к родительской в 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'))

У меня два связанных вопроса:

  1. Могу ли я аннотировать данные дочерней модели через запрос к родительской модели?
  2. Если да, то могу ли я условно указать поле, которое должно быть аннотировано, учитывая, что фактическое имя поля меняется в зависимости от рассматриваемой модели?

Мне кажется, что это место, где использование 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 и запрашивать ее. Возможно, это несколько чище, чем другие решения, но это немного нестандартно.

Вернуться на верх