I followed this example to create an editable table form to display data/edit from my database. I don't understand why the submit button is inside the row form class, so I put it in the table form class.
I keep getting TypeError: argument of type 'CSRFTokenField' is not iterable
, which I suspect has something to do with using a FieldList, but I'm stuck. I found a similar question, but it was never answered.
I am using the flask blueprint structure.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, FieldList, FormField, SelectField, BooleanField, FloatField
class RowForm(FlaskForm):
col1 = SelectField('col1',
choices=[(0, 'YES'), (1, 'NO')])
col2 = FloatField('col2')
col3 = StringField('col3')
class TableForm(FlaskForm):
title = StringField('title')
rows = FieldList(FormField(RowForm))
submit = SubmitField('submit')
views.py
#...imports
@test.route('/table_test', methods=['GET', 'POST'])
def show_test_table():
table_form = TableForm()
row_static_list = RowTest.query.all()
# looping through query to prepopulate values for editable fields
for row in row_static_list:
row_form = RowForm()
# adding editable fields to Row Form
row_form.col1 = 1 if row.col1 else 0
row_form.col2 = row.col2
row_form.col3 = row.col3
table_form.rows.append_entry(row_form)
if request.method == 'POST':
print(table_form.csrf_token)
# if form is valid, update SensorConfig with changes
if table_form.validate_on_submit():
print('***************\nFORM IS VALID\n***************')
else:
print('***************\nFORM IS NOT VALID\n***************')
return render_template('test/table_form.html', table_form=table_form, rows_fixed=row_static_list)
table_form.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}TESTING TABLE{% endblock %}
{% block head %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='dropdown.css') }}">
{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Testing Table Form</h1>
</div>
<div>
<form action method="post" name="TableForm">
{{ table_form.csrf_token }}
{{ table_form.submit }}
<div>
<table class="content-table">
<thead>
<tr>
<th>Col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
</tr>
</thead>
<tbody>
{% for r, r_fixed in zip(table_form.rows, rows_fixed) %}
<tr>
<td>{{ r.col1 }}</td>
<td>{{ r.col2 }}</td>
<td>{{ r.col3 }}</td>
<td>{{ r_fixed.col4 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
</div>
{% endblock %}
NOTE: The zip
above is required else the fixed values load as blank. See here.
Shoutout to taylor_prime and dnswrsrx on the r/Flask Discord for the help. The solution is to use Form as the Base Class for RowForm
and FlaskForm as the Base Class for TableForm
. Using FlaskForm as the Base Class for RowForm
seemed to add a new csrf_token for each new instance of RowForm
added to FlaskForm
.
Adjusting forms.py to the below fixed the problem.
from flask_wtf import Form, FlaskForm
from wtforms import StringField, SubmitField, FieldList, FormField, SelectField, BooleanField, FloatField
class RowForm(Form):
col1 = SelectField('col1',
choices=[(0, 'YES'), (1, 'NO')])
col2 = FloatField('col2')
col3 = StringField('col3')
class TableForm(FlaskForm):
title = StringField('title')
rows = FieldList(FormField(RowForm))
submit = SubmitField('submit')