Search code examples
pythonpython-3.xflaskflask-wtforms

WTForms Editable Table 'CSRFTokenField' is not iterable


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.

enter image description here


Solution

  • 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')