使用flask和WTForms在一个页面中的多个表单
Posted
技术标签:
【中文标题】使用flask和WTForms在一个页面中的多个表单【英文标题】:Multiple forms in a single page using flask and WTForms 【发布时间】:2013-08-19 20:28:52 【问题描述】:我在同一页面上有多个表单将发布请求发送到同一处理程序 在烧瓶中。
我正在使用 wtforms 生成表单。
识别提交的表单的最佳方法是什么?
我目前正在使用action="?form=oneform"
。我认为应该有一些更好的方法
达到同样的效果?
【问题讨论】:
【参考方案1】:作为其他答案,我还为页面上的每个表单的每个提交按钮分配了一个唯一的名称。
然后,flask web 操作如下所示 - 请注意 formdata
和 obj
参数,这有助于相应地初始化/保存表单字段:
@bp.route('/do-stuff', methods=['GET', 'POST'])
def do_stuff():
result = None
form_1 = None
form_2 = None
form_3 = None
if "submit_1" in request.form:
form_1 = Form1()
result = do_1(form_1)
elif "submit_2" in request.form:
form_2 = Form2()
result = do_2(form_2)
elif "submit_3" in request.form:
form_3 = Form3()
result = do_3(form_3)
if result is not None:
return result
# Pre-populate not submitted forms with default data.
# For the submitted form, leave the fields as they were.
if form_1 is None:
form_1 = Form1(formdata=None, obj=...)
if form_2 is None:
form_2 = Form2(formdata=None, obj=...)
if form_3 is None:
form_3 = Form3(formdata=None, obj=...)
return render_template("page.html", f1=form_1, f2=form_2, f3=form_3)
def do_1(form):
if form.validate_on_submit():
flash("Success 1")
return redirect(url_for(".do-stuff"))
def do_2(form):
if form.validate_on_submit():
flash("Success 2")
return redirect(url_for(".do-stuff"))
def do_3(form):
if form.validate_on_submit():
flash("Success 3")
return redirect(url_for(".do-stuff"))
【讨论】:
这种方法效果最好。所有其他方法在技术上也有效,但会导致在非预期的表单中引发验证错误。基本上它会导致form not "clicked" being ignored/dead/not interfering
的期望行为。谢谢!【参考方案2】:
我通常使用隐藏标签作为标识符。
这是一个例子:
class Form1(Form):
identifier = StringField()
name = StringField('name')
submit = SubmitField('submit')
class Form2(Form):
identifier = StringField()
name = StringField('name')
submit = SubmitField('submit')
然后你可以在view.py中添加一个过滤器:
....
form1 = Form1()
form2 = Form2()
....
if form1.identifier.data == 'FORM1' and form1.validate_on_submit():
....
if form2.identifier.data == 'FORM2' and form2.validate_on_submit():
....
最后在 HTML 中:
<form method="POST">
form1.indentifier(hidden=True, value='FORM1')
</form>
<form method="POST">
form2.indentifier(hidden=True, value='FORM2')
</form>
如果你在 if 语句中这样做,它将检查标识符是什么,如果它相等,它将运行你在代码中的表单内容。
【讨论】:
【参考方案3】:这里有一个简单的技巧
假设你有
Form1、Form2 和索引
Form1 <form method="post" action=" url_for('index',formid=1) ">
Form2 <form method="post" action=" url_for('index',formid=2) ">
现在在索引中
@bp.route('/index', methods=['GET', 'POST'])
def index():
formid = request.args.get('formid', 1, type=int)
if formremote.validate_on_submit() and formid== 1:
return "Form One"
if form.validate_on_submit() and formid== 2:
return "Form Two"
【讨论】:
【参考方案4】:我没有使用过 WTForms,但无论如何都应该可以使用。这是一个非常快速和简单的答案;您需要做的就是为提交按钮使用不同的值。然后,您可以根据每个执行不同的定义。
在 index.html 中:
<div>
<form action=" url_for('do_stuff')" method="POST">
<h1>Plus</h1>
<input type = "number" id = "add_num1" name = "add_num1" required><label>Number 1</label><br>
<input type = "number" id = "add_num2" name = "add_num2" required><label>Number 2</label><br>
<input type = "submit" value = "submit_add" name = "submit" ><br>
</form>
<p>Answer: add </p>
</div>
<div>
<form action=" url_for('do_stuff')" method="POST">
<h1>Minus</h1>
<input type = "number" id = "min_num1" name = "min_num1" required><label>Number 1</label><br>
<input type = "number" id = "min_num2" name = "min_num2" required><label>Number 2</label><br>
<input type = "submit" value = "submit_min" name = "submit"><br>
</form>
<p>Answer: minus </p>
</div>
在 app.py 中:
@app.route('/',methods=["POST"])
def do_stuff():
if request.method == 'POST':
add = ""
minus = ""
if request.form['submit'] == 'submit_add':
num1 = request.form['add_num1']
num2 = request.form['add_num2']
add = int(num1) + int(num2)
if request.form['submit'] == 'submit_min':
num1 = request.form['min_num1']
num2 = request.form['min_num2']
minus = int(num1) - int(num2)
return render_template('index.html', add = add, minus = minus)
【讨论】:
【参考方案5】:示例:单个 html 页面中的多个 WTForm
app.py
"""
Purpose Create multiple form on single html page.
Here we are having tow forms first is Employee_Info and CompanyDetails
"""
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, FloatField, validators
from wtforms.validators import InputRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Thisisasecret'
class EmployeeInfo(FlaskForm):
"""
EmployeeInfo class will have Name,Dept
"""
fullName = StringField('Full Name',[validators.InputRequired()])
dept = StringField('Department',[validators.InputRequired()])
class CompanyDetails(FlaskForm):
"""
CompanyDetails will have yearOfExp.
"""
yearsOfExp = IntegerField('Year of Experiece',[validators.InputRequired()])
@app.route('/', methods = ['GET','POST'] )
def index():
"""
View will render index.html page.
If form is validated then showData.html will load the employee or company data.
"""
companydetails = CompanyDetails()
employeeInfo = EmployeeInfo()
if companydetails.validate_on_submit():
return render_template('showData.html', form = companydetails)
if employeeInfo.validate_on_submit():
return render_template('showData.html', form1 = employeeInfo)
return render_template('index.html',form1 = employeeInfo, form = companydetails)
if __name__ == '__main__':
app.run(debug= True, port =8092)
templates/index.html
<html>
<head>
</head>
<body>
<h4> Company Details </h4>
<form method="POST" action="url_for('index')">
form.csrf_token
form.yearsOfExp.label form.yearsOfExp
<input type="submit" value="Submit">
</form>
<hr>
<h4> Employee Form </h4>
<form method="POST" action="url_for('index')" >
form1.csrf_token
form1.fullName.label form1.fullName
form1.dept.label form1.dept
<input type="submit" value="Submit">
</form>
</body>
</html>
showData.html
<html>
<head>
</head>
<body>
% if form1 %
<h2> Employee Details </h2>
form1.fullName.data
form1.dept.data
% endif %
% if form %
<h2> Company Details </h2>
form.yearsOfExp.data
% endif %
</body>
</html>
【讨论】:
【参考方案6】:上述解决方案有一个验证错误,当一个表单导致验证错误时,两个表单都会显示错误消息。我改变if
的顺序来解决这个问题。
首先,用不同的名称定义多个SubmitField
,如下所示:
class Form1(Form):
name = StringField('name')
submit1 = SubmitField('submit')
class Form2(Form):
name = StringField('name')
submit2 = SubmitField('submit')
....
然后在view.py
中添加一个过滤器:
....
form1 = Form1()
form2 = Form2()
....
if form1.submit1.data and form1.validate(): # notice the order
....
if form2.submit2.data and form2.validate(): # notice the order
....
现在问题解决了。
如果您想深入了解,请继续阅读。
这里是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():
"""Consider the form submitted if there is an active request and
the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
"""
return _is_submitted() # bool(request) and request.method in SUBMIT_METHODS
当您调用form.validate_on_submit()
时,无论单击哪个提交按钮,它都会检查表单是否通过HTTP 方法提交。所以上面的小技巧只是添加一个过滤器(检查提交是否有数据,即form1.submit1.data
)。
另外,我们改变了if
的顺序,所以当我们点击一个提交时,它只调用validate()
这个表单,防止了两个表单的验证错误。
故事还没有结束。这里是.data
:
@property
def data(self):
return dict((name, f.data) for name, f in iteritems(self._fields))
它返回一个带有字段名称(键)和字段数据(值)的字典,但是,我们的两个表单提交按钮具有相同的名称submit
(键)!
当我们点击第一个提交按钮(在 form1 中)时,来自form1.submit1.data
的调用会返回一个这样的字典:
temp = 'submit': True
毫无疑问,当我们调用if form1.submit.data:
时,它会返回True
。
当我们点击第二个提交按钮时(在form2中),if form1.submit.data:
中对.data
的调用在dict中添加一个键值首先,然后来自if form2.submit.data:
的调用添加另一个键值,最后,dict会是这样的:
temp = 'submit': False, 'submit': True
现在我们调用if form1.submit.data:
,它返回True
,即使我们点击的提交按钮是在form2中。
这就是为什么我们需要用不同的名字定义这两个SubmitField
。顺便说一句,感谢阅读(到这里)!
更新
还有另一种方法可以在一个页面上处理多个表单。您可以使用多个视图来处理表单。例如:
...
@app.route('/')
def index():
register_form = RegisterForm()
login_form = LoginForm()
return render_template('index.html', register_form=register_form, login_form=login_form)
@app.route('/register', methods=['POST'])
def register():
register_form = RegisterForm()
login_form = LoginForm()
if register_form.validate_on_submit():
... # handle the register form
# render the same template to pass the error message
# or pass `form.errors` with `flash()` or `session` then redirect to /
return render_template('index.html', register_form=register_form, login_form=login_form)
@app.route('/login', methods=['POST'])
def login():
register_form = RegisterForm()
login_form = LoginForm()
if login_form.validate_on_submit():
... # handle the login form
# render the same template to pass the error message
# or pass `form.errors` with `flash()` or `session` then redirect to /
return render_template('index.html', register_form=register_form, login_form=login_form)
在模板(index.html)中,你需要渲染两个表单并将action
属性设置为目标视图:
<h1>Register</h1>
<form action=" url_for('register') " method="post">
register_form.username
register_form.password
register_form.email
</form>
<h1>Login</h1>
<form action=" url_for('login') " method="post">
login_form.username
login_form.password
</form>
【讨论】:
恕我直言,更新是最好的答案。 (但methods=['POST']
似乎在@app.route
中不见了)
我使用了你的第一种方法,但是 form1.submit1.data 和 forn2.submit2.data 行总是返回 false。如何处理?
@Sivaramakrishnan 也许你可以创建一个新问题,包含相关代码,然后在此处发布链接。
第二次更新作为最佳答案。如果有人想知道,您实际上可以在 route('/') 下拥有所有功能——只要您记得在表单操作中添加大括号部分。您不需要单独的登录和注册路线,即使在注册/登录页面方面这是一个不错的功能。但是,有时您想在同一页面上放置大量不同的表单,并且不想为大量路线而烦恼。
这也解决了我没有出现验证错误消息的问题。谢谢你。对我来说最简单的解决方案是使用 if form.submit.data and form.validate()
并确保 SubmitField
s 具有不同的名称。【参考方案7】:
一种简单的方法是为不同的提交字段使用不同的名称。为 示例:
forms.py:
class Login(Form):
...
login = SubmitField('Login')
class Register(Form):
...
register = SubmitField('Register')
views.py:
@main.route('/')
def index():
login_form = Login()
register_form = Register()
if login_form.validate_on_submit() and login_form.login.data:
print "Login form is submitted"
elif register_form.validate_on_submit() and register_form.register.data:
print "Register form is submitted"
...
【讨论】:
小修正if login.validate_on_submit() and login_form.login.data:
应该是if login_form.validate_on_submit() and login_form.login.data:
@Hieu 谢谢。那么SubmitField('textHere')
指定'textHere' 是【参考方案8】:
我一直在使用两个烧瓶 sn-ps 的组合。 The first adds a prefix to a form 然后使用 validate_on_submit() 检查前缀。 I use also Louis Roché's template to determine what buttons are pushed in a form。
引用 Dan Jacob 的话:
例子:
form1 = FormA(prefix="form1")
form2 = FormB(prefix="form2")
form3 = FormC(prefix="form3")
然后,添加一个隐藏字段(或只检查一个提交字段):
if form1.validate_on_submit() and form1.submit.data:
引用 Louis Roché 的话:
我的模板中有:
<input type="submit" name="btn" value="Save">
<input type="submit" name="btn" value="Cancel">
为了弄清楚我的views.py文件中有哪个按钮通过了服务器端:
if request.form['btn'] == 'Save':
something0
else:
something1
【讨论】:
注意这里所说的顺序***.com/a/39739863/564979 提供的链接已过时。 这是 IMO 这个问题的最佳答案!以上是关于使用flask和WTForms在一个页面中的多个表单的主要内容,如果未能解决你的问题,请参考以下文章