I am trying to use two Flask forms on the same page. The first form gathers infos to list availables databases, which are passed on as choices to the "SelectField" of the second form.
In my views.py, I define my Forms
class AvailableBDDForm(FlaskForm):
host = StringField('host',
validators=\[DataRequired()\])
port = StringField('port',
validators=\[DataRequired()\])
user = StringField('user',
validators=\[DataRequired()\])
password = StringField('password', validators=\[DataRequired()\])
submitAvailableBDD = SubmitField('METTRE A JOUR LA LISTE')
next = HiddenField()
class ControlerBDDForm(FlaskForm):
database = SelectField('database', validators=\[DataRequired()\])
submit = SubmitField('SE CONNECTER')
next = HiddenField()
def update_choices(self, databases):
self.database.choices = databases
As well as my route
@app.route("/analye-bdd/", methods=("GET","POST",))
def analyse_bdd():
availableForm = AvailableBDDForm()
controlerForm = ControlerBDDForm()
selected_options = []
scroll=""
bdd = BDD()
available_databases = bdd.lister_bdd()
try:
if availableForm.validate_on_submit():
print("first if")
bdd.update_parameters(availableForm.host.data, availableForm.port.data, availableForm.user.data, availableForm.password.data)
available_databases = bdd.lister_bdd()
print(available_databases)
controlerForm.update_choices(available_databases)
if controlerForm.validate_on_submit():
print("helloooo")
except Exception as e:
print(f"An error occurred: {e}")
return render_template(
"analyse-bdd.html",
title="Analyze",
AvailableBDDForm=availableForm,
ControlerBDDForm=controlerForm,
selected_options=selected_options,
scroll_to=scroll,
host = bdd.host,
port=bdd.port,
database=bdd.database,
user=bdd.user,
password=bdd.password
)
And here is what I put in my HTML page:
<form method="POST" action="{{ url_for('analyse_bdd')}}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
{{ AvailableBDDForm.host(size=30, value=host, class="texteInput")}}
{{ AvailableBDDForm.port(size=10, value=port) }}
{{ AvailableBDDForm.user(size=30, value=user) }}
{{ AvailableBDDForm.password(size=30, value=password) }}
{{ AvailableBDDForm.next }}
<br>
{{ AvailableBDDForm.submitAvailableBDD(class_="btn btn-primary") }}
</form>
<form method="POST" action="{{ url_for('analyse_bdd')}}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<label>Database</label>
{{ ControlerBDDForm.database }}
{{ ControlerBDDForm.submit(class_="btn btn-primary") }}
</form>
When I click on the first submit button (linked to AvailableBDDForm), the terminal shows:
first if ['db1', 'db2']
So it works. Alas, when I click on the second submit button (linked to ControlerBDDForm):
An error occurred: Choices cannot be None.
I get neither of my prints (first if or hellooooo), but I do get this error.
I really don't understand what's going on, plus the logic inside the try is all messed up, like I also previously tried
@app.route("/analye-bdd/", methods=("GET","POST",))
def analyse_bdd():
availableForm = AvailableBDDForm()
controlerForm = ControlerBDDForm()
selected_options = []
scroll=""
bdd = BDD()
available_databases = bdd.lister_bdd()
try:
availableFormBool = availableForm.validate_on_submit()
controlerFormBool = controlerForm.validate_on_submit()
print(availableFormBool, controlerFormBool)
if availableForm.validate_on_submit():
print("first if")
bdd.update_parameters(availableForm.host.data, availableForm.port.data, availableForm.user.data, availableForm.password.data)
available_databases = bdd.lister_bdd()
print(available_databases)
controlerForm.update_choices(available_databases)
if controlerForm.validate_on_submit():
print("helloooo")
except Exception as e:
print(f"An error occurred: {e}")
return render_template(
"analyse-bdd.html",
title="Analyze",
AvailableBDDForm=availableForm,
ControlerBDDForm=controlerForm,
selected_options=selected_options,
scroll_to=scroll,
host = bdd.host,
port=bdd.port,
database=bdd.database,
user=bdd.user,
password=bdd.password
)
To see which form was submitted when I clicked, but I just didn't get anything on the terminal, neither the result of
print(availableFormBool, controlerFormBool)
or the "first if"
print, nothing...
Also, a similar post exists on stackoverflow (flask multiple forms on the same page) but my forms are already different in id, name and value, so it doesn't solve my problem.
Thanks in advance
EDIT : Original problem solved but I have another question
There is a list of checkboxes (input) on my HTML page, and I can access their content in my view with the following code :
`for field_name in bdd.analyze_options:
if request.form.get(field_name):
selected_options.append(request.form.get(field_name))
print("selected options", selected_options)
analyze_results = bdd.affichage_analyse(selected_options)
print(f"analyze results {analyze_results}")``
I placed it here :
@app.route("/analyze/", methods=("GET","POST",))
def analyze():
bdd = BDD()
form = AvailableBDDForm(request.form, data=session.get('avail_bdd', {}))
selected_options = []
analyze_results = {}
scroll_to = ""
if form.validate_on_submit():
data = {k:v for k,v in form.data.items() if k in ('host', 'port', 'user', 'password',)}
if session.get('avail_bdd', {}) != data:
session['avail_bdd'] = data
return redirect(url_for('analyze'))
if 'avail_bdd' in session:
bdd.update_parameters(**session['avail_bdd'])
form1 = ControlerBDDForm(request.form, data=session.get('ctrl_bdd', {}))
form1.update_choices(bdd.lister_bdd())
if form1.validate_on_submit():
session['ctrl_bdd'] = {k:v for k,v in form1.data.items() if k in ('database',)}
print(form1.database.data)
scroll_to = "for-scroll-results"
for field_name in bdd.analyze_options:
if request.form.get(field_name):
selected_options.append(request.form.get(field_name))
print("selected options", selected_options)
analyze_results = bdd.affichage_analyse(selected_options)
print(f"analyze results {analyze_results}")
return redirect(url_for('analyze'))
return render_template('analyze.html', **locals(),
title="Analyze BDD")
I don't know why, selected_options only has content when I click on the submit button of AvailableBDDForm. But I would want this part of the code that concerns the checkboxes only when ControlerBDDForm is validated... I tried a variety of locations and alternatives to this code, but to no avail.
My example uses the session to temporarily save the form entries.
If the valid data has changed when the first form was submitted compared to the data saved in the session, it is updated in the session. If this is not the case, the data from the session is used to fill out the second form. After the second form has been submitted, its entries can be output.
To request checkboxes, you can add a SelectMultipleField
to the respective form in which the widgets are adjusted. The options are added in the same way as with a SelectField
. However, due to a bug, the data of the fields must be set manually so that they retain their status.
from flask import session
from wtforms.fields import SelectMultipleField
from wtforms.widgets import ListWidget, CheckboxInput
# ...
class AvailableBDDForm(FlaskForm):
host = StringField('Host',
validators=[DataRequired()])
port = StringField('Port',
validators=[DataRequired()])
user = StringField('User',
validators=[DataRequired()])
password = StringField('Password',
validators=[DataRequired()])
submit = SubmitField('METTRE A JOUR LA LISTE')
next = HiddenField()
class ControlerBDDForm(FlaskForm):
database = SelectField('Database', validators=[DataRequired()])
options = SelectMultipleField('Analyze Options',
option_widget=CheckboxInput(),
widget=ListWidget(prefix_label=False))
submit = SubmitField('SE CONNECTER')
next = HiddenField()
def update_choices(self, databases):
self.database.choices = databases
def update_options(self, options, data=[]):
self.options.choices = options
if not self.is_submitted():
self.options.data = data
@app.route('/analyze', methods=['GET', 'POST'])
def analyze():
bdd = BDD()
form = AvailableBDDForm(request.form, data=session.get('avail_bdd', {}))
if form.validate_on_submit():
data = {k:v for k,v in form.data.items() if k in ('host', 'port', 'user', 'password',)}
if session.get('avail_bdd', {}) != data:
session['avail_bdd'] = data
return redirect(url_for('analyze'))
if 'avail_bdd' in session:
bdd.update_parameters(**session['avail_bdd'])
data = session.get('ctrl_bdd', {})
form1 = ControlerBDDForm(request.form, data=data)
form1.update_choices(bdd.lister_bdd())
form1.update_options(bdd.analyze_options, data.get('options', []))
if form1.validate_on_submit():
session['ctrl_bdd'] = {k:v for k,v in form1.data.items() if k in ('database','options',)}
print(form1.database.data)
print(form1.options.data)
return redirect(url_for('analyze'))
return render_template('analyze.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Analyze</title>
</head>
<body>
<form method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.host.label() }}
{{ form.host() }}
</div>
<div>
{{ form.port.label() }}
{{ form.port() }}
</div>
<div>
{{ form.user.label() }}
{{ form.user() }}
</div>
<div>
{{ form.password.label() }}
{{ form.password() }}
</div>
{{ form.submit() }}
</form>
{% if form1 -%}
<form method="POST">
{{ form1.hidden_tag() }}
<div>
{{ form1.database.label() }}
{{ form1.database() }}
</div>
<div>
{{ form1.options.label() }}
{{ form1.options() }}
</div>
{{ form1.submit() }}
</form>
{% endif -%}
</body>
</html>
If you want to use more than two forms, I recommend using a URL parameter that is incremented depending on the form submitted.
@app.route('/analyze', methods=['GET', 'POST'])
def analyze():
bdd = BDD()
form = AvailableBDDForm(request.form, data=session.get('avail_bdd', {}))
step = request.args.get('step', 1, type=int)
match step:
case 1:
if form.validate_on_submit():
data = {k:v for k,v in form.data.items() if k in ('host', 'port', 'user', 'password',)}
if session.get('avail_bdd', {}) != data:
session['avail_bdd'] = data
return redirect(url_for('analyze', step=step+1))
case 2:
if 'avail_bdd' in session:
bdd.update_parameters(**session['avail_bdd'])
data = session.get('ctrl_bdd', {})
form1 = ControlerBDDForm(request.form, data=data)
form1.update_choices(bdd.lister_bdd())
form1.update_options(bdd.analyze_options, data.get('options', []))
if form1.validate_on_submit():
session['ctrl_bdd'] = {k:v for k,v in form1.data.items() if k in ('database',)}
print(form1.database.data)
print(form1.options.data)
# possibly here forward
# return redirect(url_for('analyze_1', step=step+1))
# ...
case _:
pass
return render_template('analyze.html', **locals())
<form method="POST" action="{{ url_for('analyze', step=1) }}">
{# ... #}
</form>
{% if form1 -%}
<form method="POST" action="{{ url_for('analyze', step=2) }}">
{# ... #}
</form>
{% endif -%}