WTForms double POST submit prevention on refresh flask-wtf - python

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?

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?

Related

How to provide a way to edit a text on my flask application from front end?

I'm creating a flask app, I want to provide a way to edit the text on my personal website once I have logged in. I'm currently using MySQL as my database. To be clear I want the editing do be done from front end, is there any way to accomplish this?
You can use WTForm in flask, pass that edited data to the database on form submit.
Flask-WTF provides your Flask application integration with WTForms. For example:
`
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
class MyForm(FlaskForm):
name = StringField('name', validators=[DataRequired()])
`
Note
From version 0.9.0, Flask-WTF will not import anything from wtforms, you need to import fields from wtforms.
In addition, a CSRF token hidden field is created automatically. You can render this in your template:
`
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>
If your form has multiple hidden fields, you can render them in one block using hidden_tag().
<form method="POST" action="/">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>
Validating Forms Validating the request in your view handlers:
#app.route('/submit', methods=('GET', 'POST'))
def submit():
form = MyForm()
if form.validate_on_submit():
#you can write your db logic here.
return redirect('/success')
return render_template('submit.html', form=form)
`
Note that you don't have to pass request.form to Flask-WTF; it will load automatically. And the convenience validate_on_submit will check if it is a POST request and if it is valid.

Django - render only partial / change context / change form after POST without refresh

I need some help with django
I have kind of landing page with form, which need to have 3 different states.
Flow looks like this: enter email -> check database for user with that email -> render login or register form, still in same template.
Code looks like this:
.html
// lots of code
<form method="POST" id="form">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="send">
</form>
// lots of code
and in views.py i have render method with context
return render(
request,
'apply.html',
context,
)
where in context i placed simple standard django form like:
class ApplyForm(forms.Form):
email = forms.CharField()
phone = forms.CharField()
Is there and way to change ApplyForm to LoginForm or RegisterForm after sending POST from first one, without refreshing whole page?
I though about injecting all three forms to context, sending some ajax request, and then differentiate which form should be displayed, but I'm not sure how could it be done...
thanks in advance!

View function is not being called after submitting form in django

I have made a simple form inside a html file whose path is www.site.com/posts/5. Whenever the form is submitted, it redirects back to the same page i.e www.site.com/posts/5 displaying a message given by user in the form.
However, whenever the form is submitted it doesn't call the foobar view.
The urls.py, views.py and html files are as follows:-
urls.py
urlpatterns = [
path('posts/<int:foo>',user_views.display, name="display",
path('posts/<int:foo>',user_views.foobar, name="makefoo"),
]
views.py
def foobar(request, foo):
#do something
html file
<form name="fooform" action= "{% url 'makefoo' 5 %}" method = "post">
{% csrf_token %}
<input type="text" name="FOO_BODY" maxlength="300" required>
<input type="submit" value="comment">
<input type="reset" value="clear">
</form>
Edit : user_views is just from user import views as user_views
You can not attach two views to the same URL. The {% url ... %} template tag, only generates a URL for that path. But if there is a "url clash", then it is possible that the requests ends up in the other view.
You thus should define another URL, or encode the post logic in the display view. In case of a POST request, you can thus first take the necessary steps, and then for example return a redirect to the page, such that we can again render the page:
def display(request, foo):
if request.method == 'POST':
# do something
return redirect(display, foo=foo)
#do something else (original code)
return HttpResponse(..)
This is the famous Post/Redirect/Get web development design pattern [wiki]. This is usually better than returning a HTTP response directly in the POST, since if the user performs a refresh, the POST will be performed a second time.
As mentioned in the comment by #williem, you have two path() defined in the urls.py.
Always First matching route will be picked up from the url route table. So whenever r^'posts/' is requested it will call the display() from the user_views, so it will never go to foobar(). Either remove the route with display() or change the sequence. Also, I assume you imported the user_views.
Reference:
https://docs.djangoproject.com/en/2.1/topics/http/urls/

Passing Variable from HTML to Python/Flask

Let me try this again. I want to enter a variable into my HTML form to submit, so far from reading the link here How to display a variable in HTML I've tried the following code, which is inside of main.html
<form>
Asset Tag:<br>
<input type="text" name="Asset Tag"><br>
<input type="submit" value="Submit">
<form action="{{ asset_tag }}" method="get">
</form>
I then have a python script that goes like this,
from flask import Flask, render_template
app = Flask('server')
#app.route('/py')
def server():
return render_template('main.html')
#API URL
JSS_API = 'https://apiresource.com'
#Pre-Defined username and password
username = 'username'
password = 'password'
#Ask User for the Asset tag
asset_tag = {{ }}
After the asset tag is entered it just searches through a JSON file for match, the rest doesn't matter so much so I didn't include the next piece of the script.
So Flask renders my HTML just fine and I can submit a value but it's not being passed back to the script, which makes sense as I'm doing the opposite of the link I provided, but I just can't not think of how it's done. Any suggestions?
You have a few issues that I've outlined below. Overall though, the frontend is passing the variable to the backend, it's just that the variables are only accessible via the request object from within the route to which the data is passed.
I am not sure why you have a <form> nested within a <form> here, but you'll want to remove the inner one since it's not doing anything.
You want to setup your form to POST the data to your backend when submitted. If you don't specify an action, then it will POST to the same page the same page that you're currently viewing.
<form method="POST">
Asset Tag:<br>
<input type="text" name="tag"><br>
<input type="submit" value="Submit">
</form>
You need to setup your route to accept a POST request so that it can receive data from the form on your page. See here for more information on HTTP methods.
#app.route('/py', methods=['GET', 'POST'])
Inside of your route, you'll want to check whether it was a GET request (and load a normal page) or whether it was a POST (form data was sent so we should use it)
from flask import request
#app.route('/py', methods=['GET', 'POST'])
def server():
if request.method == 'POST':
# Then get the data from the form
tag = request.form['tag']
# Get the username/password associated with this tag
user, password = tag_lookup(tag)
# Generate just a boring response
return 'The credentials for %s are %s and %s' % (tag, user, password)
# Or you could have a custom template for displaying the info
# return render_template('asset_information.html',
# username=user,
# password=password)
# Otherwise this was a normal GET request
else:
return render_template('main.html')

How to get a “nice” URL that can be bookmarked in Flask-WTF?

I try to learn Flask and I can't find an answer to the question: Is there a way to get a “clean” URL, that can be bookmarked, if I use GET as form method with the Flask-WTF plugin?
If I use in a template the method POST:
<form method="POST" action="">
The URL in the browser will not alter, in Flask's debug mode it would be:
http://127.0.0.1:5000/
If I submit the form.
If I use the method GET, the URL would look like this:
http://127.0.0.1:5000/?name=test&submit=Submit&csrf_token=1453393786%23%23a327427d
But I would like to use the Flask-WTF plugin to make forms and get back a nice, bookmarkable URL in the browser like this:
http://127.0.0.1:5000/?name=test
Is that possible?
What I try to achieve is something like this in PHP:
<?php
if( $_GET["name"] ) {
echo "Hello, ". $_GET['name']. "!";
exit();
}
?>
<html>
<body>
<form action = "<?php $_PHP_SELF ?>" method = "GET">
Name: <input type = "text" name = "name" />
<input type = "submit" />
</form>
</body>
</html>
After submitting, I get this URL:
http://127.0.0.1/test.php?name=test
I can copy this link, send it to someone else, he or she can open it with a browser and will get the same result. It is simply done, by using the method GET in the form. Look at this:
http://www.utrace.de/?query=8.8.8.8
With Flask I cloud do this:
http://127.0.0.1/query/8.8.8.8
But what, if I would use more than one parameter? In PHP it would look like this:
http://127.0.0.1/?para1=8.8.8.8&para2=US
I've tried it, using this code (lent from Miguel Grinberg):
Program:
from flask import Flask, render_template
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required, Length
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top secret!'
class NameForm(Form):
name = StringField('What is your name?', validators=[Required(),
Length(1, 16)])
submit = SubmitField('Submit')
#app.route('/', methods=['GET', 'POST'])
def index():
name = None
form = NameForm()
if form.validate_on_submit():
name = form.name.data
form.name.data = ''
return render_template('index.html', form=form, name=name)
if __name__ == '__main__':
app.run(debug=True)
Template:
<form method="GET" action="">
{{ form.name.label }} {{ form.name(size=16) }}
{% for error in form.name.errors %}
{{ error }}
{% endfor %}
<br>
{{ form.submit() }}
{{ form.hidden_tag() }}
</form>
{% if name %}
<h1>Hello, {{ name }}!</h1>
{% endif %}
I would suggest factoring this out into its own view. You should use CSRF for forms, and you should try to separate GET and POST interfaces.
Forcing one piece of code or function to do multiple things sometimes might seem cleaner, but what happens is you add to maintainability costs and make things less clear down the road.
How you'd do this in Flask:
#app.route('/<name>/', methods=['GET'])
Now, this does not give you the validation. You can either do this yourself, or use a library such as Marshmallow: https://marshmallow.readthedocs.org/en/latest/
In your example Marshmallow would be overkill, but it is great if you plan on expanding your API. It will allow you to take more complicated JSON blobs and validate and serialize them into Python objects.
Also, I'd look into why you're using GET over POST. POST should be used to create new data on a server, GET should be used to get that information.
EDIT (After your edits):
With Flask I cloud do this:
http://127.0.0.1/query/8.8.8.8
But what, if I would use more than one
parameter? In PHP it would look like this:
You would make your Flask view like this:
#app.route('/<name>/<another_parameter>/<another>/', methods=['GET'])
def some_route_name(name, another_parameter, another):
However, if you want to accomplish what you want with ONE form, you would have to turn CSRF off. Your PHP example is not using CSRF.
I suggest this:
1) Creating a new view, as I originally suggested
2) On your view with the form, have it POST to itself, then redirect to the new view, like the below:
if form.validate_on_submit():
return redirect(url_for('name_of_new_view', name=form.name.data))
3) In this new view, put your form, but have that form POST to your OLD view. Make sure you're including the CSRF token when you POST! Like here:
{{ form.csrf_token }}

Categories