92 votes

Plusieurs formulaires dans une seule page en utilisant flask et WTForms

J'ai plusieurs formulaires sur la même page qui envoient des requêtes post au même gestionnaire dans Flask.

Je génère des formulaires à l'aide de wtforms.

Quelle est la meilleure façon d'identifier le formulaire qui a été soumis ?

J'utilise actuellement action="?form=oneform" . Je pense qu'il devrait y avoir une meilleure méthode pour réaliser la même chose ?

104voto

Grey Li Points 2726

La solution ci-dessus a un bug de validation lorsqu'un formulaire provoque une erreur de validation, les deux formulaires affichent un message d'erreur. Je change l'ordre des if pour résoudre ce problème.

Tout d'abord, définissez vos multiples SubmitField avec des noms différents, comme ceci :

class Form1(Form):
    name = StringField('name')
    submit1 = SubmitField('submit')

class Form2(Form):
    name = StringField('name')
    submit2 = SubmitField('submit')

....

Ensuite, ajoutez un filtre dans 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 
....

Le problème était maintenant résolu.

Si vous voulez vous y plonger, alors continuez à lire.

Voici 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()

Et voici 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

Lorsque vous appelez form.validate_on_submit() Il vérifie si le formulaire est soumis par la méthode HTTP, quel que soit le bouton de soumission sur lequel on a cliqué. La petite astuce ci-dessus consiste donc à ajouter un filtre (pour vérifier si le formulaire a des données, par exemple, form1.submit1.data ).

De plus, nous changeons l'ordre des if donc quand on clique sur "soumettre", il ne fait qu'appeler validate() à ce formulaire ce qui évite l'erreur de validation pour les deux formulaires.

L'histoire n'est pas encore terminée. Voici .data :

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

Il renvoie cependant un dict avec le nom du champ (clé) et les données du champ (valeur), le bouton de soumission de nos deux formulaires a le même nom submit (clé) !

Lorsque nous cliquons sur le premier bouton d'envoi (dans le formulaire 1), l'appel de la fonction form1.submit1.data retourner un dict comme ceci :

temp = {'submit': True}

Il n'y a aucun doute quand nous appelons if form1.submit.data: il retourne True .

Lorsque nous cliquons sur le deuxième bouton d'envoi (dans le formulaire 2), l'appel à la fonction .data en if form1.submit.data: ajouter une clé-valeur dans le dict premièrement puis l'appel de if form2.submit.data: ajouter une autre clé-valeur, à la fin, le dict sera comme ceci :

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

Maintenant, nous appelons if form1.submit.data: il retourne True même si le bouton d'envoi sur lequel nous avons cliqué se trouvait dans le formulaire 2.

C'est pourquoi nous devons définir ces deux SubmitField avec des noms différents. Au fait, merci d'avoir lu (jusqu'ici) !

Mise à jour

Il existe un autre moyen de gérer plusieurs formulaires sur une même page. Vous pouvez utiliser des vues multiples pour traiter les formulaires. Par exemple :

...
@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)

Dans le modèle (index.html), vous devez rendre les deux formulaires et définir l'attribut action à la vue cible :

<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>

65voto

Rawrgulmuffins Points 1513

J'ai utilisé une combinaison de deux snippets flask. La première ajoute un préfixe à un formulaire et ensuite vous vérifiez le préfixe avec validate_on_submit(). J'utilise aussi le modèle de Louis Roché pour déterminer quels boutons sont poussés dans un formulaire .

Pour citer Dan Jacob :

Exemple :

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

Ensuite, ajoutez un champ caché (ou vérifiez simplement un champ de soumission) :

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

Pour citer celui de Louis Roché :

J'ai dans mon modèle :

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

Et pour savoir quel bouton a été passé côté serveur, j'ai dans mon fichier views.py :

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

22voto

Hieu Points 473

Une façon simple de procéder consiste à donner des noms différents aux différents champs de soumission. Par exemple exemple :

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"

    ...

9voto

turdus-merula Points 4230

Comme les autres réponses, j'attribue également un nom unique à chaque bouton d'envoi, pour chaque formulaire de la page.

Ensuite, l'action web flask ressemble à ce qui suit - notez l'élément formdata y obj paramètres, qui permettent de init / préserver les champs du formulaire en conséquence :

@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"))

5voto

joash Points 186

Voici une astuce simple

Supposons que vous ayez

Formulaire 1, Formulaire 2, et index


Form1  <form method="post" action="{{ url_for('index',formid=1) }}">

Form2  <form  method="post" action="{{ url_for('index',formid=2) }}">

Maintenant en indice

@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"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X