【问题标题】:How to structure models for forecasting app?如何构建预测应用程序的模型?
【发布时间】:2019-10-25 23:17:28
【问题描述】:

在 Django 中,我正在寻找有关如何为类似于投票或测验应用程序的预测应用程序构建模型的建议 - 但不完全是。

要求概述:

(1) 一个测验会有多个问题。

(2) 问题可以有多种形式 - 对或错、3 个选项的多项选择、4 个选项的多项选择等。

(3) 用户以概率的形式提交每个问题的预测(也称为答案),但总概率为 100%。因此,对于具有三个选项 A-C 的问题 #1,用户可能会预测 A:30%、B:50%、C:20%

(4) 每题有1个正确答案。

[使用 Brier 评分对问题进行评分,但这对于本次讨论不是必需的。]

我熟悉 Django 教程投票应用程序并查看过多个测验应用程序,但没有一个能解决我的问题。

如果我使用 Django 投票教程的结构且选择的数量不确定,那么我无法弄清楚如何组织用户对问题的预测 - 因为该预测必须具有每个选择的概率和概率加起来必须达到 100%。

如果我创建多个问题模型,例如 TrueFalseQuestion、MultipleChoice3OptionsQuestion 等,那么我的视图和模板就会变得笨拙,因为我不能只为问题模型设置上下文。

我考虑使用 abstract=True 创建一个父类 Question,然后创建像“class TFQuestion(Question):”这样的子类。但是,这再次限制了我使用 ListView 等通用模板的能力,因为我现在有很多子类。

总而言之,我的问题与我能找到的其他问题的不同之处在于:大多数测验应用程序都有一个用户提供多个选项中的一个选项,无论选项有多少。我的应用需要一个答案(预测),其中包含每个选择选项的概率以及概率等于 100% 的约束。

****************** 在下面添加更多细节 *************

为了简化,假设我希望每个问题都有 3 个多项选择选项。在这种情况下,每个用户预测都将包含一组 3 个概率和一条评论。

第一个问题:对于 3 个答案选项的情况,是否有更好的方法来构建以下模型?

class Quiz(models.Model):
   name = models.CharField(max_length=20)
   owner = models.ForeignKey(User, on_delete=models.CASCADE)

class Question(model.Model):
   quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
   question_text = models.CharField(max_length=200)
   choice1 = models.CharField(max_length=200)
   choice2 = models.CharField(max_length=200)
   choice3 = models.CharField(max_length=200)
   correct_choice = models.IntegerField()

class Forecast(model.Model):
   question = models.ForeignKey(Question, on_delete=models.CASCADE)
   user = models.ForeignKey(User, on_delete=models.CASCADE)
   comment = models.CharField(max_length=200)
   prob1 = models.IntegerField()
   prob2 = models.IntegerField()
   prob3 = models.IntegerField()

如果这是具有 3 个选项的问题的良好结构,那么我将如何将其扩展到具有 2、3、4、5 个选项的问题的原始要求?

我的问题是,如果我使用 Django 教程投票应用程序中的结构,其中选择的数量是不确定的 - 那么我上面的预测模型会中断,因为它具有硬编码的 3 个选项。

【问题讨论】:

  • 我认为您的问题过于宽泛,无法获得相关答案。看起来你在这里提出了你的要求,期望有人会为你写一个 django-model。相反,开始起草你的模型,他们的关系,然后提出更狭窄的问题。为了帮助您入门,如果您使用 django 模型更新您的问题,如果您在某个地方遇到困难,我很乐意审查并提供帮助。
  • 另外,我会考虑添加 django-model 标记并删除 postgresql,因为您的问题与数据库后端没有直接关系
  • @alfjet - 非常感谢您的参与。如果不是很明显,我是 Web 开发的新手。在上面的帖子中,我对其进行了编辑,以考虑具有 3 个选项的简化问题案例。我很想听听有关此结构的任何建议。并且,最终我将如何构建具有不同数量选项的问题案例。感谢您的帮助!

标签: python django django-models


【解决方案1】:

听起来像这样的结构对你有用(对错题被建模为二选题):

class Quiz(models.Model):
   name = models.CharField(max_length=20)
   owner = models.ForeignKey(User, on_delete=models.CASCADE)


class Question(model.Model):
   quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
   text = models.CharField(max_length=200)
   correct_choice = models.ForeignKey('Choice')


class Choice(model.Model):
   question = models.ForeignKey(Question, on_delete=models.CASCADE)
   text = models.CharField(max_length=200)


class Forecast(model.Model):
   question = models.ForeignKey(Question, on_delete=models.CASCADE)
   user = models.ForeignKey(User, on_delete=models.CASCADE)
   comment = models.CharField(max_length=200)

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


class ForecastChoice(model.Model):
   forecast = models.ForeignKey(Forecast, on_delete=models.CASCADE)
   choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
   probability = models.IntegerField()

   class Meta:
       unique_together = (('forecast', 'choice'),)

无法使用unique_together 建模的约束留给读者作为练习:

  • 预测必须以与问题中的选项一样多的 ForecastChoices 结束
  • 预测的 ForecastChoices 概率之和必须为 100

您可能还需要一个 ordering 问题字段(如果这在这里很重要),以及通常的元数据,例如 Forecasts 的创建时间。

编辑 根据 cmets 的要求,ForecastForm 的示例为每个选择动态创建了概率字段以及驱动它的视图。它是干编码的,所以可能会有一些愚蠢的错误,但这个想法是可靠的。

class ForecastForm(forms.ModelForm):
    class Meta:
        model = Forecast
        fields = ('comment',)  # tell Django to only create this field

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # Build dynamic fields.
        self.probability_fields = []
        for choice in self.instance.question.choice_set.all():
            name = f'probability_{choice.id}'
            field = forms.IntegerField(
                label=f'{choice.text} – probability',
                min_value=0,
                max_value=100,
                required=True,
            )
            # Put the field in the form...
            self.fields[name] = field
            # ... and store the name + choice object for later validation use
            self.probability_fields.append((name, choice))

    def clean(self):
        super().clean()
        probability_sum = 0
        for name, choice in self.probability_fields:
            probability = self.cleaned_data[name]
            probability_sum += probability
        if probability_sum != 100:
            raise forms.ValidationError(f'Probabilities sum up to {probability_sum}, not the expected 100.')

    def save(self):
        with transaction.atomic():
            super().save()  # will save the `self.instance` Forecast object with the comment
            for name, choice in self.probability_fields:
                ForecastChoice.objects.create(
                    forecast=self.instance,
                    choice=choice,
                    probability=self.cleaned_data[name],
                )

# This is a slightly unorthodox updateview in that the form it's driving
# is not directly related to the model the underlying "detail view" is acquiring.

class QuestionForecastView(views.UpdateView):
    model = Question

    def get_form(self, form_class=None):
        # Unsaved forecast initialized with the question and user context.
        forecast = Forecast(
            question=self.get_object(),
            user=self.request.user,
        )
        kwargs = self.get_form_kwargs()
        kwargs['instance'] = instance
        return ForecastForm(**kwargs)

【讨论】:

  • 非常感谢。我真的很感激这个建议。我需要一些时间来思考如何在这个结构中施加约束,但指出正确的方向会大有帮助。
  • save()/clean() 函数中或在您将接受数据的表单中实现这些约束并不难。
  • 我已尝试实现上述结构,但在创建 ForecastForm 时遇到了麻烦。 ForecastForm 应该有一个评论输入以及每个问题选择的概率输入,因此将创建多个对象 - 每个选择的 Forecast 和 ForecastChoice 对象。我似乎无法找到一个类比。关于我可以查看的示例的任何建议? FWIW,我确实阅读了 save()/clean() 以及如何实现约束。这就说得通了。感觉好像我在表单上做错了什么。任何建议将不胜感激!
  • 我可以找到存在单一已知外键关系的表单示例,例如书籍和出版商之间的关系。但是,我还没有找到一个示例,其中存在不确定数量的外键关系,就像上面的 ForecastChoice 情况一样。我是新手,可能在这里遗漏了一些明显的东西。
  • 谢谢AKX。对此,我真的非常感激。我会在这个问题上研究几天,然后告诉你结果如何。
猜你喜欢
  • 1970-01-01
  • 2021-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多