Dynamic Form Fields with WTForms, Flask, HTMX, and Frustration
I’ve been working on creating a data layer generator using flask-wtf for quite some time – 6 months I think – and was quite perplexed when I wanted to create a dynamic form field where you can add/remove form fields. The idea was that you had a basic event group and event name as a required parameter and you can add more custom parameters to build your data layer. The output would be a pretty printed JSON you can copy without worrying about missing brackets, or structure.
There were two references that I thought would be great:
Both were great at explaining WTF was WTForms. The documentation for the package itself got me quite lost. But it was integrated with flask so I thought it would be less of a pain and there should be a lot of references right? Well, those were the only two reference that matched what I was looking for.
Geekforgeeks had a good tutorial and was one of the indicator that using htmx wasn’t a bad idea and JustDjango gave me an idea on how tohx-swap conveniently. I actually started with these articles and didn’t come back for another month because I could not translate it into what I wanted.
Not Pythonic Enough
Grinberg’s and Medina’s solution was the best I could find but it had some issues for me: the use of macros and javascript. I really did not want to overcomplicate my code by adding too much bells and whistles, I only wanted this piece of code to work.
<button type="button"
hx-post="{{ url_for('add_dl_form') }}"
hx-target="#parameter"
hx-swap="beforeend">
Add Parameter
</button>
This should add a form after the required parameters (event group and event name). Simple. But both Grinberg and Medina became complicated fast because of how FieldLists work in WTForms. They needed to have an index that points the form inputs. The docs mention this but does not give an example on how to implement it.
After reading it twenty times, I noticed that FieldList had an append_entry() function. Since most references would do a for loop for the entries which would mean it would contain the list of fields and can be easily manipulated.
@app.route("/add-dl-form",methods=['POST'])
def add_dl_form():
eventform = EventForm()
eventform.parameters.append_entry()
form_len = eventform.parameters.__len__()
form = eventform.parameters.entries[form_len-1]
return render_template("datalayer_param.html",form=form)
In combination with the hx-post method we can swap any tag with an id=parameter with whatever is in datalayer_param.html, which in this case would be:
<br>
{{form.parameter_key.label}}
{{form.parameter_key(class_="input")}}
<br>
{{form.parameter_value.label}}
{{form.parameter_value(class_="input")}}