I am trying to set a dynamic form on the fly from JSON data, using Flask-WTF. It doesn’t matter here, but for context the form is made of multiple fieldsets, populated with different field types depending on the content of the JSON, which is why I use FormFields.
I understand I have to manually bind each FormField, however I keep getting a RecursionError when trying to access the forms, which obviously I need in my Jinja2 template.
>>> from wtforms import FormField
>>> from flask_wtf import FlaskForm, Form
>>> class DynamicForm(Form):
... pass
>>> class ParentForm(Form): # should inherit from FlaskForm but it won't work in console
... pass
>>> p = ParentForm()
>>> foobar = FormField(DynamicForm, label="foo")
>>> foobar
<UnboundField(FormField, (<class '__main__.DynamicForm'>,), {'label': 'foo'})>
>>> foobar.form
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'UnboundField' object has no attribute 'form'
>>> foobar = FormField(DynamicForm, label="foo").bind(p, name="bar")
>>> foobar
<wtforms.fields.form.FormField object at 0x7fbaf8776710>
>>> foobar.form # same with .data or ._fields or ._unbound_fields…
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/wtforms/fields/form.py", line 89, in __getattr__
return getattr(self.form, name)
^^^^^^^^^
File "/usr/lib/python3/dist-packages/wtforms/fields/form.py", line 89, in __getattr__
return getattr(self.form, name)
^^^^^^^^^
File "/usr/lib/python3/dist-packages/wtforms/fields/form.py", line 89, in __getattr__
return getattr(self.form, name)
^^^^^^^^^
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
The issue is that the binding doesn’t initialize the fields of the FormField. You need to call process
after the binding :
foobar = FormField(DynamicForm, label="foo").bind(p, name="bar")
foobar.process(formdata=None)
Then it works :
>>> foobar.form
<__main__.DynamicForm object at 0x7f4bbfcd97d0>
>>> foobar.data
{}
>>> foobar._fields
OrderedDict()
>>> foobar._unbound_fields
[]
Complete code for convenience :
from flask import request
from flask_wtf import FlaskForm, Form
from wtforms import FormField, IntegerField
class DynamicForm(Form):
pass
class ParentForm(Form): # should inherit from FlaskForm but it won't work in console
pass
# dynamically set whichever fields you want in subforms
setattr(DynamicForm, "foo", IntegerField("bar"))
p = ParentForm()
foobar = FormField(DynamicForm, label="foo").bind(p, name="bar")
# initialize with None only for first display,
# when the user clicks we want the actual data !
foobar.process(formdata=request.form if request.method=="POST" else None)