I have a list of Prediction
models. I want to bind them to a form and allow the use to post back. How can I structure my form so the post associates a Home/Away score with a Prediction
model’s id
field for each item I bind to the form?
view
@app.route('/predictor/',methods=['GET','POST']) @login_required def predictions(): user_id = g.user.id prediction= # retrieve prediction if request.method == 'POST': if form.validate() == False: flash('A score is missing, please fill in all predictions') render_template('predictor.html', prediction=prediction, form=form) else: for pred in prediction: # store my prediction flash('Prediction added') return redirect(url_for("predictions")) # display current predictions elif request.method == 'GET': return render_template('predictor.html', prediction=prediction, form=form)
form
class PredictionForm(WTForm): id = fields.IntegerField(validators=[validators.required()], widget=HiddenInput()) home_score = fields.TextField(validators=[validators.required()]) away_score = fields.TextField(validators=[validators.required()])
template
<form action="" method="post"> {{form.hidden_tag()}} <table> {% for pred in prediction %} <tr> <td>{{pred.id}}</td> <td>{{form.home_score(size=1)}}</td> <td>{{form.away_score(size=1)}}</td> </tr> {% endfor %} </table> <p><input type="submit" value="Submit Predictions"></p> </form>
I am unable to get my data to bind correctly on POST
. The required validators continually fail because the post data is missing all the Required fields.
Advertisement
Answer
You need a subform that will bind to the items in a list of predictions:
The form you have described will only allow you to submit a single prediction. There seems to be a discrepancy because you bind an iterable of predictions and it would appear that you want a home and away prediction for each. In fact as it stands it will never post back an id
field. This will always cause you to fail form validation. I think what you want is a list of subforms. Like so:
# Flask's form inherits from wtforms.ext.SecureForm by default # this is the WTForm base form. from wtforms import Form as WTForm # Never render this form publicly because it won't have a csrf_token class PredictionForm(WTForm): id = fields.IntegerField(validators=[validators.required()], widget=HiddenInput()) home_score = fields.TextField(validators=[validators.required()]) away_score = fields.TextField(validators=[validators.required()]) class PredictionListForm(Form): predictions = FieldList(FormField(PredictionForm))
Your view will need to return something along the lines of:
predictions = # get your iterable of predictions from the database from werkzeug.datastructures import MultiDict data = {'predictions': predictions} form = PredictionListForm(data=MultiDict(data)) return render_template('predictor.html', form=form)
Your form will need to change to something more like this:
<form action='my-action' method='post'> {{ form.hidden_tag() }} {{ form.predictions() }} </form>
Now this will print a <ul>
with an <li>
per item because thats what FieldList does. I’ll leave it up to you to style it and get it into a tabular form. It might be a bit tricky but its not impossible.
On POST a you will get a formdata dictionary with a home and away score for each prediction’s id
. You can then bind these predictions back into your SQLAlchemy model.
[{'id': 1, 'home': 7, 'away': 2}, {'id': 2, 'home': 3, 'away': 12}]