How to redirect while keeping form data using Flask and WTForms?
Asked Answered
C

1

7

Let's have a page with a registration form on it. It's in section #registration. If user submits invalid data, the page should return him back to the #registration section and display to which fields were submitted invalid values.

I tried to render template and make response and redirect to it but I'm getting a TypeError:

  File ".../app/routes.py", line 28, in index
    return redirect(url_for('.index', form=form, _anchor='registration'), 302, response)
  File ".../python3.7/site-packages/werkzeug/utils.py", line 507, in redirect
    mimetype="text/html",
TypeError: __call__() got an unexpected keyword argument 'mimetype'

The function looks like this:

@app.route('/', methods=['GET', 'POST'])
def index():
    form = RegisterForm()
    if form.validate_on_submit():
        # everithing OK
        return redirect(url_for('.index', _anchor='registration'))

    # If form was submitted but contained invalid information
    if form.is_submitted():
        response = make_response(render_template('index.html', form=form))
        return redirect(url_for('.index', _anchor='registration'), 302, response)

    return render_template('index.html', form=form)
Collegium answered 5/7, 2019 at 14:15 Comment(0)
G
8

You can't send content with a redirect response, you can only say "go to this url". Also flask accepts a Response class, not an instance as a parameter for redirect.

To solve your problem, you need to use a session (or simply flashing) to preserve state across requests.

Here's a prototype:

from flask import Flask, render_template_string, request, session, redirect
from werkzeug import MultiDict

from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField
from wtforms.validators import AnyOf

app = Flask(__name__)
app.secret_key = 'secret'

class MyForm(FlaskForm):
    name = StringField('name', validators=[AnyOf(['secretname'])])

@app.route('/', methods=['POST', 'GET'])
def form_page():
    form = MyForm()
    html = '''
    {% for error in form.name.errors %} <span>{{ error }}</span> {% endfor %}
    <form method="POST" action="/">
        {{ form.csrf_token }}
        {{ form.name.label }} {{ form.name(size=20) }}
        <input type="submit" value="Go">
    </form>
    '''
    
    if request.method == 'GET':
        formdata = session.get('formdata', None)
        if formdata:
            form = MyForm(MultiDict(formdata))
            form.validate()
            session.pop('formdata')
        return render_template_string(html, form=form)
    
    if form.validate_on_submit():
        # use the form somehow
        # ...
        return redirect('/#registered')

    if form.is_submitted() and not form.validate():
        session['formdata'] = request.form
        return redirect('/#invalid')

if __name__ == "__main__":
    app.run()

When you run the server, you get:
enter image description here

After you submit an invalid form, you get redirected to /#invalid and form is populated as you'd expect:

invalid submission

Gneiss answered 5/7, 2019 at 14:21 Comment(5)
If I remove it, the page will be displayed without the being templated. (I'll lose the form object)Collegium
I'm using Flask-WTForms. The form contains its own error messages from validators. I'd like to use those messages.Collegium
Save the form data into session with flash, then recreate the form back using those values by supplying formdata argument like PhotoForm(CombinedMultiDict((request.files, request.form))) as explained here flask-wtf.readthedocs.io/en/stable/…Gneiss
Yes, now it redirects correctly. However, if you add template for displaying messages they're not displayed ``` {% for error in form.name.errors %} <span>{{ error }}</span> {% endfor %} ```Collegium
Adding a form.validate() after recreating it fixes the issue, I've edited my answerGneiss

© 2022 - 2024 — McMap. All rights reserved.