Reputation: 623
I have following issue with validate_on_submit() method in my Flask application.
Quick overview what I have:
My form class is created dynamically based on input:
class EditUsers(FlaskForm):
submit = SubmitField('Submit')
def form_builder(roles_list):
class EditUsersForm(EditUsers):
pass
for role in roles_list:
if isinstance(role, tuple) and len(role) == 5:
if not role[2] and role[3]:
setattr(EditUsersForm, f'{role[0]}_{role[4]}_bool', BooleanField(label=role[0]))
setattr(EditUsersForm, f'{role[0]}_{role[4]}_date', SelectField('Expiration Period', choices=choices))
else:
raise IncorrectType
IncorrectType is custom Exception i prepared and choices are created using datetime in same file (This is irrelevant so I am not including it in code).
My route in flask app (simplified):
#### EDIT: I pasted the wrong route, POST and GET Methods are included###
@edit_users.route('/users', methods=['GET', 'POST'])
def users():
... # Some code there
form = form_builder(roles_to_form)
print(form.is_submitted())
print(form.validate())
print(form.validate_on_submit())
return render_template('edit_user.html',
data_dict=data_dict, # in data_dict i pass form fields names and some other data
form=form,
)
My template:
<!-- Some usual stuff goes there css js etc -->
<div >
<form class="form form-horizontal" method="post" role="form" style="margin: auto; text-align: center; width: 40%;">
{{ form.csrf_token() }}
<table class="table" align="center">
<thead>
<th>Role</th>
<th>Expiration Date</th>
<th>Give Role?</th>
</thead>
{% for field in data_dict['id_list'] %}
<tr>
<td align="left">{{ field[2] }}</td>
<td align="left">
{{ form[field[1]] }}
</td>
<td align="left">{{ form[field[0]] }}</td>
</tr>
{% endfor %}
<tr>
<td colspan="3" align="center">{{ form.submit() }}</td>
</tr>
</table>
</form>
</div>
What is my issue?
When I am hitting my Submit button, is_submitted()
returns True but validate()
does not, thus I cannot use typical if form.validated_on_submit()
as it returns False even when I submit my form.
I dig a little and spot something unusual. I used protected members of form attributes, to check whatever validate() function sees as input:
for name in form._fields:
print(name)
inline = getattr(form.__class__, 'validate_%s' % name, None)
print(inline)
The output (don't mind names of fields):
submit
None
Engineer_2_bool
None
Engineer_2_date
None
Operator_3_bool
None
Operator_3_date
None
Supervisor_4_bool
None
Supervisor_4_date
None
Guest_5_bool
None
Guest_5_date
None
csrf_token
None
I don't know why my form does not have validate_{name}
attributes.
Upvotes: 0
Views: 81
Reputation: 623
The problem was with type of data passed to choices of SelectField
. I passed list of tuples with datetime.datetime
to it. When changed type to str
everything works smoothly.
Upvotes: 0
Reputation: 828
I made a working example from your code, and I ended up with the problem you described.
If I reversed well your code, it seems that form[field[1]]
is your BooleanField or SelectField. So to render it in a template your have to use form[field[1]]()
(can use render_field as well).
So :
<tr>
<td align="left">{{ field[2] }}</td>
<td align="left">
{{ form[field[1]] }}
</td>
<td align="left">{{ form[field[0]] }}</td>
</tr>
corrected into :
<tr>
<td align="left">{{ field[2] }}</td>
<td align="left">
{{ form[field[1]]() }}
</td>
<td align="left">{{ form[field[0]]() }}</td>
</tr>
https://flask.palletsprojects.com/en/1.1.x/patterns/wtforms/#forms-in-templates
As you are using a FlaskForm, in your template you have to replace {{ form.csrf_token() }}
by {{ form.csrf_token }}
https://flask-wtf.readthedocs.io/en/stable/csrf.html#html-forms
[EDIT Does not make any difference]
[Edited after w8eight's comment]
The route is not authorized for POST (and the form makes a POST request as seen in <form class="form form-horizontal" method="post" [...]
.
So you have to change : @edit_users.route('/users', methods=['GET'])
to @edit_users.route('/users', methods=['GET','POST'])
Upvotes: 1