create dynamic fields in WTform in Flask
Asked Answered
A

1

10

I want to create different forms in Flask using WTForms and Jinja2. I make a call to mysql, which has the type of field.

So i.e. the table could be:

 form_id   |  type         |   key    |    options      | default_value
    1      |  TextField    |   title  |                 |      test1
    1      |  SelectField  |   gender |{'male','female'}|      
    2      |  TextAreaField|   text   |                 |   Hello, World!

Then I query on form_id. then I want to create a form with WTforms having the fields of the rows which are returned.

For a normal form I do:

class MyForm(Form):

    title = TextField('test1', [validators.Length(min=4, max=25)])
    gender = SelectField('', choices=['male','female'])


def update_form(request):

     form = MyForm(request.form)

     if request.method == 'POST' and form.validate():
          title = form.title.data
          gender = form.gender.data

          #do some updates with data
          return .....
     else:
          return render_template('template.html',form)
          #here should be something like:
          #dict = query_mysql()
          #new_form = MyForm(dict);
          #render_template('template.html',new_form)

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? I do have the form_id in the form so I can generate it and then validate.

Asylum answered 22/9, 2016 at 13:9 Comment(0)
R
12

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.

Refinery answered 24/9, 2016 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.