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

标签 python mysql django django-rest-framework

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

问题应该随机出现在用户面前,每个用户不应出现超过一次(用户不能在没有投票的情况下继续)。

模型.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. 最重要的一个 - 如何排除用户已经投票的问题?目前我只是从用户 = request.user 的投票表中选择所有问题并使用“NOT IN” - 当缩放时这肯定不会很好......

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

非常感谢

最佳答案

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

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

您的应用程序是否会在任何给定时间处理有限数量的未决问题? 如果是这样,并且您预计不会有太多流量,您可以坚持使用 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 和问题总数之间的随机整数(不包括用户已回答的问题)来盲目选择 ID。
  • 同样,如果您删除问题但不多,只需重试,直到您幸运为止。要使这种方法奏效,您的问题删除率需要相对较低。
  • 如果您知道您会删除很多问题,但您也很清楚有效问题的分布情况,您可以对问题进行划分,以达到有效问题平均数量已知的范围。
  • 或者您可以在每一行中存储一个已知限制内的随机整数。这为您提供了均匀分布,因此您可以定位一个范围并了解将获取多少项目。例如,您的整数在 [1..100000] 中。如果你有 5000 个有效问题,而你想得到 10 个,你可以随机选择一个 x 并在 [x..x+400] 范围内过滤问题。你得到大约 20 个,你保留 10 个(不要忘记你可能很不走运,得到的更少,处理它)。不要忘记在上限处换行。

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

关于python - Django - 处理投票的最佳方式,随机选择并只向用户显示一次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27526551/

相关文章:

mysql - 尝试使用 DataGrip 连接到 MySQL 实例时如何修复导致异常的服务器时区?

mysql - 行的最小值和最大值,而不是列的最小值和最大值

python - 使用 python 处理 ember.js 标记的 html

python - 在djangorest框架中访问用户

jquery - 获取文本区域或文本编辑器的值

python - 使用elasticsearch-dsl的delete方法时版本冲突

Django:POST 表单需要 CSRF? GET 没有?

django - 如何在 Django 中调用 forms.Select 的 render_option()?

python - Python 如何使用 Gunicorn 和 Kubernetes 进行扩展?

java - 使用 Python 读取 AVRO 文件