'How to create a voting model in Django?

I have a model called posts which users can vote up. If the user has voted a post up, he/she can only vote it down after that. How do I implement this system?

My current models looks something like this:

class Post(models.Model):
    title = models.CharField(max_length = 140)
    user = models.ForeignKey('Auth.user')
    votes = models.IntegerField(default = 0)

Is it better to make another model called Vote and link that up to Post, and if so, how do I do it in a way that will scale with a large number of users?



Solution 1:[1]

The case you explained, says that a post can only take up votes.

And as you need to hold who has voted up, we need to keep vote and it's user

class Post(models.Model):
    title = models.CharField(max_length = 140)
    user = models.ForeignKey('Auth.user')

    def votes_count(self):
        return self.votes.all().count()

class Vote(models.Model):
    class Meta:
        unique_together = [('post', 'user')]

    post = models.ForeignKey(Post, related_name='votes')
    user = models.ForeignKey('auth.User')

Solution 2:[2]

My answer is similar to that of @doniyor's. But, have added some more features :

  1. User cant upvote or downvote posts more than once
  2. When a post has been downvoted previously, it can be upvoted as well. To ensure this, I add +2 to Post.votes. Similarly, the vice-versa also holds.
  3. Have used vote_type as a bool variable to save some memory!
class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()
    votes = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def upvote(self, user):
        try:
            self.vote_set.create(user=user, post=self, vote_type=True)
            self.votes += 1
            self.save()
        except IntegrityError:
            vote = self.vote_set.filter(user=user)
            if vote[0].vote_type == False:
                vote.update(vote_type=True)
                self.votes += 2
                self.save()
            else:
                return 'already_upvoted'
        return 'ok'

    def downvote(self, user):
        try:
            self.vote_set.create(user=user, post=self, vote_type=False)
            self.votes -= 1
            self.save()
        except IntegrityError:
            vote = self.vote_set.filter(user=user)
            if vote[0].vote_type == True:
                vote.update(vote_type=False)
                self.votes -= 2
                self.save()
            else:
                return 'already_downvoted'
        return 'ok'

class Vote(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    vote_type = models.BooleanField()

    class Meta:
        unique_together = ('user', 'post')

Examples:

u = User.objects.get(pk=1)
p1 = Post.objects.get(pk=7)

>>> p1.votes
3

>>> p1.upvote(u)
'ok'
>>> p1.votes
4
>>> p1.upvote(u)
'already_upvoted'

>>> p1.downvote(u)
'ok'
>>> p1.votes       
2
>>> p1.downvote(u)
'already_downvoted'

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 kia
Solution 2 Muhammad Dyas Yaskur