Adding fields dynamically
I think best would be to create an empty form and then add fields in a for-loop, however if a form is posted back how can I validate the form if I don't have it defined in a class?
Add the fields to the form class using setattr
before the form is instantiated:
def update_form(request):
table = query()
class MyForm(Form):
pass
for row in table:
setattr(MyForm, row.key, SomeField())
form = MyForm(request.form)
However, I think your question is part of a bigger problem, which I have tried to address below.
Mapping tables to forms
Your table seem to map very nicely to the form itself. If you want to create forms dynamically from your tables, you could write the logic yourself. But when the range of fields and options to support grows, it can be a lot of work to maintain. If you are using SQLAlchemy, you might want to take a look at WTForms-Alchemy. From its introduction:
Many times when building modern web apps with SQLAlchemy you’ll have
forms that map closely to models. For example, you might have a
Article model, and you want to create a form that lets people post new
article. In this case, it would be time-consuming to define the field
types and basic validators in your form, because you’ve already
defined the fields in your model.
WTForms-Alchemy provides a helper class that let you create a Form
class from a SQLAlchemy model.
The helper class is ModelForm
, and in the style of your table, below is a Python 2/3 sample with WTForms-Alchemy. Install the package wtforms-alchemy
first, which will pull in SQLAlchemy and WTForms as well.
from __future__ import print_function
from __future__ import unicode_literals
import sqlalchemy as sa
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from wtforms_alchemy import ModelForm
engine = create_engine('sqlite:///:memory:')
Base = declarative_base(engine)
Session = sessionmaker(bind=engine)
session = Session()
class MyClass(Base):
__tablename__ = 'mytable'
id = sa.Column(sa.BigInteger, autoincrement=True, primary_key=True)
title = sa.Column(sa.Unicode(5), nullable=False)
gender = sa.Column(sa.Enum('male', 'female', name='gender'))
text = sa.Column(sa.Text)
class MyForm(ModelForm):
class Meta:
model = MyClass
form = MyForm()
print('HTML\n====')
for field in form:
print(field)
Running the above code prints:
HTML
====
<input id="title" name="title" required type="text" value="">
<select id="gender" name="gender"><option value="male">male</option><option value="female">female</option></select>
<textarea id="text" name="text"></textarea>
As you can see, WTForms-Alchemy did a whole lot with MyForm
. The class is essentially this:
class MyForm(Form):
title = StringField(validators=[InputRequired(), Length(max=5)])
gender = SelectField(choices=[('male', 'male'), ('female', 'female')])
text = TextField()
The documentation for WTForms-Alchemy seems to be very comprehensive. I have not used it myself, but if I had a similar problem to solve I would definitely try it out.