Python Control Flow (Identifying different POST methods in same app route - Flask) - python

I'm in Flask. I have two forms on one html page, both with post methods to the same route. I have read that it is possible to use control flow to identify which form is being posted, under the same app route. I seem to be battling with the logic here. So far I have the below:
#app.route('/page-with-2-forms, methods=['GET', 'POST'])
def function():
if request.method == "POST":
if 'form_1_element_name' in request.form:
#form1
#request args from this form and do some stuff
#form2
else:
#request args from this form and do some stuff
return render_template('page.html')
The problem here is that when attempting to post #form1 it still attempts to execute the block for #form2 (the else statement). However, when posting #form2 everything works fine. As I said I seem to be missing some logic here.
The element name tag is posted under the html tag. <form id="form_1_element_name" name="form_1_element_name" action="page-with-2-forms" method="POST">
Please note that I know the conventional practice is to use WTForms, but I do not want to use it here
Any help would be massively appreciated

Will post the solution just for future reference and people having similar issues. In the original post, the if statement references the form name, as stated in the html snippet
<form id="form_1_element_name" name="form_1_element_name" action="page-with-2-forms" method="POST">
This doesn't work. Instead, the if statement should reference an element inside the actual form. In the below, I referenced the submit button, which is ID and named to a unique name, such as "editFormSubmitButton".
#app.route('/page-with-2-forms, methods=['GET', 'POST'])
def function():
if request.method == "POST":
if 'editFormSubmitButton' in request.form:
#form1
#request args from this form and do some stuff
#form2
else:
#request args from this form and do some stuff
return render_template('page.html')
This is a great alternative to WTForms
Thanks to #go2nirvana for the bit of help

Related

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)

Routing fade modal in flask

I am having an issue with my fading modal routing in flask. My user login opens a modal and i m trying to implement the POST feature from the modal in flask.
I thought of implementing under index and search for the form name form the the post like below.
#app.route('/')
#app.route('/index')
def index():
if request.form.get('login', None) == 'submit' :
return 'Yeah hooo'
return render_template('index.html')
However, when i execute the code above, i get
Method Not Allowed
on /index. My other worries is that my Login form is in the template and can therefore be call from any routes. Since template is not a route. Please is there any way i can achieve this in flask ? Or do i have to take the login in to a seperate html file instead of the template ?
You should explicitly add POST to the list of methods that can be processed
#app.route('/', methods=['GET', 'POST'])
See here for more information
As per the second question, it's ok as long as your login form makes POST request to the same route ('/' in your case).

How do I make a POST request to flask?

I'm new to Flask and I'm trying to find a way to invoke the elif statement in the code below, without having to manually type in the url when I run my app. In other words, I'd like to be able to provide a url in one of my templates that will make a POST request for question(title). Can anyone give me insight?
#application.route('/question/<title>', methods=['GET', 'POST'])
def question(title):
if request.method == 'GET':
question = r.get(title+':question')
return render_template('AnswerQuestion.html',
question = question)
elif request.method == 'POST':
submittedAnswer = request.form['submittedAnswer'];
answer=r.get(title+':answer')
if submittedAnswer == answer:
return render_template('Correct.html');
else:
return render_template('Incorrect.html',
answer = answer,
submittedAnswer = submittedAnswer);
Looks like in thePOST request, you are getting the contents of a form. You can try to create a form whose action="/question/some_title" and method="post". So on submit this will be handled on theelif part of your flask code.
Or you can try sending am ajax request through JavaScript or jQuery, with relevant data, method and URL.

Django controller to require method

I read the following page:
https://docs.djangoproject.com/en/1.2/topics/http/decorators/
Basically, I have a function in a controller that only accepts POST requests. And the documentation on that page allowed me to do that. However, i'm noticing that when the user sends a GET or PUT etc, the response is literally nothing. How can I send a generic error or a 404 page or something?
It doesn't return nothing. It returns a 405 Method Not Allowed HTTP status code. This indicates to the client that the requested method is not allowed (as the name describes).
If you're dead set on returning something else, just don't use the decorator. All it does is test if the requested method is in the allowed list of methods. Just add the following to your view code and you can do whatever you want for each condition:
if request.method in ['GET', 'POST']:
// allowed
else:
// not allowed
I can't remember if it was request.method or not and I don't have Django currently installed on any machines to double-check, but something like this could work.
#require_http_methods(["GET", "POST"])
def my_view(request):
if request.method == 'GET':
# return a 404 or something
# or
if request.method != 'POST':
# return a 404 or something
But shouldn't you be getting your generic 405 - Method not allowed return page if you've only allowed POST for eg. to a certain controller ?
Try this =) Good luck!
from django.http import HttpResponseNotAllowed
def my_view(request):
if request.method != 'POST':
return HttpResponseNotAllowed(permitted_methods=('POST',))

Categories

Resources