Сложный набор запросов с аннотациями для разных моделей

Я использую Django 1.6 и имею следующие модели (немного упрощенные для удобства чтения):

class Person(models.Model):
    name = models.CharField(max_length=128)

class Room(models.Model):
    max_persons = models.SmallIntegerField()
    name = models.CharField(max_length=30, unique=True)

class Stay(models.Model):
    person = models.ForeignKey(Person)
    STAY_TYPE = (
        (1, 'Type 1'),
        (2, 'Type 2'),
        (3, 'Type 3'),
    )
    stay_type = models.CharField(max_length=1, choices=STAY_TYPE)

class Hospitalization(models.Model):
    stay = models.ForeignKey(Stay)
    date_in = models.DateField()
    date_out = models.DateField(blank=True, null=True)
    room = models.ForeignKey(Room)

С queryset (в менеджере) я хотел бы получить все доступные комнаты на заданную дату, т.е. где max_persons не встречается.

До сих пор я пытался поиграть с Q и такими вещами, как Room.objects.filter(hospitalization__date_out__lt="2014-04-25"), но я не могу понять сравнение max_persons.

У вас есть идея?

ИЗМЕНИТЬ

Количество объектов Person, связанных с каждой комнатой, отражает количество людей в этой комнате.

Поэтому, согласно приведенным ниже предложениям Карла, я попытался поиграть с:

  • Room.objects.filter(hospitalization__date_out__lte="2014-04-25").annotate(num_persons=hospitalization_set__stay__person.Count()).exclude(num_persons__lte=max_persons)
    • Yields NameError: name 'hospitalization_set__stay__person' is not defined
  • Room.objects.filter(hospitalization__date_out__lte="2014-04-25").annotate(num_persons=Count('hospitalization_set__stay__person')).exclude(num_persons__lte=max_persons)
    • Yields FieldError: Cannot resolve keyword 'hospitalization_set' into field.
  • Room.objects.filter(hospitalization__date_out__lte="2014-04-25").annotate(num_persons=Count('hospitalization__stay__person')).exclude(num_persons__lte=F('max_persons'))
    • Yields nothing ([]), while I'm expecting at least three results according to my current DB.
    • При удалении .exclude() я все еще не получаю никаких результатов. Похоже, что .annotate(), который я использую, делает что-то не так.

person SaeX    schedule 19.04.2014    source источник


Ответы (1)


Я считаю, что что-то похожее на Room.objects.filter(hospitalization__date_out="2014-04-25", max_persons__lt=x) должно помочь. Помните, что у вас может быть несколько объявлений в операции фильтрации, а также вы можете связать как исключения, так и фильтры. Это не увеличивает активность базы данных; Фильтры queryset и тому подобное выполняются только тогда, когда оценивается сам queryset. См. Документацию Django по созданию запросов.

EDIT: Это на самом деле оказалось немного сложнее, чем я сначала думал, но я считаю, что разобрался (после импорта объявлений модели и сеанса оболочки!)

rooms = Room.objects.filter(DATE FILTERING HERE).annotate(num_persons=Count('hospitalization__room__id')).filter(num_persons__lt=F('max_persons')).count()

Дает вам количество комнат, в которых num_persons (наша аннотированная переменная выше) меньше, чем max_persons, с предварительной фильтрацией даты.

Не забудьте включить обязательный импорт: from django.db.models import Count from django.db.models import F

person Karl Fischmann    schedule 19.04.2014
comment
Хм, но я должен каким-то образом получить «x», используя тот же набор запросов, не так ли? Как это сработает? - person SaeX; 19.04.2014
comment
Это зависит от того, какое значение x должно иметь. Я предполагаю, что количество объектов Person, связанных с каждой комнатой, будет отражать количество людей в этой комнате? В этом случае вы должны аннотировать набор запросов с количеством этих отношений. Я бы лично изменил объявления вашей модели, чтобы включить более простую связь между «Человек» и «Комната». Как бы то ни было, я считаю, что что-то подобное может работать (непроверено): Room.objects.filter(hospitalization__date_out=2014-04-25).annotate(num_persons=Hospitalization_set__stay__person.Count()).exclude(num_persons__lte=max_persons) - person Karl Fischmann; 19.04.2014
comment
Правильно, количество объектов Person, связанных с каждой комнатой, будет отражать количество людей в этой комнате. Я думал об изменении своих моделей, чтобы упростить вещи, но проблема в том, что за одно Пребывание Комната Человека может измениться; Добавлю к своему вопросу. Я попробовал ваш запрос (спасибо за помощь!!), но не смог заставить его работать. Кажется, я не могу использовать обратный hospitalization_set в запросе. Я попытался исправить это, добавив путь в качестве параметра в функцию агрегирования Count (не уверен, что это нормально), но теперь я не могу выполнить сравнение «исключить». - person SaeX; 21.04.2014
comment
(Исправлено сравнение exclude с использованием объектов F - скорректирован вопрос) - person SaeX; 21.04.2014
comment
Я совершенно забыл добавить комментарий здесь @SaeX ... Пожалуйста, смотрите приведенное выше редактирование моего ответа. Этот запрос должен дать вам ответ, который вы ищете, без необходимости изменять объявления вашей модели. - person Karl Fischmann; 23.04.2014
comment
Спасибо, Карл, ваши правки упускают именно ту часть, с которой я все еще борюсь, то есть DATE FILTERING HERE. Но вы ответили на мой главный вопрос: как получить num_persons и сравнить с max_persons. Спасибо за это! Я открою другой вопрос, поиграв с ним еще немного. - person SaeX; 23.04.2014