Django GET request params from POST request - python

I have a form that you need to fill out using a POST request, but I want to the result to be a redirection to various pages on my site. Sometimes I want the user to be redirected to a profile page, sometimes to a purchase page.
So I put the redirect URI in the GET params of the URL of the form page that POSTs:
/form/?redirect_uri=/payments/
The url is correct upon landing on the forms page, with the appropriate redirect_uri. However, when I fill out the form and POST, Django doesn't seem to think there are any params in the GET request.
How can I get those params from the URL out on a POST request?
EDIT:
def form_view(request):
if request.method == "POST":
# Do stuff with form validation here
redirect_uri = "/home/"
if "redirect_uri" in request.GET:
redirect_uri = request.GET['redirect_uri']
if redirect_uri == request.path:
# avoid loops
redirect_uri = "/home/"
return redirect(redirect_uri)
This form page is loaded by accessing this url:
/form/?redirect_uri=/payments/
Form:
<form action="/form/" method="POST" class="wizard-form" enctype="application/x-www-form-urlencoded" autocomplete="off">
{% csrf_token %}
<fieldset class="form-group">
<label for="email">Email address</label>
<input name="email" type="text" class="form-control" id="email" placeholder="Enter email or username" required>
</fieldset>
<button type="submit" class="btn btn-primary">Go</button>
</form>

Your form has to be modified as follows:
<form action="/form/?redirect_uri={{ request.GET.redirect_url}}"
method="POST" class="wizard-form"
enctype="application/x-www-form-urlencoded" autocomplete="off">
Permit me to also suggest a slight optimization to your view
def form_view(request):
if request.method == "POST":
# Do stuff with form validation here
redirect_uri = request.GET.get('redirect_uri',"/home/")
if "redirect_uri" == request.path:
# avoid loops
redirect_uri = "/home/"
return redirect(redirect_uri)

In the POST request - i.e. when you submit the form - the url will be what you specified in the 'action' attribute in the form. If the parameters you require are not there, your view will not get them.
So either update your forms's action attribute to have these parameters or alternatively you can add thin in the form itself (as hidden inputs).
In GET request you will get attributes from url that you see in the browser.

Related

Form Doesn't Validate and there are No Errors. CSRF Token Included

I am working on a flask app with a few flask wtforms. When I get the forms they are never validated, yet the form errors are empty. What might be my issue?
One common issue I know I don't have is including the csrf_token. I included it and can see it's rendered in the html when my webpage loads. But none the less there is no validation
This is my html file being rendered
<div class="forms center">
<h1>Account Details</h1>
<h2>Update Email Address</h2>
<form id="update-email" action={{ url_for('crud.update_email') }} method="post">
{{ email_form.hidden_tag() }}
<div><input type="text" name="new-email" placeholder="new email address" value=""></div>
<div><input type="submit" name="submit" value="Update Email Address"></div>
</form>
<h2>Update Password</h2>
<form id="update-password" action={{ url_for('crud.update_password') }} method="post">
{{ password_form.hidden_tag() }}
<div><input type="password" name="old-password" placeholder="enter old password" id="old-password" value=""></div>
<div><input type="password" name="new-password" placeholder="enter new password" id="new-password" value=""></div>
<div><input type="password" name="retypepassword" placeholder="retype password" id="retypepassword" value=""></div>
<div><input type="submit" name="submit" id="submit" value="Update Password"></div>
</form>
</div>
And it is being rendered in this route
#views.route('/account-details', methods=['POST', 'GET'])
#login_required
def account_details():
email_form = UpdateEmailForm()
password_form = UpdatePasswordForm()
return render_template('account-details.html', email_form=email_form, password_form=password_form)
The submit of the update password form goes to here
#crud.route('/update-password', methods=['POST'])
def update_password():
form = UpdatePasswordForm()
old_password = request.form.get('old-password')
new_password = pbkdf2_sha256.hash(request.form.get('new-password'))
userid = session.get('userid')
user_dao = UserDao()
if form.validate_on_submit():
user_dao.update_password(userid, new_password)
return jsonify({"success": True})
return jsonify({"success": False})
Here is the form class I am using
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, validators
from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
class UpdatePasswordForm(FlaskForm):
oldpassword = PasswordField('old-password', [DataRequired()])
newpassword = PasswordField('new-password', [DataRequired(), EqualTo('retypepassword'), Length(min=6)])
retypepassword = PasswordField('retypepassword', [DataRequired()])
Looking at the network tab in the browser it is sending the expected results in the post request and if I check on the back end it is receiving everything as expected. Unfortunately the form doesn't validate anyway and for this reason I expect it has to do with something I don't understand about wtforms
Thank you!
I don't believe the alternative answer to be correct. I have many FlaskForms where I do not pass the request.
Instead I think your error might be in your naming convention. Your Class field names are:
'oldpassword' 'newpassword' etc..
but your HTML element ids and names are:
'old-password' 'new-password'
I.e. the form does not know what to parse. Align your names..
In your POST, you need to pass the request data into the form:
form = UpdatePasswordForm(request.POST)
You won't need to get values from the request.form and you can validate before doing updates:
#crud.route('/update-password', methods=['POST'])
def update_password():
form = UpdatePasswordForm(request.POST)
if form.validate_on_submit():
userid = session.get('userid')
user_dao = UserDao()
new_password = pbkdf2_sha256.hash(form.newpassword.data)
user_dao.update_password(userid, new_password)
return jsonify({"success": True})
return jsonify({"success": False})
Edit: I would highly recommend reading the WTForms crash course (and more) to get a good idea of what WTForms can do - it's very useful.

Why is there another post request after submitting a form?

I am using Django to create a form where users will have to enter their email to get notified in the future. After successfully submitting the form I get a post call that refreshes the page that the form is in:
[07/Dec/2017 01:22:41] "POST /notify-email/add/ HTTP/1.1" 200 21
[07/Dec/2017 01:22:42] "POST / HTTP/1.1" 200 23030
Below is the relevant code:
views.py
def add_notify_email(request):
if request.method == "POST":
form = NotifyEmailForm(request.POST)
if form.is_valid():
form.save(commit=True)
return JsonResponse({"status": "success"})
else:
return JsonResponse({"status": "error"})
else:
form = NotifyEmailForm()
return render(request, "landing/home.html", {"form": form})
html
<form class="form" onsubmit="addNotifyEmailInHero()" method="POST" id="notify_email_form_in_hero">
{% csrf_token %}
<div class="form-group row">
<div class="col-sm-10 input-group" id="email_input_in_hero">
<div class="input-group-addon" id="coming_soon_email_icon_in_hero"><i class="fa fa-envelope fa fa-2x" aria-hidden="true" id="email_icon_in_hero"></i></div>
<input class="form-control" type="email" name="email" onkeypress="if(event.keyCode === 13){addNotifyEmailInHero()}" placeholder="If you want to get notified when we go live, please enter your email...." maxlength="255" required id="id_email"/>
</div>
<div class="col-sm-2" id="notify_email_button_in_hero">
<button type="submit" class="btn btn-block btn-primary" id="submit_notify_email_in_hero"><i class="fa fa-bell nav-icon" aria-hidden="true"></i>Notify Me</button>
</div>
</div>
</form>
<script src="coming_soon.js"></script>
coming_soon.js
function addNotifyEmailInHero(e){
var notifyEmailForm = $("#notify_email_form_in_hero");
var thanksModal = $("#thanks");
$.ajax({
type: 'POST',
url: '/notify-email/add/',
data: notifyEmailForm.serialize(),
success: function(res){
alert(res.status);
if(res.status === "success") {
thanksModal.modal('show');
}
else {$("#id_email").val("");
$("#id_email").attr("placeholder", "Please Enter A Valid Email Address");
}
}
})}
What I think is happening:
When you click the button or press enter the form gets "submitted", that means the onsubmit function runs. Specifically, in the js script, you get the form and the modal, then there is an ajax post request with the data from the form to /notify-email/add/.
Based on the urls.py, /notify-email/add/ is connected to the add_notify_email functional view in views.py. This function checks if it is a post request and if the form is valid it saves the data to the database and returns a json response, if the form is not valid it returns also a json response.
If the request is not a post request it renders the specified template.
If add_notify_email returns a json response that gets handled by the js function in coming_soon.js.
That's my understanding of what really is going on.
Because you have an onsubmit handler that ultimately makes an AJAX call to /notify-email/add/, and then the form is submitted to its own handler, which is / because you did not specify a handler.

flask Cannot get the form submitted values

I'm newbie to flask,I have such a form:
<form class="form" action={{ url_for('/login') }} method="POST">
<input type="text" name="usename" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit" id="login-button">Login</button>
</form>
I use request.form()and request.form.get() to get submitted values,but the result is always the same**"GET / HTTP/1.1" 200 -**
#app.route('/login',methods=['POST','GET'])
def login():
name=request.form['usename']
password=request.form['password']
form_data=request.form.get('usename')
print name,password,form_data
return render_template('login.html')
And I try visiting /login ,it shows Bad Request.But when it is modified to such,it's ok.It seems my request statement block is incorrect.
#app.route('/login',methods=['POST','GET'])
def login():
return render_template('login.html')
It's because of form action change the
action="{{url_for('index')}}"
Your form does not post actually and you are try to get values. And don't forget to add a view function named index or you can use instead login instead index
i guess you are trying to get those values from login.html file. If it is so you have to use something like this
#app.route('/login',methods = ['GET','POST'])
def login():
if request.method =='POST':
name=request.form['usename']
password=request.form['password']
form_data=request.form.get('usename')
print name,password,form_data
return render_template('login.html')
try leaving the action field emtpy.
<form class="form" action="" method="POST">
leaving it empty will post to the same location where the form was downloaded from, which I guess is your use case here.
[1] Is it a good practice to use an empty URL for a HTML form's action attribute? (action="")

How do I pass through the "next" URL with Flask and Flask-login?

The documentation for Flask-login talks about handling a "next" URL. The idea seems to be:
User goes to /secret
User is redirect to a login page (e.g. /login)
After a successful login, the user is redirected back to /secret
The only semi-full example using Flask-login that I've found is https://gist.github.com/bkdinoop/6698956 . It's helpful, but since it doesn't include the HTML template files, I'm seeing if I can recreate them as an self-training exercise.
Here's a simplified version of the /secret and /login section:
#app.route("/secret")
#fresh_login_required
def secret():
return render_template("secret.html")
#app.route("/login", methods=["GET", "POST"])
def login():
<...login-checking code omitted...>
if user_is_logged_in:
flash("Logged in!")
return redirect(request.args.get("next") or url_for("index"))
else:
flash("Sorry, but you could not log in.")
return render_template("login.html")
And here's login.html:
<form name="loginform" action="{{ url_for('login') }}" method="POST">
Username: <input type="text" name="username" size="30" /><br />
Password: <input type="password" name="password" size="30" /><br />
<input type="submit" value="Login" /><br />
Now, when the user visits /secret, he gets redirected to /login?next=%2Fsecret . So far, so good - the "next" parameter is in the query string.
However when the user submits the login form, he is redirected back to the index page, not back to the /secret URL.
I'm guessing the reason is because the "next' parameter, which was available on the incoming URL, is not incorporated into the login form and is therefore not being passed as a variable when the form is processed. But what's the right way to solve this?
One solution seems to work - change the <form> tag from
<form name="loginform" action="{{ url_for('login') }}" method="POST">
to:
<form name="loginform" method="POST">
With the "action" attribute removed, the browser (at least Firefox 45 on Windows) automatically uses the current URL, causing it to inherit the ?next=%2Fsecret query string, which successfully sends it on to the form processing handler.
But is omitting the "action" form attribute and letting the browser fill it in the right solution? Does it work across all browsers and platforms?
Or does Flask or Flask-login intend for this to be handled in a different way?
If you need to specify a different action attribute in your form you can't use the next parameter provided by Flask-Login. I'd recommend anyways to put the endpoint instead of the url into the url parameter since it is easier to validate. Here's some code from the application I'm working on, maybe this can help you.
Overwrite Flask-Login's unauthorized handler to use the endpoint instead of the url in the next parameter:
#login_manager.unauthorized_handler
def handle_needs_login():
flash("You have to be logged in to access this page.")
return redirect(url_for('account.login', next=request.endpoint))
Use request.endpoint in your own URLs too:
{# login form #}
<form action="{{ url_for('account.login', next=request.endpoint) }}" method="post">
...
</form>
Redirect to the endpoint in the next parameter if it exists and is valid, else redirect to a fallback.
def redirect_dest(fallback):
dest = request.args.get('next')
try:
dest_url = url_for(dest)
except:
return redirect(fallback)
return redirect(dest_url)
#app.route("/login", methods=["GET", "POST"])
def login():
...
if user_is_logged_in:
flash("Logged in!")
return redirect_dest(fallback=url_for('general.index'))
else:
flash("Sorry, but you could not log in.")
return render_template("login.html")
#timakro provides a neat solution.
If you want to handle a dynamic link such as
index/<user>
then using url_for(request.endpoint,**request.view_args) instead because request.endpoint will not contain the dynamic vairable info:
#login_manager.unauthorized_handler
def handle_needs_login():
flash("You have to be logged in to access this page.")
#instead of using request.path to prevent Open Redirect Vulnerability
next=url_for(request.endpoint,**request.view_args)
return redirect(url_for('account.login', next=next))
the following code shall be changed to:
def redirect_dest(home):
dest_url = request.args.get('next')
if not dest_url:
dest_url = url_for(home)
return redirect(dest_url)
#app.route("/login", methods=["GET", "POST"])
def login():
...
if user_is_logged_in:
flash("Logged in!")
return redirect_dest(home=anyViewFunctionYouWantToSendUser)
else:
flash("Sorry, but you could not log in.")
return render_template("login.html")
Just in case someone is trying to pass through the "next" URL with Flask-Login but with Flask_Restful, the workaround I've been using is passing the "next" argument from the GET method to the POST method. With flask_restful the "next_page" argument is set to "None" after clicking on the Login Button in the login.html
login.html
...
<!-- next_page came from "render_template(next_page=request.args.get('next') ...)" in the get() function -->
<!-- And also from render_template('login.html', next_page=next_page) in the post() function -->
<form action="{{ url_for('login', next=next_page) }}" method="POST" >
<div class="field">
<div class="control">
<input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-large" type="password" name="password" placeholder="Your Password">
</div>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" name="remember_me">
Remember me
</label>
</div>
<button class="button is-block is-info is-large is-fullwidth">Login</button>
</form>
...
auth.py
class Login(Resource):
def get(self):
if current_user.is_authenticated:
return redirect(url_for('home'))
headers = {'Content-Type': 'text/html'}
#1 --> # Here I pass the "next_page" to login.html
return make_response(render_template('login.html', next_page=request.args.get('next')), 200, headers)
def post(self):
#2 --> # After the user clicks the login button, I retrieve the next_page saved in the GET method
next_page = request.args.get('next')
if current_user.is_authenticated:
return redirect(url_for('home'))
# Check if account exists in the db
existing_account = Account.objects(email=request.form.get('email')).first()
# Only redirects when the URL is relative, which ensures that the redirect
# stays within the same site as the application.
if existing_account:
if existing_account.check_password(request.form.get('password')):
login_user(existing_account, remember=request.form.get('remember_me'))
if not next_page or url_parse(next_page).netloc != '':
return redirect(url_for('home'))
#3 --> # Here I use the retrieved next_page argument
return redirect(url_for(next_page))
# Account not recognized
flash('Please check your login details and try again.')
headers = {'Content-Type': 'text/html'}
#4 --> # I also pass the "next_page" here in case the user-provided data is wrong
return make_response(render_template('login.html', next_page=next_page), 200, headers)
Out of all the above answers I didn't find anything helpful for me.
But what I found, I will share with you..
In login.html just add the below code
<input
type="hidden"
name="next"
value="{{ request.args.get('next', '') }}"
/>
And login definition changes as follows
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
next_url = request.form.get("next")
if username in users and users[username][1] == password:
session["username"] = username
if next_url:
return redirect(next_url)
return redirect(url_for("profile"))
return render_template("login.html")
And This worked perfectly for me...
Thank you ...
Simple Example
#app.route('/login', methods=['GET', 'POST'])
def login():
// do your stuff
next_page = request.form.get('next_page')
# next_page either be 'http://localhost:5000/None or 'http://localhost:5000/some_endpoints
return redirect(next_page) if 'None' not in next_page else redirect(url_for('default_action'))
Note
as Mentioned in Flask documentation
don't forget to pass next_url in html
<input type="hidden" value="{{ request.args.get('next') }}" name="next_page" id="next_page"/>
This is what worked for me, this is an addition to what #timakro and #Kurumi Tokisaki has provided in an earlier post.
#login_manager.unauthorized_handler
def handle_needs_login():
return redirect(url_for('login', next_page=request.endpoint))
#app.route('/login/<next_page>', methods=['GET', 'POST'])
def login(next_page):
if current_user.is_authenticated:
return redirect(url_for(next_page))
if is_a_successfull_login():
redirect(url_for(next_page))

How to unit test a form submission when multiple forms on a route?

I want to add unit tests to my flask app that tests form behavior on valid and invalid logins + signups. Currently, I have the signup form and a login form hosted on one page and route, and am using a hidden input field to identify which of the two forms is submitted / determine next actions.
My question is - how do I write a unit test that targets a specific form on a page? All the examples I've seen so far post data to a specific route, which is currently what I am doing. But that is failing because I need an added way to say "and we're submitting x form".
So is there a way to add "and we're submitting x form" in the post request?
**
edited to add, here are the different ways I've tried to pass the hidden form data in the post data dict, with no success.
data = dict(username="test#gmail.com", password="test", login_form)
data = dict(username="test#gmail.com", password="test", "login_form")
data = dict(username="test#gmail.com", password="test", login_form=True)
login unit test:
from app import app
import unittest
class FlaskTestCase(unittest.TestCase):
#ensure that login works with correct credentials
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
login route in views.py:
#app.route('/login', methods=['POST', 'GET'])
def login():
login_form = LoginForm()
signup_form = SignupForm()
error_login = ''
error_signup = ''
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
return render_template('login.html', login_form=login_form, signup_form=signup_form, error=error)
login.html:
<div class="login-form form-400">
<h3>Log In To Your Account</h3>
<form action="" method="post">
<input type="hidden" name="login_form">
{% if error_login != '' %}
<label class="error">
{{ error_login }}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
<input type="submit" value="Login" class="button button-blue">
</form>
</div>
<p class="login-divider">or</p>
<div class="signup-form form-400">
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
{% if error_signup != '' %}
<label class="error">
{{ error_signup | safe}}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ signup_form.hidden_tag() }}
{{ render_field(signup_form.username, placeholder="Pick a Username", class="form-item__full") }}
{{ render_field(signup_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(signup_form.password, placeholder="Create a Password", class="form-item__full") }}
<input type="submit" value="Sign Up" class="button button-green">
</form>
</div>
Ok I figured it out. To pass the login_form info, I had to end up passing an empty string on the login_form like this:
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test", login_form=""),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
I did this by throwing a print request.form in my views.py for this route and then saw the empty string.
It was still failing, but because the login_form.validate() was failing because of the csrf token added by the WTForms module. In the end, this discussion had the answer.
Flask-WTF / WTForms with Unittest fails validation, but works without Unittest
Thanks #drdrez for your suggestions!
Update:
Thanks for updating your question with what you've already tried! I have a few other ideas about what's causing the issue.
Let's continue to look at the HTML and try to understand the technologies your program is built on top of. In the server side login.html file, notice these lines:
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
It isn't HTML, and is probably being processed on the server side to produce HTML and serve to the client. The line that contains login_form.hidden_tag() looks interesting, so I would recommend loading this page in your browser and inspecting the HTML served to the client. Unfortunately, I haven't used Flask before, so I can't give any more direct help.
However, my advice is to continue digging into how Flask and the HTML Form works. The nice thing about Python is you have access to libraries' source code, which allows you to figure out how they work so you can learn how to use them and fix bugs in your application that uses them.
Sorry I can't give you more direct help, good luck!
Let's look at login.html. When you submit a form, how does the login route in views.py know which form was submitted? If you know HTML Forms, <input> elements nested in a form are used to, in this case, post data to your server/application.
Back to login.html, notice these two lines:
...
<h3>Log In To Your Account</h3>
<input type="hidden" name="login_form">
...
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
...
Those are <input> elements, with a type of "hidden", so they won't display, with names of "login_form" and "signup_form", which are included in the data that is submitted by the form.
Now in the login route in views.py, you'll notice there two lines:
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
Those are testing to see if the phrase "login_form" or "signup_form" are in present in the list request.form. Back to your unit test now:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
Notice the data you are passing in the dict, this is mimicking the form data, so you should probably include either "login_form" or "signup_form" to mimic the behavior of the HTML form correctly.
If you're unfamiliar with HTML Forms and HTTP Post, I would suggest searching for some tutorials, or just reading documentation on MDN or elsewhere. When building software on top of a technology (like HTTP and HTML), it can be helpful to understand how those technologies work when you run into bugs in your own software.
Hope this helps, let me know if I can clarify anything!
You might be experiencing a problem because you have not flagged the request as being of the form application content type. I note you are trying to access request.form, which requires that the data package is parsed in a certain way. You could try to do something like the following:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True,
headers = {"Content-Type":"application/x-www-form-urlencoded"}
)

Categories

Resources