python-3.xflask-wtforms

WTForms subform entry disappears after submit


I am implementing a web page in Python that uses FieldList to populate subform data. When the page is first rendered, the form is correctly populated with two subform entries, but after a SubmitField is submitted, one of the subform entries disappears even though exactly the same dictionary is supplied each time to populate the form.

Here are the classes that define my form and subform, followed by the first part of my view function for index.html:

class RunControl(FlaskForm):
    start = SubmitField('Start Run')
    delete = SubmitField('Delete Run')
    plot = SubmitField('Plot')
    runID = StringField('Run ID', render_kw={'readonly': True})
    imageURL = StringField('Image URL', render_kw={'readonly': True})

class RunsForm(FlaskForm):
    runs = FieldList(FormField(RunControl))
    stop = SubmitField('Stop Run')
    activeRunID = StringField('Active Run ID',  render_kw={'readonly': True})
    activeDescr = StringField('Active Run Description', render_kw={'readonly': True})

@app.route('/', methods=['GET', 'POST'])
def index():
    runList = getRunList()
    runsForm = RunsForm(runs=runList, min_entries=len(runList))

Each time the view function is executed, the contents of runList are exactly the same: a dictionary with the same two items. But if I add a debug statement to print the length of runsForm.runs right after RunsForm is created, it shows that when the page is first displayed, the length of runsForm.runs is 2, and after one of my SubmitFields is submitted and the view function is called again, the length of runsForm.runs is 1.

What could be causing this? Why should the initialization of RunForm completely ignore one of my dictionary items on the second pass? Am I just misunderstanding how space is allocated with min_entries?


Solution

  • It turns out that every form AND sub-form on a page must have its own hidden_tag( ) in the Jinja2 template. Apparently, the CRSF protection in Flask will render the first sub-form and then drop all the rest if you don't do this, and it won't give you any indication of a problem. My previous template had this loop.

        {% for run in runsForm.runs %}
            {{ run.runID.data }} {{ run.start() }} {{ run.plot()}} {{ run.delete() }}
            <br>
        {% endfor %}
    

    So all I had to do was add hidden_tag() to the loop like this and it worked.

    
        {% for run in runsForm.runs %}
            {{ run.hidden_tag() }}
            {{ run.runID.data }} {{ run.start() }} {{ run.plot()}} {{ run.delete() }}
            <br>
        {% endfor %}