Django - 处理投票的最佳方式,随机选择并只向用户显示一次?

时间:2014-12-17 13:20:41

标签: python mysql django django-rest-framework

我正在构建一个应用,按类别向用户显示随机问题。 每个用户都应该通过" yes"," no"进行投票。或" na"。 该应用程序计算每个问题的投票数,每个用户可以为每个问题投票一次。

这些问题应该随机出现给用户,并且不应该每个用户出现一次以上(用户无法进行投票)。

models.py:

class Category(models.Model):
    name = models.CharField(max_length=500, null=False)
    parent = models.ForeignKey("self", null=True, default=None)

class Question(models.Model):
    question = models.CharField(max_length=500, null=False, blank=False)
    title = models.CharField(max_length=100, null=False, blank=False)
    category = models.ForeignKey(Category, null=True, default=None, blank=True)
    no_count = models.BigIntegerField(default=0)
    yes_count = models.BigIntegerField(default=0)
    na_count = models.BigIntegerField(default=0)
    user = models.ForeignKey(User, null=True, default=None)
    rand = models.FloatField(null=True, default=0)

    def save(self, *args, **kwargs):
        self.rand = random.random()
        super(Picture, self).save(*args, **kwargs)

class Vote(models.Model):
    VOTE_CHOICES = (
        (1, 'Yes'),
        (2, 'No'),
        (3, 'N/A'),
    )
    user = models.ForeignKey(User)
    question = models.ForeignKey(Question, null=True, default=None)
    user_vote = models.IntegerField(choices=VOTE_CHOICES)

class UserSettings(models.Model):
    user = models.OneToOneField(User)
    categories = models.CommaSeparatedIntegerField(max_length=1000, null=True)

views.py:

class GetQuestions(generics.ListAPIView):
    model = Question
    serializer_class = QuestionSerializer

    def get_queryset(self):
        user = self.request.user
        lookup = dict()
        categories = user.usersettings.categories
        if categories is None:
            categories = Category.objects.filter(~Q(parent=None)).values_list('id', flat=True)
        else:
            categories = ast.literal_eval(categories)
        lookup['category__in'] = categories
        voted = Vote.objects.filter(user=self.request.user).values_list('question')
        questions = Question.objects.filter(**lookup).exclude(id__in=voted).order_by('rand')
        return questions

class NewVote(generics.CreateAPIView):
    model = Vote
    serializer_class = VoteSerializer

    def post(self, request, *args, **kwargs):
        current_vote = Vote.objects.filter(user=request.user, picture=int(self.request.DATA['question']))
        if current_vote:
            return HttpResponseForbidden()
        return super(NewVote, self).post(request, *args, **kwargs)

    def pre_save(self, obj):
        obj.user = self.request.user

    def post_save(self, obj, created=False):
        if created:
            vote_count = obj.vote.get_user_vote_display().lower().replace(" ", "")
            vote_count += "_count"
            count = getattr(obj.picture, vote_count)
            setattr(obj.picture, vote_count, count + 1)
            obj.picture.save()

投票时我只是增加问题的相关数量。 我的问题是:

  1. 选择随机问题的最佳方法是什么?目前我已经在问题上添加了随机字段并使用了order_by(' rand') - 有更好的方法吗?
  2. 选择用户类别问题的最佳方法是什么?目前我正在使用过滤器类别_in
  3. 最重要的一个 - 如何排除用户已投票的问题?目前我只是从投票表中选择user = request.user并使用" NOT IN" - 缩放时,肯定不会很好......
  4. 欢迎使用概念,代码示例和链接。

    非常感谢

6 个答案:

答案 0 :(得分:1)

question = Question.objects.filter(category__in=categories).exclude(vote_set__user=user).order_by('?')[0]

这应该有用。

filter将确保您获得的问题来自所需的类别

exclude将排除用户已投票的所有问题

order_by('?')将以随机方式对查询集进行排序。

答案 1 :(得分:1)

questions = list(Question.objects.filter(category__in=categories)) 
  

在会话中存储问题列表

“选择随机问题的最佳方式是什么?”

  
    

使用python random.choice随机化问题列表。

  

“如何排除用户已投票的问题”

  
    

每次用户回答问题时,请执行问题。删除(问题)并将其保存回会话。

  

答案 2 :(得分:0)

select * from question order by rand()

将随机排序所有行。如果表有很多行,查询将很慢。这里有一些approch: How can i optimize MySQL's ORDER BY RAND() function?

答案 3 :(得分:0)

import random
questions = Question.objects.filter(category__in=categories).exclude(vote_set__user=user)
rand_question = random.choice(questions)

答案 4 :(得分:0)

让用户登录是强制“一个用户一票”约束的唯一方式,无论您是否可以使用服务器端或客户端代码以其他方式模仿此约束,但两者都很容易被破解。

关于性能,不要一次选择所有问题,因为希望你没有在一个大的服务器响应中向你的用户发送所有这些信息,但是你正在使用ajax加载下一个问题。

在可扩展的Web应用程序中,您在代码的回复方面没有太多计算内存和内存扩展代码,因此您肯定不会使用python随机化您的数据,但您将使用您的数据库随机化这些代码,表示可能你必须首先使用django orm然后进行原始查询来检查数据库后端的随机性能。

django查询应如下所示:

Question.objects.exclude(replies__user=current_user).order_by('?')[:10] 

查询确实很昂贵,因为你在MtM字段上进行过滤,然后进行随机排序,但是一开始就足够了,如果你发现它太慢,你可以做这样的事情

num = some integer 
rando = some random bigger or equal to num, and smaller than the Question table length 
Question.objects.all()[rando-num: rando].exclude(replies__user=current_user).order_by('?') 

所以你有一个双随机快速的一个告诉你的数据库你只需要一个(随机但你的数据库不知道那个;))问题表的子集,然后只做那个昂贵的工作,大多数情况下你不希望你的子集(包括连接等)超过你的L2内存大小,而整个表可能很容易超过(如果你必须在ram和缓存之间传递信息,随机事情可能会更难)。

交易是因为您无法保证会有任何结果,因此仍有问题的用户无法在给定的请求中看到任何结果,但您可以做一些,有点贵,额外的查询,如果是这种情况,。

答案 5 :(得分:0)

随机事物总是与关系数据库进行权衡。 没有明确的答案,这完全取决于你究竟需要什么。

你真的需要随机性吗? 它会查找您的情况,您可以选择下一个未回答的问题。除非用户创建多个帐户,否则他无法注意到。我唯一能看到的问题是你是否需要在问题中传播答案。

您的应用程序是否会在任何给定时间处理有限数量的未解决问题? 如果是这样,并且您预计不会有太多流量,您可以坚持使用order_by('?')。任何低于几千个未解决问题的事情都可以。

下一步:放弃排序 分拣真的很贵。因为它将生成所有行,所以将随机值附加到它们,然后对它们进行排序,然后只选择第一行。这很快就会变得昂贵。 你的下一个选择是自己挑选。

questions = list(Question.objects.filter(whatever_condition))
return random.sample(questions, 10)

您仍然会在内存中加载每个问题。如果他们有很多数据,或解析它们有点复杂,那仍然会很昂贵。

下一步:放弃加载 这里的第一个小权衡:我们将进行两次查询。

question_ids = Question.objects.filter(whatever_condition).values_list('id', flat=True)
questions = Question.objects.filter(pk__in=random.sample(question_ids, 10))
return questions

第一个查询返回所有有效的id,第二个查询选择一组随机的id并加载完整的实例。

下一步:它变得混乱 根据您的具体情况,您可以采取许多措施来改进它。一些想法:

  • 如果您不删除问题,可以盲目选择ID,方法是选择1和问题总数之间的随机整数,不包括用户已经回答的问题。
  • 如果你删除问题但不是很多,只需重试,直到你运气好。要使这种方法起作用,您需要将删除的问题的比例相对较低。
  • 如果你知道你会删除很多问题,但你也很清楚有效问题的传播,你可以将你的问题划分到一个已知平均有效问题数量的范围内。
  • 或者您可以在每行的已知限制内存储随机整数。这为您提供了均匀分布,因此您可以定位范围并了解要获取的项目数。例如,您的整数在[1..100000]中。如果您有5000个有效问题,并且想要获得10个问题,则可以在[x..x + 400]范围内选择随机x和过滤问题。你得到大约20,你保持10(不要忘记你可能非常不走运,得到更少,处理)。不要忘记在上限包裹。

最后一个选项应该执行非常快速的读取,因为可以索引数字。