Add input fields dynamically with wtforms
Asked Answered
F

2

27

I'm not quite sure how approach this matter. I hope i get there.

For example I have a table full of addresses on a page. The count of these are dynamic (could be 5 or 10 or any other count). And I want the possibility to edit them on one page.

My approach was to create a Form with wtforms to edit one address and to multiply it in a jinja2 for loop and append to the html propertys name and id the loop.index0 from the itereation, so i can extract each row of data manually and put it back in my form, when I want to evaluate it.

So the Form for this example would be:

class AdressForm(Form):
    name = TextField()

so now my template aproach looks like the following (break down to one input field):

{% for address in addresses %}
    {{ forms.render_field(addressform.name, id = "name_" ~ loop.index0, 
                          name = "name_" ~ loop.index0, value = address.name) }}
{% endfor %}

(forms.render_field is just a macro to specify the right classes to the field function of wtforms. like they use in many tutorials)

So this is not working, since you can't pass the name parameter manually to the field function, since wtforms create the name html-paramter from the variblename of the intial Form.

So is there a way to add a prefix or postfix to the name of a form I want to render. Or is this a XY-Problem and my approach is totaly wrong.

or have I do it all plain by myself (I really try to avoid this)

Fescennine answered 6/2, 2015 at 22:2 Comment(0)
H
48

WTForms has a meta-field called FormField and another meta-field called FieldList. These two combined together will get you what you want:

class AddressEntryForm(FlaskForm):
    name = StringField()

class AddressesForm(FlaskForm):
    """A form for one or more addresses"""
    addresses = FieldList(FormField(AddressEntryForm), min_entries=1)

To create entries in the AddressesForm, simply use a list of dictionaries:

user_addresses = [{"name": "First Address"},
                  {"name": "Second Address"}]
form = AddressesForm(addresses=user_addresses)
return render_template("edit.html", form=form)

Then, in your template, simply loop over the sub-forms:

{% from 'your_form_template.jinja' import forms %}
{% for address_entry_form in form.addresses %}
    {{ address_entry_form.hidden_tag() }}
    {# Flask-WTF needs `hidden_tag()` so CSRF works for each form #}
    {{ forms.render_field(address_entry_form.name) }}
{% endfor %}

WTForms will automatically prefix the names and the IDs correctly, so when you post the data back you will be able to just get form.addresses.data and get back a list of dictionaries with the updated data.

Hereinafter answered 7/2, 2015 at 3:22 Comment(9)
Thx, it's working. Only just not with a form element named name. With some try and error address_entry_form.name would always by the prefix of the fieldname/idFescennine
oh and with type the same problemFescennine
Is there a way to adapt this so that I could add Fields via the templates? I would like to allow a user to click a + button and have another field added to the FieldList.Cloudberry
Yes - simply have the click post back to your endpoint and when that particular post request is received just add another entry to your data list (in the example above, it's user_addresses).Hereinafter
But how do I do that without resetting all my form fields. To explain in simple problem. If user selects radio button with number 1 or 2 or 3, the form seamlessly generates the new the correct number of form fields for the user without losing any previous form info. If I post back to my form view route in flask, it resets my form by does add the new field. I do the post through an ajax call to a special view route whose function is to redirect to the route with my form. In a sense I just want to rerender my form based in user actions from the front end.Sivia
@Sivia - ask a separate question - the margin is too narrow to contain an answer ;-)Hereinafter
@Sivia does your question answered somewhere ?Gill
To work with CSRF tokens, be sure to add hidden_tag() to both forms. So in this example, {{ form.hidden_tag() }} and {{ address_entry_form.hidden_tag() }} are needed.Tonjatonjes
Note that Form is deprecated (use FlaskForm). I also think the render_field method no longer works. Just call address_entry_form.name insteadJumble
J
6

Sean Vieira's answer works great for StringFields (previously TextField), but things get a little trickier for dynamic SelectFields. For anyone interesting in how to implement dynamic SelectFields with wtforms, see https://mcmap.net/q/505814/-wtform-fieldlist-with-selectfield-how-do-i-render

Jumble answered 18/8, 2019 at 23:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.