出于简化交互的考虑,我们经常见到很多网站把登录页面和注册界面放在同一个页面上,而当我们使用Flask来实现时,却发现问题重重:
- 不管是哪个表单按下了提交按钮,总是提交第一个表单的数据;
- 当一个表单数据验证出错时,两个表单都出现了错误提示;
问题的解决
简单来说,问题的主要原因是Flask-WTF的form1.validate_on_submit()并不验证是哪个表单的submit按钮被按下了,只是通过HTTP方法是否是“PUT”或“POST”来判断。
同时form1.submit1.data的data函数会迭代字段的名字(submit)和数据(True/False)作为一个字典,这会产生键重复的问题,所以确保两个表单的submit字段的名字不同。
另外,更改if语句判断条件的顺序(只对按下按钮的表单执行.validate_on_submit()),避免一个表单出错时两个表单同时出现错误提示信息。具体见下面两个代码块。
为你的不同表单里的SubmitField定义不同的名字,像这样:
1 2 3 4 5 6 7 8 9 |
class Form1(Form): name = StringField('name') submit1 = SubmitField('submit') class Form2(Form): name = StringField('name') submit2 = SubmitField('submit') .... |
在 view.py 里添加if判断:
1 2 3 4 5 6 7 8 9 |
.... 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():
1 2 3 4 5 6 |
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():
1 2 3 4 5 6 |
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:
1 2 3 |
@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返回一个像这样的字典:
1 |
temp = {'submit': True} |
毫无疑问,当我们调用 if form1.submit.data时,它会返回True。
而当我们点击第二个表单上的提交按钮(也是submit)时,if form1.submit.data先生成了一个键值对,然后form2.submit.data生成了键值对,结果这个字典就变成了这样:
1 |
temp = {'submit': False, 'submit': True} |
这就是为什么我们不论点击那个表单上的按钮,对第一个表单的验证总是返回True。我们通过为两个表单的字段设定不同的名字解决了这个问题。
感谢你读到这里,这样看来,你有很强的好奇心和耐心,而且对编程怀有很大的热情,我说的没错吧:p
首发于Stack Overflow:python – flask-bootstrap with two forms in one page
更多关于Flask的优质内容,欢迎关注Hello, Flask! – 知乎专栏。
你好,按照你的方法我设置了多个submit都有唯一name属性,但是没效果
我尝试着print from.submit.date 这个值一直为false,而且 打印request.form发现其中不包含submit相关的值
所以不论是一个页面多个表单,还是一个表单多个submit我都实现不了,这个是什么原因啊
你可以把相关的代码贴出来我看下