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(), все должно работать так, как ожидалось. Обратите внимание, что это решение предполагает, что каждый вариант связан только с одним вопросом. Если выбор может быть связан с несколькими вопросами, вам нужно будет соответствующим образом настроить подзапрос.