Получение количества связанных элементов и вычитание его из атрибута в django

У меня три модели, и я пытаюсь немного упростить свои запросы.

Я пытаюсь вернуть список Course объектов, которые не были полностью зарегистрированы/оплачены. Поэтому мне нужно получить количество Payment объектов, а затем посмотреть, равно ли оно общему количеству available_seats на Course объекте.

У меня есть Payment, Course и Registration модели:

class Payment(models.Model):
    payment = models.ForeignKey(Payment, on_delete=models.PROTECT)
    registration = models.ForeignKey(Registration, on_delete=models.PROTECT)
    is_refunded = models.BooleanField(default=False)

class Course(models.Model):
    course = models.ForeignKey(Course, on_delete=models.PROTECT)
    location = models.ForeignKey(Location, on_delete=models.PROTECT)
    seat_count = models.IntegerField()

class Registration(models.Model):
    person = models.ForeignKey(Person, on_delete=models.PROTECT)
    course = models.ForeignKey(Course, on_delete=models.PROTECT)
    comments = models.CharField(max_length=200, blank=True, null=True)

Я хочу считать место "занятым", только если было сделано Payment - поэтому подсчет Payment для данного Course - это то, что я пытаюсь получить.

Я пытался сделать что-то вроде этого:

    payments = (
        Payment.objects.filter(
            registration__course__id=OuterRef("pk"), is_refunded=False
        ).values("pk")
        # .annotate(
        #     total_seats_available=F("registration__course__seat_count")
        #     - Count("registration")
        # )
        # .values("total_seats_available")
    )
    courses = (
        Course.objects.filter(id__in=course_ids)
        .prefetch_related(
            Prefetch(
                "registration_set",
                queryset=Registration.objects.prefetch_related(
                    "payment_set"
                ),
            )
        )
        .annotate(
            paid_seats=Case(
                When(
                    Exists(payments),
                    then=Count(payments),
                ),
                default=None,
            ),
            has_available_seats=Case(
                # No Payment have been recorded
                When(paid_seats=None, then=Value(True)),
                # Payment exist and total_seats_available should calc
                When(paid_seats__gt=0, then=Value(True)),
                # Default to False
                default=Value(False),
                output_field=BooleanField(),
            ),
        )
        .filter(has_available_seats=True)
    )

В настоящее время это возвращает счетчик объектов Payment и если он больше 0, то считается, что курс имеет свободные места.

Каким образом лучше всего выполнить логику, чтобы Course аннотировал значение, которое является seat_count - payments для получения точного представления о том, "сколько" мест действительно доступно?

Где должна находиться эта логика? Я оставил некоторую логику, которую я закомментировал и которая не работала. Я не могу понять, как это сделать правильно.

Я смог заставить это работать с помощью F() выражений:

    payments = (
        Payment.objects.filter(
            registration__course__id=OuterRef("pk"), is_refunded=False
        ).values("pk")
    )
    courses = (
        Course.objects.filter(id__in=course_ids)
        .prefetch_related(
            Prefetch(
                "registration_set",
                queryset=Registration.objects.prefetch_related(
                    "payment_set"
                ),
            )
        )
        .annotate(
            paid_seats=Case(
                When(
                    Exists(payments),
                    then=Count(payments),
                ),
                default=None,
            ),
            available_seats=Case(
                When(Exists(payments), then=F('seat_count')-F('paid_seats'),
                default=F('seat_count'),
                output_field=IntegerField(),
            ),
            has_available_seats=Case(
                When(available_seats__gte=0, then=Value(True)),
                default=Value(False),
                output_field=BooleanField(),
            ),
        )
        .filter(has_available_seats=True)
    )
Вернуться на верх