使用flask和WTForms在单个页面中包含多种表单


73

我在同一页面上有多种形式,可以将发信请求发送到烧瓶中的同一处理程序。

我正在使用wtforms生成表单。

确定提交哪种表格的最佳方法是什么?

我目前正在使用action="?form=oneform"。我认为应该有一些更好的方法来实现相同目标?

Answers:


86

上面的解决方案有一个验证错误,当一种形式导致验证错误时,两种形式都显示一条错误消息。我更改了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中)时,对.datain的调用首先if form1.submit.data:在dict中添加键值,然后从中调用添加另一个键值,最后,该dict将如下所示:if form2.submit.data:

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>

3
恕我直言,更新是最好的答案。(但methods=['POST']似乎丢失了@app.route
VPfB

1
我使用了您的第一种方法,但是form1.submit1.data和forn2.submit2.data行始终返回false。怎么处理呢?
Sivaramakrishnan

@Sivaramakrishnan也许您可以创建一个新问题,包括相关代码,然后在此处发布链接。
灰色李

其次,UPDATE是最佳答案。而且,如果有人想知道,您实际上可以在route('/')下面拥有所有功能-只要您记得在表单操作中添加大括号部分即可。您不需要单独的登录和注册路线,即使在注册/登录页面时这是一个不错的功能。但是,有时候您想在同一页面上放置大量不同的表单,而又不想打扰大量的路线。
user3661992 '20

60

我一直在使用两个烧瓶片段的组合。首先将前缀添加到表单,然后使用validate_on_submit()检查前缀。我还使用LouisRoché的模板来确定在表单中按下了哪些按钮

引用丹·雅各布:

例:

form1 = FormA(prefix="form1")
form2 = FormB(prefix="form2")
form3 = FormC(prefix="form3")

然后,添加一个隐藏字段(或仅检查提交字段):

if form1.validate_on_submit() and form1.submit.data:

引用LouisRoché的话:

我的模板中有:

<input type="submit" name="btn" value="Save">
<input type="submit" name="btn" value="Cancel">

为了找出服务器端通过了哪个按钮,我在views.py文件中有:

if request.form['btn'] == 'Save':
    something0
else:
    something1

注意顺序,这里说stackoverflow.com/a/39739863/564979
anvd

提供的链接已过时。
colidyre

20

一种简单的方法是为不同的提交字段使用不同的名称。例如:

表格.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:
Ash

@Hieu谢谢。因此,SubmitField('textHere')指定'textHere'是<form>标记的name属性的值吗?像这样:<form name='Login'>对应于SubmitField('Login')
Nikos,

7

作为其他答案,我还为页面上的每个表单的每个提交按钮分配了唯一的名称。

然后,flask Web操作如下所示-注意formdataobj参数,它们有助于相应地初始化/保留表单字段:

@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。谢谢!
GrzegorzPudłowski20年

4

好吧,这是一个简单的把戏

假设你有

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"

2

我没有使用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)

1

示例:单个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>

1

我通常使用隐藏标签作为标识符。

这是一个例子:

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语句中这样做,它将检查标识符是什么,如果相等,它将运行代码中包含的表单内容。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.