【问题标题】:Django - best way to handle votes, with random select and show only once to user?Django - 处理投票的最佳方式,随机选择并向用户显示一次?
【发布时间】:2015-02-16 01:23:38
【问题描述】:

我正在构建一个应用来按类别向用户显示随机问题。 每个用户都应该通过“是”、“否”或“否”对问题进行投票。 该应用程序计算每个问题的投票数,每个用户每个问题可以投票一次。

问题应随机出现在用户面前,每个用户出现的次数不得超过一次(用户必须投票才能继续)。

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. 用户选择问题类别的最佳方式是什么?目前我正在使用过滤器 category__in
  3. 最重要的一项 - 如何排除用户已经投票的问题?目前我只是从投票表中选择 user = request.user 并使用“NOT IN”的所有问题 - 缩放时这肯定不好......

欢迎提供概念、代码示例、链接。

非常感谢

【问题讨论】:

  • 同一个问题能出现在更多用户面前吗?
  • 是的,同样的问题可以(并且应该)出现在不同的用户面前。
  • 如果你的设计中VoteManyToManyFieldUserthrough='Question',那不是更好吗?
  • 您的意思是不要让用户作为投票的外键,而是通过问题使其多对多,而不是删除问题字段?我当然可以这样做,但我能从中得到什么?我还是会遇到同样的问题,不是吗?
  • 你说q用户可以有更多问题,同一个问题可以出现给更多用户,那么这不就是ManyToMany的情况吗?我之前指定的设计在我看来似乎更自然

标签: python mysql django django-rest-framework


【解决方案1】:

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

你真的需要随机性吗? 它寻找您的案例,您可以选择下一个未回答的问题。除非用户创建多个帐户,否则他不会注意到。我会看到的唯一问题是您是否需要分散在各个问题中的答案。

您的应用程序是否会在任何给定时间处理有限数量的未解决问题? 如果是这样,并且您不希望有太多流量,您可以坚持使用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..x+400] 范围内的问题。你得到大约 20 个,你保留 10 个(不要忘记你可能很不走运并且得到更少,处理它)。不要忘记在上限处换行。

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

【讨论】:

    【解决方案2】:
    question = Question.objects.filter(category__in=categories).exclude(vote_set__user=user).order_by('?')[0]
    

    这应该可行。

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

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

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

    【讨论】:

    • 谢谢,关于 ('?') - 我在这里看到:docs.djangoproject.com/en/dev/ref/models/querysets 这是非常缓慢和昂贵的:“注意:order_by('?') 查询可能既昂贵又缓慢,取决于您使用的数据库后端。”会比按字段排序更好吗?
    • @eladelad TBH 你完全可以不用任何排序。由于您保证会收到用户未投票的问题,因此您可以默认获取您获得的第一个项目。
    • 你不需要随机化整个集合。那是昂贵的。不要返回第一个元素(索引'0'),而是使用 python random.choice 来随机化实例。
    【解决方案3】:
    questions = list(Question.objects.filter(category__in=categories)) 
    

    在会话中存储问题列表

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

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

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

    每次用户回答问题时,请执行 questions.remove(question) 并将其保存回会话。

    【讨论】:

      【解决方案4】:

      select * from question order by rand()

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

      【讨论】:

        【解决方案5】:
        import random
        questions = Question.objects.filter(category__in=categories).exclude(vote_set__user=user)
        rand_question = random.choice(questions)
        

        【讨论】:

        • 请为您的代码添加一些解释,解释它的作用以及它如何解决问题。这将有助于以后看到您的答案的其他人
        【解决方案6】:

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

        关于性能,不要一次选择所有问题,因为希望您不会在一个大型服务器响应中将所有信息发送给您的用户,而是使用 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 和缓存之间传递信息,随机的事情可能会更难)。

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

        【讨论】:

          猜你喜欢
          • 2014-01-02
          • 2019-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-04
          • 2012-01-30
          • 2014-05-31
          相关资源
          最近更新 更多