[Answered ]-Annotating single model instance (instead of queryset) in django

1👍

If I understood you already have a Post instance, and you would like to get its score? I so, I think you could implement a computed property as follow:

class Post(models.Model):
    voters = models.ManyToManyField(User, through="PostVote")

    @property
    def score(self):
        return sum([-1 if vote.state == 'downvote' else 1 for vote in self.postvotes.all()])

Note that this method will trigger extra DB queries if you didn’t fetch your Post instance with .prefetch_related('postvotes')

Then you can use post.score to get the result

Another way to implement the property would be by doing a query instead:

class Post(models.Model):
    voters = models.ManyToManyField(User, through="PostVote")

    @property
    def score(self):
        return PostVote.objects.filter(post=self.pk).aggregate(vote=models.Sum(
            models.Case(
                models.When(state="upvote", then=1),
                models.When(state="downvote", then=-1),
                default=0,
                output_field=models.IntegerField()
            )
        ))['vote']

Note that I didn’t try any of those code, so there might be any typo/wrong variable names

0👍

Another solution might be to add it to your query sets by default.
This should make the annotation available throughout.

  1. Create a file managers.py inside of your app. And create the custom queryset/manager

    from django.db import models
    
    class PostQuerySet(models.QuerySet):
        def annotate_vote(self):
          return self.annotate(vote=models.Sum(
              models.Case(
                models.When(postvote__state="upvote", then=1),
                models.When(postvote__state="downvote", then=-1),
                default=0,
                output_field=models.IntegerField()
            )))
    
    class PostManager(models.Manager):
        def get_queryset(self):
            return PostQuerySet(self.model, using=self._db).\
                annotate_vote()
    
  1. Add the new manager as inside of your model.
from .managers import PostManager

class Post(models.Model):
    ...
    objects = PostManager()
    ...
  1. This should give you accesss to the data like so:

Post.objects.last().vote

👤S.D.

Leave a comment