单个页面多个表单

除了在单个表单上实现多个提交按钮,有时还需要在单个页面上创建多个表单。比如,在程序的主页上同时添加登录和注册表单。当在同一个页面上添加多个表单时,我们需要解决的问题是在视图函数中判断当前被提交的是哪个表单。

单视图处理

创建两个表单,并在模板中分别渲染比较容易,但当提交某个表单是,就会遇到问题,Flask-WTF根据请求方法判断表单是否提交,但并不是判断是哪个表单被提交,所以我们需要手动判断,我们知道被单击的提交字段最终的data属性值是布尔值,即True或False。而解析后的表单数据使用input字段的name属性值作为键匹配字段数据,就是说,如果两个表单的提交字段名称都是submit,那么无法判断是哪个表单的提交字段被单击。

解决问题的第一步就是为两个表单的提交字段设置不同的名称,示例程序中的两个表单如下所示:

forms.py:为两个表单设置不同的提交字段名称

  1. from wtforms.validators import Email
  2.  
  3. class SigninForm(FlaskForm):
  4. username = StringField('Username',validators=[DataRequired(),Length(1,20)])
  5. password = PasswordField('Password', validators=[DataRequired(),Length(8,128)])
  6. submit1 = SubmitField('Sign in')
  7.  
  8. class RegisterForm(FlaskForm):
  9. username = StringField('Username', validators=[DataRequired(), Length(1,20)])
  10. email = StringField('Email', validators=[DataRequired(), Email(), Length(1,254)])
  11. password = PasswordField('Password', validators=[DataRequired(), Length(8,128)])
  12. submit2 = SubmitField('Register')

在视图函数中,我们分别实例化这两个表单,根据提交字段的值来区分被提交的表单,如下所示:

app.py:

  1. from forms import SigninForm, RegisterForm
  2.  
  3. @app.route('/multi-form', methods=['GET', 'POST'])
  4. def multi_form():
  5. signin_form = SigninForm()
  6. register_form = RegisterForm()
  7.  
  8. #validate()逐个对字段调用字段实例化时定义的验证器,返回表示验证结果的布尔值
  9. if signin_form.submit1.data and signin_form.validate():
  10. username = signin_form.username.data
  11. flash('%s, you just submit the Signin Form.' % username)
  12. return redirect(url_for('index'))
  13.  
  14. if register_form.submit2.data and register_form.validate():
  15. username = register_form.username.data
  16. flash('%s, you just submit the Register Form.' % username)
  17. return redirect(url_for('index'))
  18.  
  19. return render_template('2form.html', signin_form = signin_form,register_form = register_form)

在视图函数中,我们为两个表单添加了各自的if判断,在if语句的内如,分别执行各自的逻辑,以Signinform的if判断为例,如果signin_form.submit1.data的值是True,那么说明用户提交了登录表单,这时手动调用signin_form.validate()对表单进行验证。

这两个表单类实例通过不同的变量名称传入模板,以便在模板中渲染对应的表单字段,如下所示:

2form.html:

  1. {% extends 'base.html' %}
  2. {% from 'macros.html' import form_field %}
  3.  
  4. {% block content %}
  5. <h1>Multiple Form in One Page with One View</h1>
  6.  
  7. <h3>Login Form</h3>
  8. <form method = 'post'>
  9. {{ signin_form.csrf_token }}
  10. {{ form_field(signin_form.username) }}
  11. {{ form_field(signin_form.password) }}
  12. {{ signin_form.submit1 }}
  13. </form>
  14. <h3>Register Form</h3>
  15. <form method="post">
  16. {{ register_form.csrf_token }}
  17. {{ form_field(register_form.username) }}
  18. {{ form_field(register_form.email) }}
  19. {{ form_field(register_form.password) }}
  20. {{ register_form.submit2 }}
  21.  
  22. </form>
  23. {% endblock %}

访问127.0.0.1:5000/multi-form页面,提交某个表单后,会在重定向后的页面的提示消息中看到提交表单的名称。

多视图处理

除了通过提交按钮判断,更简洁的方法是通过分离表单的渲染和验证实现。这时表单的提交字段可以使用同一个名称,在视图函数中处理表单时也只需要使用我们熟悉的form.validate_on_submit()方法。

我们在同一个视图函数内处理两类公国:渲染包含表单的模板(GET请求)、处理表单请求(POST请求)。如果想解耦这部分功能,也可以分离成两个视图函数处理。当处理多个表单时,我们可以把表单的渲染在单独的视图函数中处理,如下所示:

  1. @rouge('/multi-form-multi-view')
  2. def multi_form_multi_view():
  3. signin_form = SigninForm()
  4. register_form = RegisterForm()
  5. return render_template('2form2view.html', signin_form=signin_form, register_form=register_form)

这个视图只负责Get请求,实例化两个表单类并渲染模板。另外再为每一个表单单独创建一个视图函数来处理验证工作。处理表单提交请求的视图仅监听POST请求。如下所示:

app.py:使用单独的视图函数处理表单提交的POST请求

  1. @app.route('/handle-signin', methods=['POST']) # 仅传入POST到methods中
  2. def handle_signin():
  3. signin_form = SigninForm()
  4. register_form = RegisterForm()
  5.  
  6. if signin_form.validate_on_submit():
  7. username = signin_form.username.data
  8. flash('%s , yoou just submit the Signin Form.' % username)
  9. return redirect(url_for('index'))
  10.  
  11. return render_template('2form2view.html', signin_form = signin_form, register_form = register_form)
  12.  
  13. @app.route('/handle-register', methods=['POST'])
  14. def handle_register():
  15. signin_form = SigninForm()
  16. register_form = RegisterForm()
  17.  
  18. if register_form.validate_on_submit():
  19. username = register_form.username.data
  20. flash('%s, you just submit the Register Form.' % username)
  21. return redirect(url_for('index'))
  22. return render_template('2form2view.html', signin_form = signin_form, register_form = register_form)

在模板中,表单提交请求的目标URL通过action属性设置,为了让表单提交时将请求发送到对应的URL,我们需要设置action属性,如下所示:

2form2view.html:

  1. {% extends 'base.html' %}
  2. {% from 'macros.html' import form_field %}
  3.  
  4. {% block content %}
  5. <h2>Multiple Form in One Page with Multiple View</h2>
  6.  
  7. <h3>Login Form</h3>
  8. <form meghod="post" action="{{ url_for('handle_signin') }}">
  9. {{ signin_form.csrf_token }}
  10. {{ form_field(signin_form.username) }}
  11. {{ form_field(signin_form.password) }}
  12. {{ signin_form.submit }}
  13. </form>
  14.  
  15. <h3>Register Form</h3>
  16. <form method="post" action="{{ url_for('handle_register') }}">
  17. {{ register_form.csrf_token }}
  18. {{ form_field(register_form.username) }}
  19. {{ form_field(register_form.email) }}
  20. {{ form_field(register_form.password) }}
  21. {{ register_form.submit }}
  22. </form>
  23. {% endblock %}

例中index视图:

  1. @app.route('/index')
  2. def index():
  3. return render_template('index.html')
  4.  
  5. index.html:
  6.  
  7. {% extends 'base.html' %}
  8.  
  9. {% block content %}
  10. <h1>Forms</h1>
  11. <ul>
  12. <li><a href="{{ url_for('basic') }}">Basic Form</a></li>
  13. <li><a href="{{ url_for('upload') }}">File Upload</a></li>
  14. <li><a href="{{ url_for('multi_upload') }}">Multiple Files Upload</a></li>
  15. <li><a href="{{ url_for('two_submits') }}">Multiple Submit Buttons</a></li>
  16. <li><a href="{{ url_for('multi_form') }}">Multiple Form</a></li>
  17. <li><a href="{{ url_for('multi_form_multi_view') }}">Multiple form Multiple view</a></li>
  18. </ul>
  19. {% endblock %}

访问127.0.0.1:5000/2form2view.html,填入必填内容,点击提交,在重定向后的页面上提示提交表单的名称

虽然现在可以正常工作,但是有一个缺点,如果验证未通过,需要将错误消息的form.errors字典传入模板中。如:

  1. {% for message in register_form.username.errors %}
  2. <small class="error">{{ message }}</small><br>
处理错误处理

在处理表单的视图中传入表单错误信息,就意味着需要再次渲染模板,但是如果视图函数中还涉及大量要传入模板的变量操作,那么这种方式会带来大量的重复。

对于这个问题,一般的解决方式是通过其他方式传递错误消息,然后统一重定向到渲染表单页面的视图。比如使用flash()函数迭代form.errors字典发送错误消息的函数:

  1. def flash_errors(form):
  2. for field, errors in form.errors.items():
  3. for error in errors:
  4. flash(u"Error in the %s field - %s" % (getattr(form, field).label.text, error))
  1.  

如果你希望像往常一样在表单字段下渲染错误消息,可以直接将错误消息字段form.errors存储到session中,然后重定向到用来渲染表单的multi_form_multi_view视图。在模板中渲染表单字段错误时添加一个额外的判断,从session获取并遍历错误消息。

在本例中,错误是在宏里面渲染的:

  1. {% macro form_field(field) %}
  2. {{ field.label }}<br>
  3. {{ field(**kwargs) }}<br>
  4. {% if field.errors %}
  5. {% for error in field.errors %}
  6. <small class="error">{{ error }}</small><br>
  7. {% endfor %}
  8. {% endif %}
  9. {% endmacro %}

flask 单个页面多个表单(单视图处理、多视图处理)的更多相关文章

  1. flask 单个表单多个提交按钮

    单个表单多个提交按钮 在某些情况下,可能需要为一个表单添加多个提交按钮.比如在创建文章的表单中添加发布按钮和存草稿的按钮.当用户提交表单时,需要在视图函数中根据按下的按钮来做出不同的处理. 下面例子中 ...

  2. flask 在模板中渲染表单

    在模板中渲染表单 为了能够在模板中渲染表单,我们需要把表单类实例传入模板.首先在视图函数里实例化表单类LoginForm,然后再render_template()函数中使用关键脑子参数form将表单实 ...

  3. Flask开发系列之Web表单

    Flask开发系列之Web表单 简单示例 from flask import Flask, request, render_template app = Flask(__name__) @app.ro ...

  4. asp.net使用post方式action到另一个页面,在另一个页面接受form表单的值!(报错,已解决!)

    原文:asp.net使用post方式action到另一个页面,在另一个页面接受form表单的值!(报错,已解决!) 我想用post的方式把一个页面表单的值,传到另一个页面.当我点击Default.as ...

  5. Flask - WTF和WTForms创建表单

    目录 Flask - WTF和WTForms创建表单 一. Flask-WTF 1.创建基础表单 2.CSRF保护 3.验证表单 4.文件上传 5.验证码 二. WTForms 1. field字段 ...

  6. 线性表之单链表C++实现

    线性表之单链表 一.头文件:LinkedList.h //单链表是用一组任意的存储单元存放线性表的元素,这组单元可以是连续的也可以是不连续的,甚至可以是零散分布在内存中的任意位置. //单链表头文件 ...

  7. ActiveRecord-连接多张表之单表继承

    ActiveRecord-连接多张表之单表继承 1. 基本概念 Rails提供了两种机制,可以将复杂的面向对象模型映射为关系模型,即所谓的单表继承(single-table inheritance)和 ...

  8. 【线性表基础】顺序表和单链表的插入、删除等基本操作【Java版】

    本文表述了线性表及其基本操作的代码[Java实现] 参考书籍 :<数据结构 --Java语言描述>/刘小晶 ,杜选主编 线性表需要的基本功能有:动态地增长或收缩:对线性表的任何数据元素进行 ...

  9. [数据结构 - 第3章] 线性表之单链表(C++实现)

    一.类定义 单链表类的定义如下: #ifndef SIGNALLIST_H #define SIGNALLIST_H typedef int ElemType; /* "ElemType类型 ...

随机推荐

  1. kafka6 编写使用自定义分区的生产者

    一 客户端 在上一篇博客创建的简单生产者的基础上,进行两个修改操作: 1.新建SimplePartitioner.java,修改返回分区为1. SimplePartitioner.java代码如下 p ...

  2. Element-diag中遮罩

    <el-dialog title="收货地址" :visible.sync="dialogFormVisible" append-to-body> ...

  3. vue 后退不刷新页面

    使用 this.$router.push({path: '/aichat'})路由跳转方式跳转页面 要实现 home => chat  chat页面刷新: chat => report, ...

  4. protocol error, got 'n' as reply type byte + redis如何后台启动

    其它机子的PHP访问redis爆“protocol error, got 'n' as reply type byte ”错误 解决办法: 在redis配置文件redis.conf中注释掉bind配置 ...

  5. EOS 帐户权限操作--你找不到的干货 (原创) 续集-EOS 3.0

    https://eosfans.io/topics/372 关于2.0权限问题请移步https://eosfans.io/topics/28 目录 查看权限 改变权限 增加权限 删除权限 查看权限 有 ...

  6. Swagger Editor Linux安装(全新环境)

    查看内核版本 cat /proc/version cat /etc/redhat-release 查看系统是32位还是64位方法总结getconf LONG_BIT 安装相关工具 yum instal ...

  7. Ajax 传包含集合的JSON

    通过ajax给后台传json对象,当json中含对象集合时,如 $.ajax({ url : , type : "POST", dataType : "json" ...

  8. PHP "松散比较"

    PHP 的整数和字符串比较是 "松散比较" var_dump('dev' == 0); bool(true) switch switch 在进行比较的时候,只是对值进行比较(&qu ...

  9. iOS UI进阶-4.0 地图与定位

    在移动互联网时代,移动app能解决用户的很多生活琐事,比如 导航:去任意陌生的地方 周边:找餐馆.找酒店.找银行.找电影院   在上述应用中,都用到了地图和定位功能,在iOS开发中,要想加入这2大功能 ...

  10. 使用socat查看ios日志

    仅供记录自己查看 mac连接ios后进程ios shell apt-get socat安装 socat socat - UNIX-CONNECT:/var/run/lockdown/syslog.so ...