Flask问题集:单个页面两个(多个)表单

出于简化交互的考虑,我们经常见到很多网站把登录页面和注册界面放在同一个页面上,而当我们使用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! – 知乎专栏

Flask问题集:单个页面两个(多个)表单》上有3条评论

  1. 头像Qiu

    最后部分关于两个表单相同的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.

    回复
  2. 头像赫伟

    你好,按照你的方法我设置了多个submit都有唯一name属性,但是没效果
    我尝试着print from.submit.date 这个值一直为false,而且 打印request.form发现其中不包含submit相关的值
    所以不论是一个页面多个表单,还是一个表单多个submit我都实现不了,这个是什么原因啊

    回复

撰写评论

电子邮件地址不会被公开,必填项已用 * 标出。