8

I want to define a form class with fields based on a dict of name: label. I tried the following, which nearly worked. However, rendering the fields in a template gave AttributeError: 'UnboundField' object has no attribute '__call__'. How can I dynamically add fields to a form?

def build_form(name, record):
    class ContactForm(FlaskForm):
        name = StringField(name)
        fieldlist = {}

        for key, value in record.items():
            fieldlist[key] = StringField(key)

    @app.route('/', methods=['GET', 'POST'])
    def showform():
        form = ContactForm(request.form)

        if request.method == 'POST':
            return 'form processed'

        return render_template('cardcompare.tpl', record=record, form=form)
<form method=post>
    {{ form.name() }}
    {% for key, value in record.items() %}
        {{ form.fieldlist[key]() }}
    {% endfor %}
    <input type=submit value=Register>
</form>
davidism
  • 121,510
  • 29
  • 395
  • 339
Omniver
  • 350
  • 1
  • 3
  • 9

1 Answers1

12

Use setattr to add new fields as attributes of the form class. This will cause WTForms to set up the field correctly instead of keeping the unbound field.

# form class with static fields
class MyForm(FlaskForm):
    name = StringField('static field')

record = {'field1': 'label1', 'field2': 'label2'}

# add dynamic fields
for key, value in record.items():
    setattr(MyForm, key, StringField(value))

In the template you can iterate over the fields using the attr filter.

{% for key, value in record.items() %}:
    {{ form|attr(key)() }}
{% endfor %}
Feodoran
  • 1,752
  • 1
  • 14
  • 31
  • Thanks, is there a way though to get the dynamic fields added to a structure which can be iterated through in the template? (e.g. like fieldlist in the example)? The goal is to handle the dynamic fields differently than the static in the template. – Omniver May 09 '17 at 15:54
  • That part was deleted a litte overeagerly by an edit. I recoverd it. – Feodoran May 09 '17 at 16:01
  • I've done the same by putting `setattr` inside `__init__` of MyForm , because i need to pass attribute list to the form, it works except the first time running, do you know why? – TomSawyer Oct 05 '17 at 21:10
  • 3
    You need to add these fields to the class, not the instance. This means, *before* calling `MyForm()`. – Feodoran Oct 06 '17 at 10:09
  • @Feodoran i already added to the class but it only work since second form submit. Please take a look at my question here: https://stackoverflow.com/questions/2583620/dynamically-create-class-attributes – TomSawyer Oct 09 '17 at 08:59
  • @TomSawyer Some example of your code would help. Maybe open a new question? – Feodoran Oct 09 '17 at 18:17
  • @Feodoran i created my question here: https://stackoverflow.com/questions/46641456/python-couldnt-dynamically-set-attributes-to-a-flaskform – TomSawyer Oct 09 '17 at 18:19
  • adding attr to class is wrong, if you call this class again with other instance, the attribute will not be resetted. – TomSawyer Aug 31 '19 at 17:09
  • @TomSawyer whether this is right or wrong depends on what behavior you are looking for ... – Feodoran Sep 01 '19 at 18:12
  • @TomSawyer - https://stackoverflow.com/questions/9541025/how-to-copy-a-python-class, Chiming in a little late here but, I found creating a copy of the class (as outlined in the link) and using that to add attr to without polluting the original seemed to work for what I was trying to do. – GRW Aug 14 '20 at 03:13