Flask Validation Error 'NoneType is not iterable' from RadioField in FormList of FormFields?

705 Views Asked by At

I'm dynamically populating a form in Flask using a FormList of FormFields but am at a loss with an error from the validation function. On this form, the user provides an entry name and then for each game queried from a database table, picks one from a RadioField and ranks their confidence in their pick 1-N. I've tested the validation with a test that was hard-coded, and I've gotten the dynamic field generation below to work, but when I try to validate the below it fails when iterating through games-#-pick with 'NoneType' is not iterable.

I believe this is because the loop keeps going beyond my bounds. In my test I only have three entries in games, so idx is 0,1,2 and I should see three PickForms generated. When validate fails I see self.name is games-3-pick, so validate is checking one more FormField in my FormList than I though should have been generated.

How do I validate this? Is it an issue with the FormField validation, or how I'm dynamically appending_entry() to the FormList?

@bp.route('/pick', methods=['GET', 'POST'])
def pick():
    games = Game.query.order_by(Game.game_date.asc()).all()
    form = EntryForm()
    for idx, g in enumerate(games):
        form.games.append_entry()
        form.games[idx].pick.label = g.bowl_name
        form.games[idx].pick.choices = [('away', g.away), ('home', g.home)]

    if form.validate_on_submit():
       ...database writes and redirect if validated...

class PickForm(FlaskForm):
    pick =  RadioField(validators=[DataRequired()])
    weight = IntegerField('Weight', validators=[DataRequired()])

class EntryForm(FlaskForm):
    name = StringField('Entry Name', validators=[DataRequired()])
    games = FieldList(FormField(PickForm))
    submit = SubmitField('Submit')

    def validate(self):
        if not super(EntryForm, self).validate():
            return False
        result = True
        seen = set()
        for pick in self.games:
            if (pick.weight.data in seen and
                    pick.weight.data <= len(self.games)):
                pick.errors.append('Please rank reach pick from 1 to {} with no repeats.'.format(len(self.games)))
                result = False
            else:
                seen.add(pick.weight.data)
        return result
2

There are 2 best solutions below

0
washer On BEST ANSWER

The issue ended up being I was appending entries onto form.games on both the GET and POST requests, resulting in a list twice the size I wanted with the top half not populated. A little more logic to check the current length of form.games before appending an entry got around this. This may not be the most elegant possible way of dynamically adding these fields, but it works for now and is a lot better than the previous way I was doing it!

0
8oh8 On

I suspect you are not accessing form.games correctly. I think you need to use: form.games.entries

As explained in docs: https://wtforms.readthedocs.io/en/latest/fields.html#field-enclosures

Sorry about crappy formatting. I am on cellphone but still want to get points.