Django: Неподдерживаемый поиск для аннотированных полей ForeignKey

В приложении django у меня есть модель Question с пользовательским manager:

class QuestionManager(models.Manager):
    def get_queryset(self):
        return QuestionQS(self.model).get_queryset()


class Question(models.Model):
    objects = QuestionManager()
    text = models.CharField(max_length=255)
    label = models.CharField(max_length=40)

и модель Choice, которая имеет внешний ключ к Question:

class Choice(models.Model):
question = models.ForeignKey(Question, related_name="choices", on_delete=models.PROTECT)
text = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)

В моем классе Queryset есть пользовательская аннотация (annotate_flages метод, аннотирующий status), которую я ожидаю, что она будет включена в каждый Queryset, и другая пользовательская аннотация (extra_annotations метод, аннотирующий is_editable), которую я хочу включить вручную:

class QuestionQS(models.QuerySet):
    def get_queryset(self):
        return self.annotate_flags()

    def annotate_flags(self):
        qs = self.annotate(
            status=Case(
                When(label__icontains="active", then=Value(1)),
                When(label__icontains="closed", then=Value(0)),
                default=Value(0),
                output_field=IntegerField(),
            )
        )
        return qs

    def extra_annotations(self):
        one_hour_ago = timezone.now() - datetime.timedelta(hours=1)
        return self.prefetch_related(
            Prefetch(
                "choices",
                queryset=Choice.objects.annotate(
                    is_editable=Case(
                        When(
                            Q(Q(created_at__gt=one_hour_ago) & Q(question__status=1)),
                            then=True,
                        ),
                        default=False,
                        output_field=BooleanField(),
                    )
                ),
            )
        )

Моя проблема заключается в том, что аннотации по умолчанию (status) включены в наборы запросов, но у меня нет доступа к ним из моего пользовательского метода extra_annotations.

При вызове пользовательского метода, например:

questions = Question.objects.get_queryset().extra_annotations()

Я получаю неподдерживаемую ошибку поиска:

Unsupported lookup 'status' for ForeignKey or join on the field not permitted.

Аннотации QuerySet в Django не переносятся на связанные модели. В вашем случае аннотация status находится в модели Question, но вы пытаетесь получить к ней доступ из модели Choice в методе extra_annotations. Это не поддерживается Django напрямую.

Однако это можно обойти, используя подзапрос для аннотирования модели Choice со статусом связанного с ней вопроса.

from django.db.models import Subquery, OuterRef

class QuestionQS(models.QuerySet):
    def get_queryset(self):
        return self.annotate_flags()

    def annotate_flags(self):
        qs = self.annotate(
            status=Case(
                When(label__icontains="active", then=Value(1)),
                When(label__icontains="closed", then=Value(0)),
                default=Value(0),
                output_field=IntegerField(),
            )
        )
        return qs

    def extra_annotations(self):
        one_hour_ago = timezone.now() - datetime.timedelta(hours=1)
        status_subquery = Question.objects.filter(pk=OuterRef('question_id')).values('status')[:1]
        return self.prefetch_related(
            Prefetch(
                "choices",
                queryset=Choice.objects.annotate(
                    question_status=Subquery(status_subquery),
                    is_editable=Case(
                            When(Q(Q(created_at__gt=one_hour_ago) & Q(question_status=1)),
                            then=True,
                        ),
                        default=False,
                        output_field=BooleanField(),
                    )
                ),
            )
        )

В этом коде status_subquery - это подзапрос, который получает статус вопроса, связанного с каждым вариантом ответа. Этот статус затем аннотируется к объектам Choice как question_status, который вы можете использовать в аннотации is_editable. Теперь, когда вы вызываете Question.objects.get_queryset().extra_annotations(), все должно работать так, как ожидалось. Обратите внимание, что это решение предполагает, что каждый вариант связан только с одним вопросом. Если выбор может быть связан с несколькими вопросами, вам нужно будет соответствующим образом настроить подзапрос.

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