Django / DRF: аннотируйте модель дополнительной (вычисляемой) информацией
Я добавляю на свой сайт очень простую функцию голосования по дорожной карте, где люди могут добавлять запросы на функции, а затем голосовать за предложения друг друга. Основы довольно просты:
# models.py
class FeatureRequest(models.Model):
title = models.CharField(max_length=50)
description = models.TextField()
author = models.ForeignKey(User, editable=False, on_delete=models.CASCADE)
is_implemented = models.BooleanField(default=False, editable=False, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Vote(models.Model):
feature = models.ForeignKey(FeatureRequest, editable=False, on_delete=models.CASCADE)
user = models.ForeignKey(User, editable=False, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ["feature", "user"]
#views.py
class RoadmapController(viewsets.ModelViewSet):
permission_classes = (AuthorOrReadOnlyPermission,)
serializer_class = FeatureRequestSerializer
def get_queryset(self):
return FeatureRequest.objects.filter(is_implemented=False)
def perform_create(self, serializer):
return serializer.save(author=self.request.user)
# serializers.py
class FeatureRequestSerializer(serializers.ModelSerializer):
class Meta:
model = FeatureRequest
fields = "__all__"
Существует также отдельное представление для фактического размещения и удаления голосов, но это не нужно для моего вопроса.
Я хочу, чтобы ответ списка запросов функций включал количество голосов за каждую из них и булево значение, если пользователь, вошедший в систему, проголосовал за нее:
[
{
"title": "This is a feature request",
"description": "Foo bar",
"author": 1,
"number_of_votes": 1,
"has_my_vote": true
}
]
Я уже понял, что могу изменить свой кверисет на FeatureRequest.objects.filter(is_implemented=False).annotate(number_of_votes=Count("vote")), добавить number_of_votes = serializers.ReadOnlyField() в сериализатор, и количество голосов будет видно. Это лучший способ? Я предполагаю, что он добавляет запрос для каждого запроса функции.
Но больше всего я не знаю, как добавить булево "has_my_vote" к результату (и как сохранить его работоспособность, конечно).
После некоторых проб и ошибок я пришел к следующему:
def get_queryset(self):
return (
FeatureRequest.objects.filter(is_implemented=False)
.annotate(number_of_votes=Count("vote"))
.annotate(has_my_vote=Exists(Vote.objects.filter(feature=OuterRef("pk"), user=self.request.user)))
)
Это работает, и делает только один запрос вместо одного плюс один или два в соответствии с запросом функции.