Как я могу объединить несколько моделей в Django в виртуальную таблицу?

Если у меня есть 3 модели, например:

class Cow(models.Model):
    name =
    number_of_eyes =
    number_of_feet =
    color =

class Pig(models.Model):
    name =
    number_of_eyes = 
    number_of_feet =
    intelligence = 
    
class Horse(models.Model):
    name =
    number_of_eyes =
    number_of_hooves = 
    weight_capacity =
    speed =

И я заинтересован в создании одной Livestock таблицы в моем шаблоне, которая имеет экземпляры всех 3, но меня интересуют только эти столбцы, которые есть у всех 3 моделей:

  • имя
  • количество_глаз
  • количество_ног (количество_копыт, если лошадь)

И мы можем игнорировать все остальные столбцы.

Как я могу объединить их в один набор запросов?

Конечная цель - получить одну виртуальную таблицу (queryset), над которой я могу выполнить несколько других операций (filter, order_by, slice), а затем вернуть данные только в этих столбцах.

Возможно ли это в Django ORM?

Я думаю, у вас есть два варианта:

  1. использование itertools.chain:

    from itertools import chain
    
    
    cows = Cow.objects.all()
    pigs = Pig.objects.all()
    horses = Horse.objects.all()
    
    livestock_list = sorted(
        chain(cows, pigs, horses), 
        key=lambda livestock: livestock.created_at, reverse=True)
    )
    
  2. использование contenttypes:

    from django.contrib.contenttypes.models import ContentType
    from django.contrib.contenttypes.fields import GenericForeignKey
    
    
    class Livestock(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        content_object = GenericForeignKey('content_type', 'object_id')
        created = models.DateTimeField(auto_now_add=True)
    
        class Meta:
             ordering = ['-created']
    

    Теперь вы можете запрашивать модель Livestock как любую другую модель в Django, но у вас может быть внешний ключ, который может ссылаться на n моделей. Вот что делают contenttypes.

    Livestock.content_object дает вам то, что вы хотите, в вашем случае это может быть Cow, Pig или Horse.

    Только не забудьте добавить объекты в модель Livestock после создания экземпляров лошади и т.д. Вам нужно добавить их в 2 модели на самом деле. Вы можете сделать это с помощью сигналов.

Я думаю, что второе решение лучше.

Очевидно, это также можно сделать с помощью Union, как предложил Nick ODell:

from django.db.models import F

Cow.objects.filter(...).union(
  Pig.objects.filter(...), 
  Horse.objects.filter(...).annotate(number_of_feet=F("number_of_hooves"))
).values('name', 'number_of_eyes', 'number_of_feet').order_by('name')[:3]

К сожалению, вы не можете фильтровать результирующий набор запросов после объединения, поэтому вам нужно фильтровать каждый набор запросов перед объединением, но в остальном все работает в моем быстром тестировании.

Насколько я понимаю, отличие от предложения MojixCoder использовать ContentType заключается в том, что вам не нужно поддерживать отдельное определение этой виртуальной таблицы в модуле моделей Django. В некоторых случаях это может быть преимуществом, поскольку вам не нужно обновлять модуль при появлении новых моделей, которые вы хотите включить в свой запрос, но в других случаях это может быть недостатком, поскольку в моем способе приходится набирать много текста каждый раз, когда вы хотите использовать этот запрос, тогда как в примере MojixCoder вы определяете его один раз, и ваши запросы будут намного короче.

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