user2154587
user2154587

Reputation: 63

WTForms in Flask: errors in populate FieldList with nested FormField

I spent all yesterday on this issue. Saw Q Nested WTForms FieldList results in HTML in fields and some others.

I've got a MembersForm composed of a FieldList of a MemberForm FormField which, in turn, has an AddressForm FormField. Say:

class AddressForm(Form):
    """Form to collect addresses from GMaps API `address_components` field."""
    formatted_address = StringField(
        label="indirizzo visualizzato", validators=[DataRequired()]
    )
    administrative_area_level_1 = StringField(
        label="regione", validators=[DataRequired()]
    )
    administrative_area_level_2 = StringField(
        label="provincia", validators=[DataRequired()]
    )

class MemberForm(Form):
    """Form for user data collection."""
    name = StringField()
    address = FormField(AddressForm)

class MembersForm(FlaskForm):
    """List of MemberForm."""
    members = FieldList(FormField(MemberForm))

I need to populate MembersForm with some initial partial data, namely the nested AddressForm.

Then, in my Flask app route:

@app.route('/members')
def members():
    form = MembersForm()
    # If this is the first loading of the page, fill in some data from session:
    if request.method == 'GET' and session.get('places', False):
        for p in [ place for place in session['places'] if place.get('is_valid', False) ]:
            address = p['address']
            address.update({
                'category': 'pod',
                'formatted_address': p['title']
            })
            form.members.append_entry(MemberForm(data={
                'place_id': p['id'],
                'address': address
            }))
        form.validate()

    return render_template(
        'members.jinja', form=form)

This approach does not work because:

  1. the rendered HTML fail to get correct IDs and names (btw label's for attributes are working...):
<label for="members-1-address-street_number">numero civico</label>
<input id="address-street_number" name="address-street_number" type="text" value="52">
  1. I need to resort to ugly workarounds to get raw data from even the simplest fields in Jinja templates (i.e. I was supposed to use member.name.data):
{% for member in form.members %}
  <!-- Some ugly workaround ↓ -->
  <li id="{{ member.data.name.data }}">
    <span>{{ member.data.address.formatted_address.data }}</span>

    <!-- This works as expected ↓ -->
    {{ member.category.label(class="form-label") }} 
    {{ member.category(class="form-select") }}
    <span class="form-text">{{ member.category.description }}</span>

    <!-- AddressForm part is pretty ugly and strange: without using data attribute
      WTForms render the HTML inside the input value. -->
    {% for field in [ "administrative_area_level_1", "administrative_area_level_2" ] %}

      {{ member.address[field].label() }}
      {{ member.address[field].data(title=member.address[field].description) }}

    {% endfor %}

See cited SO question for this solution. But I cannot use their enumerate() solution to make form works.

Previously I attempted to load data via werkzeug.datastructures.MultiDict, but I do not understand how. EDIT: I also unsuccesfully tried to first populate AddressForm, then feed MemberForm, then append it to MembersForm.

By the way, I was wondering if in those cases WTForms worth the work. Actually, if I could have form inputs with the same name and browsers preserve order while POSTing it seems a bit overwhelming. What do you think?

Thanks in advance.

EDIT: I add some data to play with:

[
    {
        'address': {
            'administrative_area_level_1': 'Veneto', 
            'administrative_area_level_2': 'Città Metropolitana di Venezia', 
            'administrative_area_level_3': 'Pianiga', 
            'locality': 'Pianiga', 
            'postal_code': '30030', 
            'prov': 'VE', 
            'route': 'Via G. Puccini', 
            'street_number': ''
        }, 
        'id': 'ChIJhX9gFYzPfkcR3kaULLuW6SQ', 
        'is_main': True, 
        'is_valid': True, 
        'location': {'lat': 45.4569727, 'lng': 11.995981599999999}, 
        'title': 'Via G. Puccini, 30030 Pianiga VE'
    }
]

Upvotes: 0

Views: 60

Answers (1)

user2154587
user2154587

Reputation: 63

Sorry guys, my fault.

Thanks to this SO answer Filling WTForms FormField FieldList with data results in HTML in fields I found out that feeding initial data as a python object works:

address = p['address']
address.update({
    'category': 'pod',
    'formatted_address': p['title']
})
form.members.append_entry(data={
    'place_id': p['id'],
    'address': address
})

Or with a MultiDict:

address = p['address']
address.update({
    'category': 'pod',
    'formatted_address': p['title']
})
form.members.append_entry(MultiDict({
    'place_id': p['id'],
    'address': address
}))

Upvotes: 0

Related Questions