在 Flask 的 WTforms 中创建动态字段

create dynamic fields in WTform in Flask

我想使用 WTForms 和 Jinja2 在 Flask 中创建不同的表单。我调用了具有字段类型的 mysql。

所以 table 可能是:

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

然后我查询form_id。然后我想用 WTforms 创建一个表单,其中包含返回的行的字段。

对于正常形式,我这样做:

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)

我认为最好是创建一个空表单,然后在 for 循环中添加字段,但是如果表单被回发,如果我没有在 class?我在表单中确实有 form_id,因此我可以生成它然后进行验证。

动态添加字段

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?

在实例化表单之前使用 setattr 将字段添加到 表单 class

def update_form(request):
    table = query()

    class MyForm(Form):
        pass

    for row in table:
        setattr(MyForm, row.key, SomeField())

    form = MyForm(request.form)

但是,我认为你的问题是我试图在下面解决的更大问题的一部分。

将 tables 映射到表单

您的 table 似乎很好地映射到表单本身。如果您想从 table 动态创建表单,您可以自己编写逻辑。但是,当支持的领域和选项范围扩大时,维护工作可能会非常繁重。如果您使用 SQLAlchemy, you might want to take a look at WTForms-Alchemy。来自它的介绍:

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.

助手 class 是 ModelForm,按照您的 table 的风格,下面是一个 Python 2/3 的 WTForms-Alchemy 示例。首先安装包 wtforms-alchemy,这也会引入 SQLAlchemy 和 WTForms。

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)

运行 上面的代码打印:

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>

如您所见,WTForms-Alchemy 用 MyForm 做了很多事情。 class 本质上是这样的:

class MyForm(Form):
    title = StringField(validators=[InputRequired(), Length(max=5)])
    gender = SelectField(choices=[('male', 'male'), ('female', 'female')])
    text = TextField()

WTForms-Alchemy 的文档似乎非常全面。我自己没有用过,但如果我有类似的问题要解决,我一定会尝试一下。