出于简化交互的考虑,我们经常见到很多网站把登录页面和注册界面放在同一个页面上,而当我们使用Flask来实现时,却发现问题重重:
- 不管是哪个表单按下了提交按钮,总是提交第一个表单的数据;
- 当一个表单数据验证出错时,两个表单都出现了错误提示;
问题的解决
简单来说,问题的主要原因是Flask-WTF的form1.validate_on_submit()并不验证是哪个表单的submit按钮被按下了,只是通过HTTP方法是否是“PUT”或“POST”来判断。
同时form1.submit1.data的data函数会迭代字段的名字(submit)和数据(True/False)作为一个字典,这会产生键重复的问题,所以确保两个表单的submit字段的名字不同。
另外,更改if语句判断条件的顺序(只对按下按钮的表单执行.validate_on_submit()),避免一个表单出错时两个表单同时出现错误提示信息。具体见下面两个代码块。
为你的不同表单里的SubmitField定义不同的名字,像这样:
class Form1(Form):
name = StringField('name')
submit1 = SubmitField('submit')
class Form2(Form):
name = StringField('name')
submit2 = SubmitField('submit')
....
在 view.py 里添加if判断:
....
form1 = Form1()
form2 = Form2()
....
if form1.submit1.data and form1.validate_on_submit(): # 注意顺序
....
if form2.submit2.data and form2.validate_on_submit(): # 注意顺序
....
现在问题解决了,如果你想了解更多一点,那就继续读下去。
探究问题的本质
这是 validate_on_submit():
def validate_on_submit(self):
"""
Checks if form has been submitted and if so runs validate. This is
a shortcut, equivalent to ``form.is_submitted() and form.validate()``
"""
return self.is_submitted() and self.validate()
这是 is_submitted():
def is_submitted(self):
"""
Checks if form has been submitted. The default case is if the HTTP
method is **PUT** or **POST**.
"""
return request and request.method in ("PUT", "POST")
当你对表单调用 form.validate_on_submit() 的时候,它只是通过HTTP方法来检查提交的表单,而不是哪个表单上的按钮被按下了。所以上面的小技巧只是添加一个过滤器(也就是检查按钮是不是有数据,即 form1.submit.data)。
另外,我们改变了if判断的顺序。这样当我们点击其中一个表单上的按钮时,它只对这个表单调用验证函数validate(),所以不会两个表单都出现错误提示。
故事还没完,这是静态方法.data:
@property
def data(self):
return dict((name, f.data) for name, f in iteritems(self._fields))
它返回一个以字段名(field name)和字段值(field value)作为键值对的字典,但要注意的是,两个表单的提交按钮的字段名都是submit!
这带了什么问题呢?想一想Python里字典的特性就知道了,字典用键来索引一个值,所以会有键重复的问题。
当我们点击第一个表单上的提交按钮(submit)时,form1.submit1.data返回一个像这样的字典:
temp = {'submit': True}
毫无疑问,当我们调用 if form1.submit.data时,它会返回True。
而当我们点击第二个表单上的提交按钮(也是submit)时,if form1.submit.data先生成了一个键值对,然后form2.submit.data生成了键值对,结果这个字典就变成了这样:
temp = {'submit': False, 'submit': True}
这就是为什么我们不论点击那个表单上的按钮,对第一个表单的验证总是返回True。我们通过为两个表单的字段设定不同的名字解决了这个问题。
感谢你读到这里,这样看来,你有很强的好奇心和耐心,而且对编程怀有很大的热情,我说的没错吧:p
首发于Stack Overflow:python – flask-bootstrap with two forms in one page
更多关于Flask的优质内容,欢迎关注Hello, Flask! – 知乎专栏。
最后部分关于两个表单相同的submit的解释,个人觉得这样说明会不会好点。
1、同个html文件里,两个form表单,各自都包含
2、当点击提交的时候,都是将表单发送至服务端
3、服务端接收到后,在你实例化表单的时候,也就是
form1 = Form1(request.form)
form2 = Form2(request.form)
此时的request.form[‘submit’] 都是True,所以两个form.submit.data 都是True。
关于辉哥说的
@property
def data(self):
return dict((name, f.data) for name, f in iteritems(self._fields))
我有疑惑,此处的self._fields,其实是这个表单的fields,是不会出现submit重复的吧?
代码上来说也就是
假设Form1包含 username、password、submit字段,那么form1的_fields属性也只会包含 username、password、submit.
你好,按照你的方法我设置了多个submit都有唯一name属性,但是没效果
我尝试着print from.submit.date 这个值一直为false,而且 打印request.form发现其中不包含submit相关的值
所以不论是一个页面多个表单,还是一个表单多个submit我都实现不了,这个是什么原因啊
你可以把相关的代码贴出来我看下