【问题标题】:Django validate answer for specific questionDjango 验证特定问题的答案
【发布时间】:2020-06-02 14:44:09
【问题描述】:

概述: 我想建立一个问答网站,用户必须为每个问题输入正确的答案。为此我制作了 3 个模型:

class ProblemSet(models.Model):
    id = models.IntegerField(primary_key=True)

class Problem(models.Model):
    id = models.IntegerField(primary_key=True)
    problem_set = models.ForeignKey(ProblemSet, on_delete=models.CASCADE)
    question = models.TextField()
    solution = models.TextField()

class Solve(models.Model):
    username = models.ForeignKey(User, on_delete=models.CASCADE)
    problem_set = models.ForeignKey(ProblemSet, on_delete=models.CASCADE)
    problem_id = models.ForeignKey(Problem, on_delete= models.CASCADE)

在求解模型中,如果有任何条目表示特定用户已解决该问题 ID。 所以,我使用了通用的表单视图:

class IndexView(FormView):
    form_class = ProblemForm
    template_name = 'home/index.html'
    success_url = reverse_lazy('index')

    def get_context_data(self, **kwargs):
        context = super(IndexView, self).get_context_data(**kwargs)
        if self.request.user.is_authenticated:
            inner_qs = "fetch ids that are solved from Solve model"

            problem_obj = Problem.objects\
                            .exclude(id__in=inner_qs)\
                            .order_by('id').first()

        else:
            #do something

        context['question'] = problem_obj.question
        return context

问题形式为:

from django import forms

class ProblemForm(forms.Form):
    solution = forms.CharField(widget=forms.TextInput())

如何验证用户输入的答案是否正确?我确实在 def form_valid(self, form) 函数中获得了 solution 字段的值,但我应该如何处理呢?我应该在 context 中传递 question_id 并在 form_valid 中查询数据库,还是应该将解决方案本身传递给 context 并在 form_valid() 方法中访问上下文数据以防止双重查询,但在这种方法中,我不确定这是否安全,因为我不不希望将解决方案传递给客户。

有什么优雅的方法吗?

附:在将用户输入的解决方案与该问题的数据库中的解决方案进行比较后,我在 Solve 表中添加一个条目,表示该特定用户已解决问题 ID。

【问题讨论】:

  • 我会做一个 javascript AJAX 请求。如果您将解决方案传递给带有问题的页面,用户只需检查页面源即可获得正确答案。如果你关心这一点,如果用户在荣誉系统上,这可能不是一个大问题。
  • 两个选项: 1. 将problem_id 作为隐藏输入字段添加到表单(您可以覆盖get_initial() 以预先填充该字段),以便在提交时提交他的答案。用户可以根据需要对其进行操作,但我看不到对用户有任何好处,即。没有安全问题。 2. 由于problem_id在用户没有解决问题的情况下不会改变,你可以在form_valid的情况下再次获取它。为避免多次获取它(您需要在 get_context_data 中使用它),请创建一个新方法 get_problem 并将值分配给 self,以便它在已定义或获取它时返回它。
  • @dirkgroten 看起来这是唯一的选择......我想避免在 form_valid() 函数中再次查询问题表。
  • @Waterbyte 无论您选择哪个选项,您都必须同时查询 GET 和 POST。无论如何,这些都是两个独立的 HTTP 请求,原则上(就您的代码而言)彼此无关。但是您只需要对每个请求进行 一次查询
  • @dirkgroten 是的,但我不想在 POST 请求中查询问题表,仅解决表。而且我不想像 Mikeh 建议的那样将解决方案传递给页面。

标签: django django-forms


【解决方案1】:

FormView 正在处理两个单独的请求:第一个是 GET 请求,当学生获取带有问题要回答的表单时。然后是学生提交问题答案时的 POST 请求。

现在 HTTP 是无状态的,因此您需要以某种方式跟踪第一个请求中提出的问题,以便知道在接收 POST 请求时回答了哪个问题。

我想说的最简单的方法是在表单本身中实际包含question_id,作为隐藏的输入字段。这里没有真正的安全问题:question_id 可以被学生操纵,即使它是隐藏的,但有什么意义呢?

所以我会这样做:

  • problem 添加为ModelChoiceFieldHiddenInput 小部件到您的ProblemForm
    problem = forms.ModelChoiceField(queryset=Problem.objects.all(), widget=forms.HiddenInput())
    
  • IndexViewget_inital() 方法中为problem 设置一个初始值:

    def get_problem(self):  # use also in get_context_data() to add the question
        if hasattr(self, 'problem'):
            return self.problem
        if self.request.user.is_authenticated:
            inner_qs = "fetch ids that are solved from Solve model"
            self.problem = Problem.objects\
                            .exclude(id__in=inner_qs)\
                            .order_by('id').first()
            return self.problem
    
    def get_initial(self):
        initial = super().get_initial()
        initial['problem'] = self.get_problem()}
        return initial
    
  • 当表单被提交并且有效时,您会看到form.cleaned_data['problem'] 是提交的问题。所以你可以在form_valid() 方法中使用它:
    def form_valid(self, form):
        problem = form.cleaned_data['problem']
        # check that it hasn't been solved by the user already
        if problem.answer == form.cleaned_data['solution']:
            # create solve object for the user
        return redirect(...)
    

替代方法是不将其包含在表单中,而是重新获取form_valid 中的problem(请注意,当表单将提交的problem_id 映射到时,上述方法中也会获取problem实际的problem 实例来填充其cleaned_data)。

【讨论】:

  • 很酷,在关注了这篇文章之后,我能够实施我的解决方案。对于第 1 点,我在问题表单中添加了这个:problem = forms.ModelChoiceField(queryset=Problem.objects.all(), widget=forms.HiddenInput())
猜你喜欢
  • 2010-11-16
  • 2020-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-03
相关资源
最近更新 更多