Disclaimer: Yes I know this might not be the right way to do it, but I just need a quick and easy fix and in this case I prefer the quick and dirty way.
I have a login.html like this:
<form method="POST" class="login-form" action="/login">
<input type="text" name="code">
<input type="submit" value="Submit">
</form>
I have two types of users who will visit this page. First type of users will have "code_A", second type of users will have "code_B". Depending on which code they will type into the form field they will be redirected to gallery or gallery2.
This is my routes.py:
code_A = "code_A"
code_B = "code_B"
#app.route("/login", methods=['GET', 'POST'])
def login():
if 'code_A' in session:
redirect(url_for('gallery'))
elif 'code_B' in session:
redirect(url_for('gallery2'))
elif request.method == 'POST':
code_post = request.form['code']
if code_post == code_A:
session['code_A'] = code_A
return redirect(url_for('gallery'))
elif code_post == code_B:
session['code_B'] = code_B
return redirect(url_for('gallery2'))
else:
return render_template("login.html")
else:
return render_template("login.html")
return render_template("login.html")
#app.route("/gallery")
def gallery():
if 'code_A' not in session:
return redirect(url_for('login'))
else:
return render_template('gallery.html')
#app.route("/gallery2")
def gallery2():
if 'code_B' not in session:
return redirect(url_for('login'))
else:
return render_template('gallery2.html')
I though I could simply take the input from the form and create a session based on the input. If the right code is in the session, the user should be redirected to gallery or gallery2, depending on the session. But with this code I always get redirected to login.html and no session is created. How do I pass the input from the form to the routes.py and create a session from it. I mean it cannot be that hard, or is it?
I am thankful for every suggestion, because I am loosing my mind over it. Thanks and best regards!
Related
I am running flask python along with HTML, and the problem is simple:
It gives me error 405: Method Not Allowed: The method is not allowed for the requested URL. I have looked this up and people say to include methods=['GET', 'POST'] in the page route, but I already have this.
Python code:
#app.route('/')
#app.route('/home/', methods=['GET', 'POST'])
def home():
global count
guess = request.form.get('guess')
result, valid = guess_word(guess, word, words)
print(result, valid)
try:
guesses[count-1][2] = 'p'
except:
guesses[count-1][2] = ''
if count < 6:
if valid:
guesses[count][0] = guess
guesses[count][1] = result
session['guesses'] = guesses
if valid:
count += 1
return render_template('index.html', guesses=session['guesses'])
HTML code:
<div class="container2">
<form method="post">
<input type="text" placeholder="Guess" class="text-input" name="guess">
</form>
</div>
This worked before, and I changed (what I thought was) nothing, but it suddenly stopped working. It gives me the error when I submit the text entry.
Your form has no action attribute i.e. you have not explicitly said where your data should go to when you submit your form. In the absence of an action attribute, it will assume your home page which is / and from your routing, this does not support POST.
2 possible solutions
Add the action attribute to your form
<form action ="home" method ="post">
Keep your data as is and add support for POST to your route for / i.e. change your routing code to
#app.route('/', methods=['GET', 'POST'])
#app.route('/home/', methods=['GET', 'POST'])
This view receives the ?next= argument when making a GET request but once the user makes a POST request to the same view the argument is lost. The examples I've seen don't explicitly pass on the query arguments but seem to be able to retain them and get them in the following POST request.
#blueprint.route("/login", methods=["GET", "POST"])
#logout_required()
def login():
form = LoginForm()
#1
print("NEXT:", request.args.get("next"))
if form.validate_on_submit():
u = User.query.filter_by(username=form.username.data).first()
if u and u.check_password(password=form.password.data):
if u.is_active():
#2
print("NEXT:", request.args.get("next"))
login_user(u, remember=form.remember.data)
next_url = request.args.get("next")
if not next_url or url_parse(next_url).netloc != "":
next_url = url_for("main.index")
return redirect(next_url)
else:
flash("This account has been disabled", "error")
else:
flash("Invalid password or username.", "error")
return render_template("auth/login.html", form=form)
Figured out the issue was basically because I was mixing GET request arguments with POST input submissions.
for my login url http://localhost:8000/auth/login?next=%2Fadmin
<form action="">
will send a POST request to the login view function and allow access through request.args.get()
However, the proper way to capture and pass on the GET request next argument would be through a hidden input field that holds the initial GET request's next argument, allowing it to be passed properly as part of the form in the POST request.
<form action="{{ url_for('auth.login' }}">
<input id="next" name="next" type="hidden" value="/admin">
The view function:
#blueprint.route("/login", methods=["GET", "POST"])
#logout_required()
def login():
form = LoginForm(next=request.args.get("next"))
you are missing two redirections with return redirect() after last the two flash() functions, see the code below:
#blueprint.route("/login", methods=["GET", "POST"])
#logout_required()
def login():
form = LoginForm()
#1
print("NEXT:", request.args.get("next"))
if form.validate_on_submit():
next_url = request.args.get("next") # -- HERE -- get "?next argument"
u = User.query.filter_by(username=form.username.data).first()
if u and u.check_password(password=form.password.data):
if u.is_active():
#2
print("NEXT:", request.args.get("next"))
login_user(u, remember=form.remember.data)
# next_url = request.args.get("next") # -- HERE --
if not next_url or url_parse(next_url).netloc != "":
next_url = url_for("main.index")
return redirect(next_url)
else:
flash("This account has been disabled", "error")
return redirect(url_for('auth.index')) # -- HERE --
else:
flash("Invalid password or username.", "error")
return redirect(url_for('auth.login', next=next_url)) # -- HERE --
return render_template("auth/login.html", form=form)
i assume /main is the protected route and there's currently no session, the user is not logged in yet, so when the user try to directly access
http://localhost:5000/admin
he will be automatically redirected to this url (auth.login route with an argument ?next=%2Fadmin%2F)
http://localhost:5000/auth/login?next=%2Fadmin%2F
with the default flask-login flash() message Please log in to access this page. then he could login, if the login was successful he will be logged in and redirected to the protected area using ?next argument,
but if it happens and the temptation fails, we have two options:
if Invalid password or username he will be redirected the auth.login route with ?next=%2Fadmin%2F argument for second chance.
but if This account has been disabled he will be redirected the auth.login route without ?next=%2Fadmin%2F argument and it does make sens.
and finally your don't need to worry about the value of ?next= because the user could play with it or even remove it and this block
if not next_url or url_parse(next_url).netloc != "":
next_url = url_for("main.index")
return redirect(next_url)
defaults to main.index if any.
I'm posting data to the following Flask view using an HTML form. For some reason, the server is never created. I've confirmed that the create_server method works from the interpreter. Another form I'm using to log in works. Why isn't this working?
#app.route('/add-server/', methods=['GET', 'POST'])
def add_server_view():
if request.method == 'post':
server_name = request.form['server_name']
create_server(server_name)
return redirect(url_for('index')
return render_template('add_server.html')
<form method=post>
<input name=server_name>
</form>
request.method will be in all caps and the comparison is case sensitive.
if request.method == 'POST':
This code in Werkzueg forces the method name to uppercase for consistency.
My app has a text box and a submission button. I am trying to create and redirect to dynamic URLs in my app, that are based off of what is typed in the text box. For example, the user enters in '1234', clicks submit, then is taken to 'website.com/results/1234'. The problem seems to be that the HTML for my button doesn't want to redirect the user to the new, dynamic URL. I am passing this to the HTML with Jinja.
Here is what I have.
The user starts on the home page, that is defined like this
#app.route("/home/", methods=["GET", "POST"])
def start():
return render_template("dashboard.html")
Dashboard.html has a text box and submission button (below). As you can see, the action of this button is to redirect to {{ results_page }}, where "results_page" comes from my Python function load_results (also below) and is passed to the HTML with render_template.
<div>
<form action="{{ results_page }}" class="form-inline" method="post">
<div class="form-group">
<label for="PubmedID">Pubmed ID(s)</label>
<input type="text" class="form-control" id="PubmedID" placeholder="18952863, 18269575" name="pmid" value="{{request.form.pmid}}">
</div>
<button type="submit" id= "myButton" class="btn btn-default" data-toggle="modal" data-target="#myModal">Submit</button>
</form>
</div>
The results page of my app uses the user input to look up some information and display it.
#app.route('/results/<query>', methods=["GET", "POST"])
def load_results(query):
form = pmidForm(secret_key='super secret key')
try:
if request.method == 'POST':
query = form.pmid.data #This is the user input
results_page = "website.com/results/"+query
return(query)
#do lots of stuff using the query
return render_template('results.html', form=form, results_page = results_page)
except Exception as e:
return(str(e))
If I run my app like this, the home page is fine, but when I click "Submit", the app doesn't take in the user input or do anything with it. Simply the home page refreshes.
I am new to web development, but since this code works fine if I hardcode the button to action = "website.com/results" in the HTML, AND do without the <query> in /results/<query> for the results page, I think that only a few adjustments should be needed to make my app dynamically redirect to and load pages correctly. Right now, I'm not sure what I'm missing. Please let me know where I'm going stray.
EDIT -
Now I have implemented a handle_form function that redirects to my dynamic URL. This function properly redirects but then I get a 404 error.
#app.route('/form/', methods=["POST"]) #handles form submission
def handle_form():
form = pmidForm(secret_key='super secret key')
if request.method == "POST":
query = request.form['pmid']
return redirect('/results/'+query)
I have also edited my form in the HTML action to go to /form/
<form action="website.com/form/" class="form-inline" method="post">
<div class="form-group">
<label for="PubmedID">Pubmed ID(s)</label>
<input type="text" class="form-control" id="PubmedID" placeholder="18952863, 18269575" name="pmid" value="{{request.form.pmid}}">
</div>
<button type="submit" id= "myButton" class="btn btn-default" data-toggle="modal" data-target="#myModal">Submit</button>
</form>
With this, my site will properly redirect to /results/<query>/ (e.g. /results/1234) but I get a 404 error. Here is how I have changed my load_results
#app.route('/results/<query>', methods=["GET"])
def load_results(query):
form = pmidForm(secret_key='super secret key')
try:
if request.method == 'GET':
query = form.pmid.data #THIS IS THE USER INPUT FROM THE FORM #referencing 'class pmidForm'
return query
.
.
#do stuff
I think I am very close but need to figure out why I am getting a 404 error. My guess is that I am using "GET" incorrectly. My form in the HTML uses method="post". Since this does not match with "GET", is there no way for my load_results(query) function to retrieve the contents of the form?
EDIT 2 -
Changed handle_form to redirect with url_for:
#app.route('/form/', methods=["POST"]) #handles form submission
def handle_form():
form = pmidForm(secret_key='super secret key')
if request.method == "POST":
query = request.form['pmid']
return redirect(url_for('.load_results', query=query))
And changed load_results to not return "query"
#app.route('/results/<query>', methods=["GET"])
def load_results(query):
form = pmidForm(secret_key='super secret key')
try:
if request.method == 'GET':
query = form.pmid.data # This shouldn't work??
.
.
# do stuff with the variable "query"
With this, it's still returning the 404 Error as before. Should I not be using if request.method == GET ?
Edit 3 -
Even a very simplified load_results will give me the 404 error, so I'm not sure what's up.
#app.route('/results/<query>', methods=["GET", "POST"])
def load_results(query):
q = query
return render_template('test.html', q=q)
EDIT - 3
It seems that the accepted solution IS the correct solution to this problem, however there is an issue with uwsgi in my server that is re-directing it to the wrong location :(
Your punctual problem is that /home route function also needs to put the results_page url on the templating context.
results_page = "website.com/results"
return render_template("dashboard.html", results_page=results_page)
Since the variable is undefined, flask is calling the original endpoint on the form submission.
Your larger problem is that your appraoch isn't going to get you a dynamic results url that looks like /results/1234.
Typical approaches are to redirect on the server side when you handle the post request; or to use JavaScript in the client to get the form data and change the browser location to /results/1234.
A simplified version of how to handle this with a server side redirect might look something like this. One route that handles the form submission and another that displays results. You simply redirect from one to the other to get the nice url.
#app.route('/form', methods=["POST"])
def handle_form():
query = form.pmid.data #This is the user input
return redirect(url_for('.load_results', query=query))
#app.route('/results/<query>') *removed the method spec to handle the redirect?
def load_results(query):
.
.
# do something with query
On many forms we have a cancel button. Trying to keep things DRY it seemed that a helper function was in order. The function if_form_cancel() would check to see if the cancel button had been clicked and then redirect accordingly (with destination passed to the function). However, the redirect is ignored when we use a helper function (it works just fine in-line).
view.py
from helper.py import if_form_cancel
...
def index():
return render_template('index.html')
def edit():
...
form = EditForm(obj=user, csrf_enabled=False)
if request.method == 'POST':
if_form_cancel(url_for('.index'))
if form.validate():
...
return redirect(url_for('.index'))
return render_template('edit.html')
helper.py
from flask import request, redirect, flash
def if_form_cancel(destination='/'):
try:
if request.form['cancel']:
flash(u'Operation canceled at user request')
return redirect(destination)
except KeyError:
pass
edit.html
....
<input type="submit" name="submit" value="Submit" />
<input type="submit" name="cancel" value="Cancel" />
....
Result if cancel button is clicked: helper function is called, correct message is flashed, but redirect does not occur. Is it possible to redirect from a helper function like this? If so how? What are we doing wrong?
You aren't returning the result of if_form_cancel in the edit function. But you only want to return if the user cancelled. An exception would work well here, and it looks like werkzeug has such an exception.
See if this will work (untested).
from werkzeug.routing import RequestRedirect
def if_form_cancel(destination='/'):
try:
if request.form['cancel']:
flash(u'Operation canceled at user request')
raise RequestRedirect(destination)
except KeyError:
pass
Alternatively, you could just have the form_cancel helper check the form and return True if the user cancelled, and do the redirect in the view itself.
def edit():
...
form = EditForm(obj=user, csrf_enabled=False)
if request.method == 'POST':
if form_cancel():
return redirect(url_for('.index'))
if form.validate():
...
return redirect(url_for('.index'))
return render_template('edit.html')
Also, consider using validate_on_submit instead of checking for POST manually.
https://flask-wtf.readthedocs.org/en/latest/quickstart.html#validating-forms