Несколько форм на одной странице с использованием фляги и WTForms

У меня есть несколько форм на одной странице, которые отправляют запрос на публикацию тому же обработчику в колбе.

Я создаю формы с помощью wtforms.

как лучше всего определить, какая форма отправлена?

В настоящее время я использую action="?form=oneform". Я думаю, должен быть какой-то лучший способ добиться того же?


person iamgopal    schedule 17.08.2013    source источник


Ответы (8)


В приведенном выше решении есть ошибка проверки: когда одна форма вызывает ошибку проверки, в обеих формах отображается сообщение об ошибке. Я меняю порядок 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, независимо от того, какая кнопка отправки была нажата. Таким образом, небольшой трюк, описанный выше, - это просто добавить фильтр (чтобы проверить, есть ли у submit данные, то есть form1.submit1.data).

Кроме того, мы меняем порядок if, поэтому, когда мы нажимаем на одну отправку, она вызывает только validate() эту форму, предотвращая ошибку проверки для обеих форм.

История еще не закончена. Вот .data:

@property
def data(self):
    return dict((name, f.data) for name, f in iteritems(self._fields))

Он возвращает dict с именем поля (ключ) и данными поля (значение), однако наши две кнопки отправки формы имеют то же имя submit (ключ)!

Когда мы нажимаем первую кнопку отправки (в form1), вызов из form1.submit1.data возвращает такой dict:

temp = {'submit': True}

Несомненно, когда мы вызываем if form1.submit.data:, он возвращает True.

Когда мы нажимаем вторую кнопку отправки (в form2), вызов .data в if form1.submit.data: добавляет пару "ключ-значение" в dict first, затем вызов if form2.submit.data: добавляет еще одну пару "ключ-значение", в конце dict понравится это:

temp = {'submit': False, 'submit': True}

Теперь мы вызываем if form1.submit.data:, он возвращает True, даже если кнопка отправки, которую мы нажали, была в форме2.

Вот почему нам нужно определить эти два 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>
person Grey Li    schedule 29.09.2016
comment
ИМХО ОБНОВЛЕНИЕ - лучший ответ. (но methods=['POST'], похоже, отсутствует в @app.route) - person VPfB; 13.01.2018
comment
Я использовал ваш 1-й метод, но строки form1.submit1.data и forn2.submit2.data всегда возвращают false. как с этим справиться? - person Sivaramakrishnan; 28.11.2019
comment
@Sivaramakrishnan Может быть, вы можете создать новый вопрос, включить соответствующий код, а затем разместить ссылку здесь. - person Grey Li; 30.11.2019
comment
Во-вторых, ОБНОВЛЕНИЕ как лучший ответ. И если кому-то интересно, у вас действительно может быть вся функциональность под маршрутом ('/') - если вы не забываете добавлять фигурные скобки в действие формы. Вам не нужны отдельные маршруты для входа и регистрации, даже если это удобная функция, когда дело касается страниц регистрации / входа. Но бывают случаи, когда вы хотите разместить множество различных форм на одной странице и не хотите возиться с огромным количеством маршрутов. - person user3661992; 21.10.2020
comment
Это также устранило мою проблему с отсутствием сообщений об ошибках проверки. Спасибо. Самым простым решением для меня было сделать if form.submit.data and form.validate() и убедиться, что у SubmitFields разные имена. - person Ambassador Kosh; 21.04.2021

Я использовал комбинацию из двух фрагментов фляги. Первый добавляет префикс к форме, а затем вы проверяете префикс с помощью validate_on_submit (). Я также использую шаблон Луи Роше, чтобы определить, какие кнопки нажимаются в форме.

Цитата Дэна Джейкоба:

Пример:

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

Затем добавьте скрытое поле (или просто отметьте поле отправки):

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

Процитируем Луи Роше:

В моем шаблоне есть:

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

И чтобы выяснить, какая кнопка была передана на стороне сервера, у меня есть файл views.py:

if request.form['btn'] == 'Save':
    something0
else:
    something1
person AlexLordThorsen    schedule 17.08.2013
comment
Обратите внимание на порядок, указанный здесь stackoverflow.com/a/39739863/564979 - person anvd; 24.10.2018
comment
Предоставленные ссылки устарели. - person colidyre; 02.03.2020
comment
ИМО, это лучший ответ на этот вопрос! - person Kedar Joshi; 25.02.2021

Простой способ - иметь разные имена для разных полей отправки. Например:

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"

    ...
person Hieu    schedule 31.03.2016
comment
небольшая поправка if login.validate_on_submit() and login_form.login.data: должна быть if login_form.validate_on_submit() and login_form.login.data: - person Ash; 16.04.2018
comment
@ Привет, спасибо. Итак, SubmitField('textHere') указывает, что 'textHere' является значением атрибута name тега ‹form›? Примерно так: <form name='Login'> соответствует SubmitField('Login'). - person KeyC0de; 10.12.2019

В качестве других ответов я также назначаю уникальное имя для каждой кнопки отправки для каждой формы на странице.

Затем веб-действие flask выглядит, как показано ниже - обратите внимание на параметры 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"))
person turdus-merula    schedule 07.09.2017
comment
Этот метод работает лучше всего из всех представленных. Все остальные методы технически тоже работают, но вызывают появление ошибок валидации в формах, где это не предназначалось. В основном это вызывает желаемое поведение form not "clicked" being ignored/dead/not interfering. Спасибо! - person Grzegorz Pudłowski; 16.05.2020

Ну вот простой трюк

Предположим, у вас есть

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"
person joash    schedule 09.03.2020

Пример: несколько WTForm на одной странице html

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>
person Viraj Wadate    schedule 14.02.2019

Я не использовал 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)
person MsAliceOh    schedule 29.07.2019

Обычно я использую скрытый тег, который работает как идентификатор.

Вот пример:

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, он проверит, какой был идентификатор, и, если он равен, он запустит материал формы, который у вас есть в вашем коде.

person DeadSec    schedule 26.04.2020