2

I use WTForms and Flask with Flask-WTF extension

My form looks like:

class CommentForm(Form):
    body = TextAreaField('Body', [validators.Length(min=4, max=300)])
    entity_id = HiddenField('Entity ID', [validators.required()])

Jinja2 Template:

 <form method="POST" action="{{ request.url }}#comment-question" id="comment-question">
     <div>{{ comment_form.body }} <button type="submit">Submit</button></div>
     {{ comment_form.entity_id(value=question.id) }}
     {{ comment_form.hidden_tag() }}
 </form>

Rendered form:

<form method="POST" action="http://localhost:5000/answers/1/question-0#comment-question" id="comment-question">
  <div><textarea id="body" name="body"></textarea> <button type="submit">Submit</button></div>
  <input id="entity_id" name="entity_id" type="hidden" value="1">
  <div style="display:none;"><input id="csrf_token" name="csrf_token" type="hidden" value="20120507081937##ee73cc3cfc053266fef78b48cc645cbf90e8fba6"><input id="entity_id" name="entity_id" type="hidden" value=""></div>
</form>

Is it possible to prevent the double form submit on the browser refresh button click without changing the form "action" and doing redirects?

Zelid
  • 6,905
  • 11
  • 52
  • 76

1 Answers1

4

I don't have too much experience using WTForms or Flask, but Django class-based views prevent double-posts by redirecting after a POST, so I had assumed performing a redirect is the way to go for this sort of thing.

One alternative is to generate a unique token and attach it to your form parameters (much like a CSRF token). Cache this value and check against it on form submission. A rather primitive example for Django can be found here.

Edit: Sample code

Although I would really just go with performing a redirect after a successful form submission, here's an example of generating a form token which borrows heavily from this Flask snippet on CSRF protection:

# yourapp/views/filters.py

import random
from string import ascii_letters, digits

from flask import request, session, redirect
from yourapp import app


def generate_form_token():
    """Sets a token to prevent double posts."""
    if '_form_token' not in session:
        form_token = \
            ''.join([random.choice(ascii_letters+digits) for i in range(32)])
        session['_form_token'] = form_token
    return session['_form_token']


@app.before_request
def check_form_token():
    """Checks for a valid form token in POST requests."""
    if request.method == 'POST':
        token = session.pop('_form_token', None)
        if not token or token != request.form.get('_form_token'):
            redirect(request.url)


app.jinja_env.globals['form_token'] = generate_form_token

And in your template:

<!-- Again, I've never used WTForms so I'm not sure if this would change when using that app. -->
<input type='hidden' name='_form_token' value='{{ form_token() }}' />

Note that using the CSRF protection method in the snippet also accomplishes pretty much the same effect (although the above code performs a redirect, while the snippet returns a 403).

But this really begs the question--if you're performing a redirect on an invalid token, why not get rid of all this complexity and just redirect on successful form submission?

Brian Gesiak
  • 6,648
  • 4
  • 35
  • 50
  • Just wanted to add that this solution would never work with longer requests, and with session being stored inside of the cookie (default in Flask). Moving session to DB or file will help, as well as disabling submit button after request. – Boris Apr 26 '15 at 02:40