【问题标题】:Multiple forms in a single page using flask and WTForms使用flask和WTForms在一个页面中的多个表单
【发布时间】:2013-08-19 20:28:52
【问题描述】:

我在同一页面上有多个表单将发布请求发送到同一处理程序 在烧瓶中。

我正在使用 wtforms 生成表单。

识别提交的表单的最佳方法是什么?

我目前正在使用action="?form=oneform"。我认为应该有一些更好的方法 达到同样的效果?

【问题讨论】:

    标签: forms flask flask-wtforms


    【解决方案1】:

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

    然后,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 的期望行为。谢谢!
    【解决方案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() 并确保 SubmitFields 具有不同的名称。
            【解决方案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' 是
              标签的name 属性的值?像这样:&lt;form name='Login'&gt; 对应于SubmitField('Login')
            【解决方案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
            

            【讨论】:

            猜你喜欢
            • 2021-03-05
            • 1970-01-01
            • 1970-01-01
            • 2014-03-23
            • 2021-09-28
            • 1970-01-01
            • 2016-02-19
            相关资源
            最近更新 更多