'Django / DRF: annotate a model with extra (computed) information

I'm adding a very simple roadmap voting feature to my website, where people can add feature requests, and people can then vote on each other's suggestions. The basics are pretty simple:

# 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)

    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__"

There is also a separate view for actually creating and deleting votes, but that's not needed here for my question.

What I want is that the response of the feature request list includes the number of votes for each one, and a boolean if the logged-in user has voted for it:

[
  {
    "title": "This is a feature request", 
    "description": "Foo bar", 
    "author": 1, 
    "number_of_votes": 1, 
    "has_my_vote": true
  }
]

I already figured out that I can change my queryset to FeatureRequest.objects.filter(is_implemented=False).annotate(number_of_votes=Count("vote")), add number_of_votes = serializers.ReadOnlyField() to the serializer, and the number of votes is visible. Is that the best way? It adds a query for each feature request I would assume.

But most of all, I don't know how to add that "has_my_vote" boolean to the result (and how to keep that performant, of course).



Solution 1:[1]

After some trial and error, I came up with this:

    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)))
        )

That works, and it only does one query instead of one plus one or two per feature request.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kevin Renskers