Flask-Cache doesn't distinguish between GET and POST - python

I have a Flask route structured like so:
#app.route('/rootpath1/<path:path>')
#app.route('/rootpath2/<path:path>', methods=['GET', 'POST'])
#cache.cached()
def rootpath():
...
POSTs to '/rootpath2/' for a given page are typically retrieved from cache (when a cached value is present), which is usually the last GET request.
For example, a user would visit '/rootpath2/myform', fill out and then submit the form. The form would post to '/rootpath2/myform' and the user would be returned to the same URI with a message indicating that the form submission was successful (or that errors occurred, if they did).
The problem here is that a GET always precedes the POST, and the POST always triggers a cache hit and returns that value.
Is there a way for Flask-Cache to distinguish between GETs and POSTs and handle them according (only caching GETs)?

Yes. The cache decorator provides an unless kwarg that accepts a callable. Return True from the callable to cancel caching. Test it out with the following:
from flask import Flask, request, render_template_string
from flask.ext.cache import Cache
app = Flask('foo')
cache = Cache(app,config={'CACHE_TYPE': 'simple'})
t = '<form action="/" method="POST">{{request.form.dob}}<input name="dob" type="date" value={{request.form.dob}} /><button>Go</button></form>'
def only_cache_get(*args, **kwargs):
if request.method == 'GET':
return False
return True
#app.route('/', methods=['GET', 'POST'])
#cache.cached(timeout=100, unless=only_cache_get)
def home():
if request.method == 'GET':
print('GET is not cached')
return render_template_string(t)
if request.method == 'POST':
print('POST is not cached')
return render_template_string(t)
app.run(debug=True)

Related

How to convert POST request to GET request in flask?

I created a web application in flask that has a form and whatever text the user enters appears on the bottom along with all the previously entered messages.
I was trying to load test it using JMeter, but I'm not able to send POST request using multiple threads in JMeter so I wanted to convert the post request to GET request so that I am able to perform load tests on my application.
Currently my route looks something like this
#app.route('/blog', methods=['GET', 'POST'])
#app.route('/', methods=['GET', 'POST'])
def blog():
print
form = PostForm()
if form.validate_on_submit():
post = Post(body=form.post.data)
db.session.add(post)
db.session.commit()
return redirect(url_for('blog'))
posts = Post.query.all()
return render_template('index.html', title='Blogger', form=form,
posts=posts)
What can I do to send the parameters through the URL.
I am very new to web development and I followed the mega tutorial in flask. Is there a workaround this?
add #app.route("/<string:param>",methods['GET']) and give it default values def blog(param = "") and use it for your get method
#app.route("/<string:param>",methods['GET'])
#app.route("/blog/<string:param>",methods['GET'])
#app.route('/blog', methods=['GET', 'POST'])
#app.route('/', methods=['GET', 'POST'])
def blog(param = ""):
print
if request.method == "POST":
##your post code here
elif request.method == "GET":
## new code using 'param' here

Flask redirects to wrong view when redirecting to index

I keep running into this strange issue that I can't seem to figure out a solution for. I cannot copy and show all of my code in it's entirety here, but I will try to outline the general structure of my flask app to present my issue.
(Let's ignore all of the content in the /static folder and my helper modules)
I have 3 main views, let's call them viewA, viewB, and index:
viewA.html
viewB.html
index.html
viewA and viewB both display two forms, but with different content (i.e. viewA displays form1 & form2, and viewB also displays form1 & form2).
A simplified version of my script code is as follows:
#imports
from flask import Flask, render_template, session, redirect, url_for, request
from flask_wtf import FlaskForm
#etc. etc.
app = Flask(__name__)
app.config['SECRET_KEY'] = 'blah blah blah'
manager = Manager(app)
bootstrap = Bootstrap(app)
moment = Moment(app)
class FormOne(FlaskForm):
sample_field = StringField('Sample Field:')
class FormTwo(FlaskForm):
other_field = StringField('Other Field:', validators=[Required()])
submit = SubmitField('Submit')
class UploadToA(FlaskForm):
content= StringField('Content to send to view A:', validators=[Required()])
submit = SubmitField('Submit')
class UploadToB(FlaskForm):
content= StringField('Content to send to view A:', validators=[Required()])
submit = SubmitField('Submit')
#app.route('/ViewA', methods=['GET', 'POST'])
def view_a():
"""
A lot of data manipulation
"""
form1 = FormOne()
form2 = FormTwo()
if request.method == 'GET':
"""
populate forms with content
"""
if request.method == 'POST':
if form2.validate_on_submit();
"""
clear session variables
"""
return redirect(url_for('index'), code=302)
return render_template('viewA.html', form1=form1, form2=form2)
#app.route('/ViewB', methods=['GET', 'POST'])
def view_b():
"""
A lot of data manipulation
"""
form1 = FormOne()
form2 = FormTwo()
if request.method == 'GET':
"""
populate forms with content
"""
if request.method == 'POST':
if form2.validate_on_submit();
"""
clear session variables
"""
return redirect(url_for('index'), code=302)
return render_template('viewB.html', form1=form1, form2=form2)
#app.route('/', methods=['GET', 'POST'])
def index():
"""
Some data manipulation
"""
formA = UploadToA()
formB = UploadToB()
if formA.validate_on_submit()':
"""
pull content from form A
create some session variables
"""
return redirect(url_for('view_a'))
if formB.validate_on_submit()':
"""
pull content from form B
create some session variables
"""
return redirect(url_for('view_b'))
return render_template('index.html', formA=formA, formB=formB)
if __name__ == '__main__':
manager.run()
Now the issue at hand I am having here is that for some strange reason when I'm in 'viewA.html' and I submit my form, I SHOULD be redirected back to 'index.html' but for some strange reason it redirects me to 'viewB.html'. Furthermore, the opposite also holds true: when i'm in 'viewB.html' and I submit my form, I SHOULD also be redirected back to 'index.html' but it redirects me to 'viewA.html'. Yet, if I am in either viewA or viewB, I have no issues of going back to the index view if I manually enter the url into my browser.
Any ideas as to why I might be running into this issue?
Thanks in advance :)
I have finally figured out the source of my problem. It turns out that in my 'viewA.html' template file, I had the following in my < form > tag:
<form class="form form-horizontal" method="post" role="form" action="{{url_for('index')}}">
And the problem all lies in that last part:
action="{{url_for('index')}}"
As a result, everytime I would submit form2 in viewA.html it would create a post request for my index page rather than a post request for the viewA.html page (which caused a redirect to the wrong view). Thus, by simply removing the action attribute (action="{{url_for('index')}}"), I was able to solve my problem!
Since the full code isn't here, I can't confirm this for sure, but what I think is happening is this:
You open form A
You submit form A
It sends a redirect to /index
It sends a redirect to /FormB
if formB.validate_on_submit():
return redirect(url_for('view_b'))
This is probably sending a redirect to View B. Try changing that last line to something like return something_else and seeing if it sends that after submitting form A.

How to rewrite this Flask view function to follow the post/redirect/get pattern?

I have a small log browser. It retrieves and displays a list of previously logged records depending on user's input. It does not update anything.
The code is very simple and is working fine. This is a simplified version:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
return render_template('log.html', form=form, log=getlog(*args))
return render_template('log.html', form=form)
However it does not follow the post/redirect/get pattern and I want to fix this.
Where should I store the posted data (i.e. the args) between post and get? What is the standard or recommended approach? Should I set a cookie? Should I use flask.session object, create a cache there? Could you please point me in the right direction? Most of the time I'm writing backends...
UPDATE:
I'm posting the resulting code.
#app.route('/log', methods=['POST'])
def log_post():
form = LogForm()
if form.validate_on_submit():
session['logformdata'] = form.data
return redirect(url_for('log'))
# either flash errors here or display them in the template
return render_template('log.html', form=form)
#app.route('/log', methods=['GET'])
def log():
try:
formdata = session.pop('logformdata')
except KeyError:
return render_template('log.html', form=LogForm())
args = parse(formdata)
log = getlog(args)
return render_template('log.html', form=LogForm(data=formdata), log=log)
So, ultimately the post/redirect/get pattern protects against submitting form data more than once. Since your POST here is not actually making any database changes the approach you're using seems fine. Typically in the pattern the POST makes a change to underlying data structure (e.g. UPDATE/INSERT/DELETE), then on the redirect you query the updated data (SELECT) so typically you don't need to "store" anything in between the redirect and get.
With all the being said my approach for this would be to use the Flask session object, which is a cookie that Flask manages for you. You could do something like this:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
session['log'] = getlog(*args)
return redirect(url_for('log'))
saved = session.pop('log', None)
return render_template('log.html', form=form, log=saved)
Also, to use session, you must have a secret_key set as part of you application configuration.
Flask Session API
UPDATE 1/9/16
Per ThiefMaster's comment, re-arranged the order of logic here to allow use of WTForms validation methods for invalid form submissions so invalid form submissions are not lost.
The common way to do P/R/G in Flask is this:
#app.route('/log', methods=('GET', 'POST'))
def log():
form = LogForm()
if form.validate_on_submit():
# process the form data
# you can flash() a message here or add something to the session
return redirect(url_for('log'))
# this code is reached when the form was not submitted or failed to validate
# if you add something to the session in case of successful submission, try
# popping it here and pass it to the template
return render_template('log.html', form=form)
By staying on the POSTed page in case the form failed to validate WTForms prefills the fields with the data entered by the user and you can show the errors of each field during form rendering (usually people write some Jinja macros to render a WTForm easily)

How to pass a "WTF object" in Flask

I am using flask to develop a website and now i encountered a problem.
I am thinking whether I can pass a "WTF form" object in flask.
Like,
#app.route('/')
#app.route('/index')
#login_required
def index():
user = flask.g.user
form = PostForm()
return flask.render_template("index.html",
title="Home",
user=user,
form = form)
This form, an instance of PostForm, actually will be processed by the following code:
#app.route('/note/<int:id>', methods=['POST'])
def note(id):
form = ?(how to get this form?)?
if form.validate_on_submit():
print id
content = form.body.data
currentTime = time.strftime('%Y-%m-%d', time.localtime(time.time()) )
user_id = id
return flask.redirect(flask.url_for('login'))
return flask.redirect( flask.request.args.get('next') or
flask.url_for('index') )
In the template, I set the action to be "/note/1", so it will forward to this address. But the question, how can I get the form created in the function index?
I have tried to use flask.g (Obviously, it does not work because it's another request). And I also tried to use global variable. It failed, either.
Could anyone give me a solution or any advice?
Thank you in advance!
You simply need to construct a new version of PostForm in your note route and use the posted data in request.form:
from flask import request
#app.route('/note/<int:id>', methods=['POST'])
def note(id):
form = PostForm(request.form)
# or, if you are using Flask-WTF
# you can do
# form = PostForm()
# and Flask-WTF will automatically pull from request.form

Flask login_required + next url params

I have a protected view in my app which just accepts POST requests.
#app.route("/booking", methods=("POST", ))
#login_required
def booking():
arg1 = request.form.get("arg1")
arg2 = request.form.get("arg2")
When an unauthorized user tries to access this view, I want them to
login and then be redirected here.
Right now, my login view looks like this:
#app.route("/login", methods=("GET", "POST"))
#login_required
def login():
do_login()
return redirect(request.args.get('next') or url_for('home'))
So what ends up happening is a POST request to /booking (which is the
"next" parameter) and I get a NOT ALLOWED error.
The problem is that login() makes a GET request to booking(). I can
get around that, but I am not sure how to retrieve the original POST
form arguments from /booking? Any ideas to get round that?
I would solve this by pulling the data and putting it in the session. You can remove the #login_required decorator and check this in the function using current_user.is_authorized. See Flask Sessions and Flask Login.
Something like this might work for you, I didn't test it:
from flask import session
from flask_login import current_user
#app.route("/booking", methods=("POST", ))
def booking():
if not 'arg1' in session.keys() and not 'arg2' in session.keys():
session['arg1'] = request.form.get("arg1")
session['arg2'] = request.form.get("arg2")
# Now the data will persist in the session
if current_user.is_authorized:
# Do what you need...
else:
# Redirect to login, session will persist
Why would you only use POST in the booking view ? You are probably rendering a form which should also allow GET.
#app.route("/booking", methods=['GET','POST'])
#login_required
def booking():
# render the form. something like
form = BookingForm()
# Check if POST
if request.method == 'POST':
# process the form now and do whatever you need.
return redirect(url_for('index'))
# code below will run if not POST. You should render the template here
return render_templte('booking.html')

Categories

Resources