简体   繁体   English

在 Django 中只对帖子和评论投票一次

[英]Vote only once in Django to posts and comments

I have two models called Post and Comment and a User that can upvote or downvote that post or comment.我有两个模型,称为PostComment ,还有一个User可以支持或反对该帖子或评论。 I want to restrict each user however to be able to to vote only once to a certain Post or Comment .但是,我想限制每个用户只能对某个PostComment投票一次。

I could do something on the model level to Post and Comment and instead tally up total votes for the specific post or comment.我可以在 model 级别上做一些事情来PostComment ,而不是计算特定帖子或评论的总票数。 However, I think it's a bit more scalable to create a separate Vote model where:但是,我认为创建单独的Vote model 更具可扩展性,其中:

  1. total votes can be tallied with one query ( SELECT count(*) FROM votes where user_id= , etc...)总票数可以通过一个查询来计算( SELECT count(*) FROM votes where user_id=等...)
  2. voting once per user can be enforced with unique_together可以使用unique_together强制每个用户投票一次

Currently I have figured out something like this:目前我已经想出了这样的事情:

import uuid
from django.db import models
from django.contrib.auth import get_user_model
from django.db import models

# Create your models here.


class Post(models.Model):
    LINK = "link"
    VIDEO = "video"
    IMAGE = "image"
    POST_TYPE_CHOICES = ((LINK, "Link"), (VIDEO, "Video"), (IMAGE, "Image"))

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=300)
    url = models.URLField(max_length=300)
    category = models.CharField(max_length=50)
    score = models.DecimalField(default=0, max_digits=20, decimal_places=2)
    votes = models.IntegerField(default=0)
    views = models.IntegerField(default=0)
    post_type = models.CharField(max_length=5, choices=POST_TYPE_CHOICES, default=LINK)
    text = models.CharField(max_length=40000)
    owner = models.ForeignKey('users.User', related_name='posts', on_delete=models.CASCADE)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ["-created"]


class PostVote(models.Model):
    DOWNVOTE = -1
    UPVOTE = 1
    UNVOTE = 0
    VOTE_TYPE_CHOICES = ((DOWNVOTE, "Downvote"), (UPVOTE, "Upvote"), (UNVOTE, "Unvote"))

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    score = models.IntegerField(choices=VOTE_TYPE_CHOICES, default=UPVOTE)
    voter = models.ForeignKey('users.User', related_name='post_votes', on_delete=models.CASCADE)


class Comment(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    body = models.CharField(max_length=10000)
    post = models.ForeignKey('Post', related_name='comments', on_delete=models.CASCADE)
    owner = models.ForeignKey('users.User', related_name='comments', on_delete=models.CASCADE)

    class Meta:
        ordering = ["-created"]

class CommentVote(models.Model):
    DOWNVOTE = -1
    UPVOTE = 1
    UNVOTE = 0
    VOTE_TYPE_CHOICES = ((DOWNVOTE, "Downvote"), (UPVOTE, "Upvote"), (UNVOTE, "Unvote"))

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    score = models.IntegerField(choices=VOTE_TYPE_CHOICES, default=UPVOTE)
    voter = models.ForeignKey('users.User', related_name='comment_votes', on_delete=models.CASCADE)

    def __str__(self):
        return self.score

    class Meta:
        ordering = ["-created"]

I just know this is a bad model of separating the votes to PostVote and CommentVote and instead would want a more generic Vote model where I could specify the vote to be towards a Post or a Comment .我只知道这是一个糟糕的 model 将投票分开到PostVoteCommentVote ,而是想要一个更通用的Vote model ,我可以在其中指定投票是针对PostComment

What I don't want is having a Vote model that has both the post_id and comment_id which would make it so that either one is always null, since one vote is always either for a Comment or a Post .我不想要的是Vote model 同时具有post_idcomment_id这将使得任何一个总是 null,因为一个投票总是要么是Comment要么是Post

Any help?有什么帮助吗?

This can be solved better by using Generic Relations .这可以通过使用Generic Relations更好地解决。 This https://simpleisbetterthancomplex.com/tutorial/2016/10/13/how-to-use-generic-relations.html is a great article to understand the concept and the example here is something similar to your requirement.这篇https://simpleisbetterthancomplex.com/tutorial/2016/10/13/how-to-use-generic-relations.html是一篇很好的文章来理解这个概念,这里的例子与您的要求相似。

I believe that, in your example, one important missing thing is that a vote doesn't know to which Post or Comment it belongs.我相信,在你的例子中,一个重要的缺失是投票不知道它属于哪个PostComment

Generic relations一般关系

You could have a single Vote model as you already guessed in the question using a generic relation .正如您在使用泛型关系的问题中已经猜到的那样,您可以有一个单一的Vote model 。 In practice, this is a foreign key which can point to different models.在实践中,这是一个可以指向不同模型的外键。

An example could be:一个例子可能是:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

class Vote(models.Model):
   # ...
   # your model stuff
   # ...
   content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
   voted_object_id = models.UUIDField(default=uuid.uuid4)
   voted_object = models.GenericForeignKey('content_type', 'voted_object_id')

And in your Post and in your Comment , in order to be able to access votes of a "voted object" with a query (inverse relationships for generic foreign keys aren't automatically generated):在您的Post和您的Comment中,为了能够通过查询访问“投票对象”的投票(不会自动生成通用外键的反向关系):

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation

class Post(models.Model):
   # ...
   # other stuff
   # ...
   votes = GenericRelation(Vote, related_query_name='post')

class Comment(models.Model):
   # ...
   # other stuff
   # ...
   votes = GenericRelation(Vote, related_query_name='comment')

In this way you can access the "voted object" (so the Post or the Comment) in this way:通过这种方式,您可以通过以下方式访问“投票对象”(即帖子或评论):

post = Post.objects.all()[0]
vote_to_post = Vote.objects.create(**vote_data, voted_object=post)
print(vote_to_post.voted_object)  # <Post: (0)>

print(post.votes)  # <QuerySet [<Vote: 0>]>

I believe, but I didn't have the opportunity to test, that you can also do:我相信,但我没有机会测试,你也可以这样做:

print(vote_to_post.post)  # <Post: (0)>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM