【问题标题】:How to pass hidden variable to a form in django so it could be validated?如何将隐藏变量传递给 django 中的表单以便对其进行验证?
【发布时间】:2018-02-05 05:11:54
【问题描述】:

我有一个注册表单,它包含所有常见的东西,但有一个机器人预防功能。我制作了一个模型 SecurityQuestion,它包含两个字符域,问题和答案。在注册期间,其中一个是随机挑选的,应该将答案传递给表格,以便在那里进行验证。但是,由于我尚未弄清楚的原因,它似乎没有将答案传递给表单

让我们从代码开始

profile/forms.py

# FORM: Register an account
class UserRegistrationFirstForm(forms.ModelForm):

    username = forms.CharField(max_length=20)
    password = forms.CharField(widget=forms.PasswordInput)
    password_confirm = forms.CharField(widget=forms.PasswordInput)
    email = forms.EmailField()
    email_confirm = forms.EmailField()

    answer = forms.CharField(max_length=50, required=True)
    hidden_answer = forms.CharField(widget=forms.HiddenInput)

    class Meta:
        model = User
        fields = [
            'username',
            'email',
            'email_confirm',
            'password',
            'password_confirm',
            'answer',
        ]

    def clean_answer(self):

        formated_user_answer = self.cleaned_data.get("answer").lower()
        formated_hidden_answer = self.cleaned_data.get("hidden_answer").lower()

        if formated_hidden_answer != formated_user_answer:
            raise forms.ValidationError("Incorect answer to security question!")

        return answer

如您所见,有两个字段,answerhidden_answeranswer 是用户输入答案的地方,hidden_answer 应该在初始化表单和传递 init 时填充。

profiles/views.py

# VIEW: Register an account
def custom_register(request):

    if request.user.is_authenticated():
        return redirect(reverse('profile', host='profiles'))

    # Grab a random registration security Q&A
    qa_count = SecurityQuestion.objects.count() - 1
    sec_qa = SecurityQuestion.objects.all()[randint(0, qa_count)]


    # Form for users to register an account
    form = UserRegistrationForm(request.POST or None, initial={"hidden_answer": sec_qa.answer,})

    # Validate the registration form
    if form.is_valid():

        # Create new account
        user = form.save(commit=False)
        password = form.cleaned_data.get("password")
        user.set_password(password)
        user.save()

        # Set the default avatar
        user.profile.avatar = get_default_avatar()
        user.profile.save()

        login(request, user)
        messages.success(request, "Welcome " + user.username + ", you have successfully registered an account!")
        return redirect(reverse('pages:frontpage', host='www'))

    # Context dict to return for template
    context = {
        "title": "Registration",
        "form": form,
        "question": sec_qa.question,
    }
    return render(request, 'profiles/register.html', context)

好的,所以在注册视图中,我随机选择一个安全问题,然后将其传递给带有initial={"hidden_answer": sec_qa.answer,} 的表单。但是它似乎没有通过,因为我收到以下错误:

'NoneType' object has no attribute 'lower'
Exception Location: path/to/profiles/forms.py in clean_answer, line 103
formated_hidden_answer = self.cleaned_data.get("hidden_answer").lower()

好的,所以 NoneType 意味着没有什么可参考的。我尝试了几种不同的方法来解决这个问题。我尝试将 hidden_​​answer 放入表单的元字段列表中。我还在模板中尝试了{{ form.hidden_answer.as_hidden }}(这与我在这里尝试实现的完全相反,因为答案仍然显示在页面源中隐藏输入的值中)。知道我做错了什么吗?

编辑:如果我正在尝试做的事情有替代或简单的解决方案,您能否参考任何有关它的文档?

【问题讨论】:

    标签: python django forms validation


    【解决方案1】:

    发送隐藏输入不能阻止用户知道 hidden_​​answer。它不会在浏览器中显示 ,但会很好地显示在您的 DOM 中,并且任何用户都可以访问。 将答案(隐藏或不隐藏)发送到客户端本身就是 缺陷安全性。

    您应该只将问题发送到客户端(浏览器),然后在您的 clean() 方法中进行验证。

    如果我理解你的用例正确(如果我错了,请纠正我),你应该这样做:

    在您的 views.py 中,执行以下操作:

    def custom_register(request):
    
    if request.user.is_authenticated():
        return redirect(reverse('profile', host='profiles'))
    
    if request.method == 'GET':   
    
        # Grab a random registration security Q&A
        qa_count = SecurityQuestion.objects.count() - 1
        sec_qa = SecurityQuestion.objects.all()[randint(0, qa_count)]
        #Give the text of your question to sec_qa_title. Do something like the following.
        #sec_qa_title = sec_qa.title        
        #sec_qa_title should now have the question string of the SecurityQuestion model object instance. 
    
        form = UserRegistrationForm(initial={'question' : sec_qa_title})
    
        #initialize whatever context variables you want.
        #Rest of your code.
        #return a suitable response which will display you form with the security question.
    
    
        #return render(request, 'profiles/register.html', context)
    
    if request.method == 'POST':
        #All the data of form submitted by the user is inside request.POST
        form = UserRegistrationForm(request.POST)
    
        # Validate the registration form
        if form.is_valid():
            #Do your stuff. Return a suitable response.
    
        else:
            #Do your stuff. Return a suitable response.
    

    现在在您的 forms.py 中,执行以下操作:

    class UserRegistrationFirstForm(forms.ModelForm):
    
        username = forms.CharField(max_length=20)
        password = forms.CharField(widget=forms.PasswordInput)
        password_confirm = forms.CharField(widget=forms.PasswordInput)
        email = forms.EmailField()
        email_confirm = forms.EmailField()
    
        question = forms.CharField(max_length=50, required=True)
        #removed hidden_answer field and added a question field.
        answer = forms.CharField(max_length=50, required=True)
    
        class Meta:
            model = User
            fields = [
                'username',
                'email',
                'email_confirm',
                'password',
                'password_confirm',
                #Remove the answer field.
            ]
    
        def clean_answer(self):
            security_question_title = self.cleaned_data.get("question")
            #get the question title.
            formatted_user_answer = self.cleaned_data.get("answer").lower()
    
            #now get the SecurityQuestion model.
            try:
                sec_qa = SecurityQuestion.objects.get(title = security_question_title)
                #Don't forget to import SecurityQuestion model.
            except SecurityQuestion.DoesNotExist:
                #If a user changes the question, you don't want it to fiddle with you system.
                raise forms.ValidationError("Question was changed. Wrong practice.")
    
            #Finally check the answer.
            if formatted_user_answer != sec_qa.answer.lower():
                raise forms.ValidationError("Incorrect answer to security question!")
    
            return answer
    

    您可以稍后尝试许多改进。

    例如:发送一个问题和一个 id 以便稍后通过该 id 提取问题(而不是从整个字符串中提取它;稍微不可靠)

    希望您理解流程并正确构建它。

    由于我没有测试代码,可能会有一些错误,但我希望你能修复它们。

    我希望这能以某种方式指导您。谢谢。

    【讨论】:

    • 非常感谢。稍微调整一下,它就像一个魅力。我切换到发送 id 而不是问题字符串。我还有一个问题。对于 try/except 部分,用户究竟会如何更改问题?我只是好奇,这并不重要,因为该措施只是为了防止垃圾邮件程序注册帐户。如果这些相当独特的问题被机器人以某种方式绕过,则必须在表单中添加 recaptcha。
    • 要理解我为什么使用 try/except(这通常是一个很好的做法),您应该首先知道 request.POST 是一个简单的数据字典,由客户端发送到您的服务器(通过浏览器或其他东西)。现在任何客户端都可以发送此字典中的任何数据。可疑的客户可能会通过脚本发送它。现在想想。如果在这本词典中,客户更改了问题文本怎么办?确实很简单。您可能已经知道,如果数据库中不存在条目,则 Model.objects.get() 会引发错误。
    • 因此,如果您认为 request.POST 看起来像:{'question' : 'my_question_text' , 'other_fields' : 'other_field_strings' , ... },用户可以很好地发送如下内容: {'问题':'manipulated_text_will_throw_an_error','other_fields':'other_field_strings',...}。现在,如果您不输入 try/catch,则会引发错误。因此,您使用 try/catch。您不希望用户发送一些随机字符串来摆弄您的数据库。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-03
    • 1970-01-01
    • 2016-08-27
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    相关资源
    最近更新 更多