Flask: Is it possible to Mask a URL with variables? - python

I want to pass variables from a site to another.
This is no problem, as there are many ways to do it.
I'm struggling though, in how I can 'hide' these variables in the URL, and yet be able to get the values.
Ex.:
If I use 'request.args.get':
#page.route('/users', methods=['GET', 'POST'])
def users():
user = request.args.get('user')
return render_template('users.html', user=user)
When I click in the link, the URL generated is:
http://localhost:5000/users?user=john
My goal is to access the 'users' page, in the 'John' section, but what the user will see in the URL path is only http://localhost:5000/users

I was able to achieve my goal using:
window.history.pushState({"html":response.html,"pageTitle":response.pageTitle},"", "/users/");
I'm no Web Dev'er, just a Python/Flask enthusiast and know that 'window.history.pushState()' is meant for other purposes. I'm also aware that it a HTML5 Feature and not all browsers are compatible. But hey, it did the trick ;) .
Unless someone point out reasons I shouldn't be using this approach, this is my solution.
Thanks all for your time

If you'd only want to hide the variable name then you could use converters to create a route like 'users/<str:username>'. Your url would be http://localhost:5000/users/john.
Your can find the documentation here: http://exploreflask.com/en/latest/views.html#built-in-converters
Note that hiding the variables completely would mean, that your users would lose the ability to bookmark the page they are on. Additionaly if they bookmark /users anyways, you would have to catch the case that your variable is not sent or run into errors.

Post method can hide data and variables from URL. So you need to integrate it in your project. Here is an example.
app.py:
from flask import Flask, render_template, request
app = Flask(__name__)
#app.route('/users', methods=['GET', 'POST'])
def show_users():
if request.method == 'POST':
username = request.form.get("username", None)
return render_template('post_example.html', username = username)
else:
return render_template('post_example.html')
if __name__ == '__main__':
app.run(debug = True)
post_example.html:
<html>
<head></head>
<body>
{% if username %}
Passed username: {{ username }}
{% endif %}
<form action="/users" method="post">
Username: <input type="text" name="username">
<input type="submit" name="submit" value="Submit">
</form>
</body>
</html>
Output:
You can check the HTTP methods in Flask official documentation here

Related

cannot get html front end to pass input to python script using flask

So, I am building a webapp which takes a link from a shopping website then runs it through a python script which interprets the data, stores it in a database and that populates a table for reference.
I am running into a couple issues:
if I put the link into the front end input (html) then submit it just takes me to "page isn't working HTTP error 405". I'm not sure what to do about that one.
the more pressing issue is that even though I believe I routed the input properly through flask I get this issue when I run the python script alongside the frontend
"RuntimeError: Working outside of request context."
I tried some of the advice mentioned in these existing posts to no avail:
Sending data from HTML form to a Python script in Flask
Connecting python script with html button and flask
I also tried changing the script itself to use getvalue() instead of getvalue when associating it as an input variable for the python script to work with.
this is my route code from app.py
#app.route("/", methods=['POST'])
def getvalue():
HTML_Info = request.form['data_bridge']
return HTML_Info
code for the HTML input
<form name="passdata" action="{{ url_for('getvalue') }}" method="POST">
<input type='text' name="data_bridge" placeholder="paste shoe link here">
<input type="submit">
</form>
and the python code just imports the app file and the getvalue function and then assigns it to a variable.
if you guys could help me sort this out I would greatly appreciate it.
I assume you want to take an input (e.g. shoe link) from the user and then do some operations based on the input.
To access the HTML form from / path you need to enable both GET and POST requests in that route. Otherwise, when you try to access the root path / from your browser, you will get the HTTP Method not allowed error.
app.py:
from flask import Flask, render_template, request
app = Flask(__name__)
def get_value_related_info(value):
return f"You have entered {value}"
#app.route('/', methods=['POST', 'GET'])
def getvalue():
if request.method == "POST":
HTML_Info = request.form['data_bridge']
return get_value_related_info(HTML_Info)
return render_template('form.html', text="")
if __name__ == "__main__":
app.run(debug=True)
Output:
Before form submission:
After form submission:
templates/form.html:
<html>
<head>
<title>Form example</title>
</head>
<body>
<form name="passdata" action="{{ url_for('getvalue') }}" method="POST">
<input type='text' name="data_bridge" placeholder="paste shoe link here">
<input type="submit">
</form>
</body>
</html>
Explanation:
I have mocked the functionality on the user input in get_value_related_info method.
References:
Flask documentation for request object

Allowing users to delete their accounts leads to 400 Bad Request in Flask

I am having problems with setting up user account deletion option. I have set up a Bootstrap modal to pop up for assurance and I want the 'Delete' button to delete the current user's account from the database, log the user out, and redirect the user to the homepage with a flash message. However, when I click the button it throws out a 400 Bad Request Error. I don't know if the reason is the POST method or something else, so here I am, asking for your help. I am using PostgreSQL along with SQLAlchemy, and Flask-Login (for the current_user) in my application. This is my first time setting up something like this, and if my question is not clear enough, please let me know so I can provide more details. Also if you have a suggestion on how can I optimize this, I would be more than grateful to hear it. Here is the related code:
The button in the modal:
<form action="{{ url_for('user.delete_account') }}" method="POST">
<input type="submit" id="delete" name="delete" value="Delete" class="btn btn-danger">
</form>
The route and the delete function:
#user.route('/delete', methods=['POST'])
#login_required
def delete_account():
if request.method == 'POST':
if request.form['delete'] == 'Delete':
current_user.delete()
flash('Your account has been successfully deleted.', 'success')
return redirect(url_for('core.home'))
I really hope that my question is clear enough, because I got stuck with this for more than 10 hours straight and I need the help. Thanks in advance.
FOUND THE SOLUTION:
So basically, I played around a bit with the code and found the solution. Here is the relevant code:
First, I created a Jinja2 macro with a hidden CSRF token:
{%- macro form_tag(endpoint, fid='', css_class='', method='post') -%}
<form action="{{ url_for(endpoint, **kwargs) }}" method="{{ method }}"
id="{{ fid }}" class="{{ css_class }}" role="form">
{{ form.hidden_tag() }}
{{ caller () }}
</form>
{%- endmacro -%}
After that I created the simplest form possible:
from flask_wtf import Form
from wtforms import SubmitField
class DeleteUserForm(Form):
delete = SubmitField('Delete')
After that I added this to the Bootstrap modal:
{% import 'macros/form.html' as f with context %}
{% call f.form_tag('user.delete') %}
<button type="submit" class="btn btn-danger">Delete</button>
{% endcall %}
At last, I modified the route in my views.py file:
#user.route('/settings/delete', methods=['POST'])
#login_required
def delete():
form = DeleteUserForm()
if form.validate_on_submit():
current_user.delete()
flash('Your account has been successfully deleted. Hope to see you again.', 'success')
return redirect(url_for('user.login'))
return render_template('user/login.html', form=form)
This solution was the best I could do. I hope this will help someone else too. Oh, and I almost forgot, since I was editing my settings.html file, I also had to pass the form=form argument in my /settings route of the page:
#user.route('/settings')
#login_required
def settings():
form = DeleteUserForm()
return render_template('user/settings.html', form=form)
That's all. Thanks to the ones who posted an answer too, I appreciate it.
Edit: It turns out that the issue is probably from a globally installed CSRF protection layer (from something like Flask-WTF). Debugging if you are even getting to your handler will greatly help in creating a minimal reproducible example.
Original Best-Guess Answer: You are almost certainly getting a 400 Bad Request error from request.form['delete'] - Flask raises such an error when you attempt to get a value out of request.{form, args, values} that wasn't sent to the server (the error raised is also a subtype of KeyError which an ordinary Python dictionary would raise in the same case:
({ "x": 123 })["y"] # Raises a KeyError
request.form["not-in-form"] # Raises a BadRequest error
So now the question is, "why is 'delete' not in the form request?". The answer there is almost certainly because you are catching the submit event for this form in JavaScript and submitting the form programmatically ... and not including the value of the button. Include the necessary parameter or simply don't check for the form value (or even for the method, since you've already limited the route to only receive POST).
#user.route('/delete', methods=['POST'])
#login_required
def delete_account():
current_user.delete()
flash('Your account has been successfully deleted.', 'success')
return redirect(url_for('core.home'))

How to use recaptcha without Flask-WTF

I have been making a file sharing website for quite a while and I want to implement recaptcha when people register to the website. The problem is that I can't use Flask-WTF because I will have to change a lot of my code (I have been programming without it).
I have found this Flask recaptcha that doesn't include the use of Flask-WTF but I can't seem to make it work (it doesn't show the recaptcha itself):
https://github.com/mardix/flask-recaptcha
I have followed step by step and still, it doesn't work. The only thing that I didn't do is the config.
EDIT:
The captcha is not working. Everytime that I enter the right info for the registration and mark the captcha, it says that the username/password is incorrect. If I don't mark it, it does the same.
Here is the captcha code (the others worked before):
recaptcha = ReCaptcha(app=app)
if recaptcha.verify() is False:
flash('Captcha is incorrect')
return redirect(url_for('register'))
<div id="captcha"">
{{ recaptcha }} - HTML PART
</div>
EDIT: After getting help from Nurzhan, I have changed the code and the captcha always returns false, no matter what.
You didn't try the config, but you need to indicate the keys in order to make your recaptcha work. These 2 options are NOT optional in the config:
RECAPTCHA_SITE_KEY : Public key
RECAPTCHA_SECRET_KEY: Private key
Set them with proper values and it see if it works.
EDIT:
It's working now. This is app.py:
import requests
import json
from flask import Flask, render_template, request
from flask_recaptcha import ReCaptcha
app = Flask(__name__)
app.config.update({'RECAPTCHA_ENABLED': True,
'RECAPTCHA_SITE_KEY':
'site_key',
'RECAPTCHA_SECRET_KEY':
'secret_key'})
recaptcha = ReCaptcha(app=app)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/submit', methods=['GET', 'POST'])
def submit():
print('SUBMIT CALLED')
username = ''
password = ''
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
print(request.form)
if username == 'username' and password == 'password':
print('CREDENTIALS ARE OK')
r = requests.post('https://www.google.com/recaptcha/api/siteverify',
data = {'secret' :
'secret_key',
'response' :
request.form['g-recaptcha-response']})
google_response = json.loads(r.text)
print('JSON: ', google_response)
if google_response['success']:
print('SUCCESS')
return render_template('profile.html')
else:
# FAILED
print('FAILED')
return render_template('index.html')
# if recaptcha.verify():
# # SUCCESS
app.run(debug=True)
This is the index.html page:
<!DOCTYPE html>
<html>
<head>
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<body>
<h1>Flask Recaptcha</h1>
<p>Flask Recaptcha Test</p>
<form method="post" action="/submit">
Username:<br>
<input type="text" name="username"><br>
Password:<br>
<input type="password" name="password">
{{ recaptcha }}
<input type="submit" value="Submit">
<div class="g-recaptcha" data-sitekey="site_key"></div>
</form>
</body>
</html>
This is the profile.html page if you pass the validation:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>Profile page</h1>
<p>Registration is ok</p>
</body>
</html>
I couldn't make recaptcha.verify() work. In the official docs of Google Recaptcha it is stated that you need to send a post request to google recaptcha api separately after the client submits your form with your secret_key and g-recaptcha-response which you receive when user puts a tick in recaptcha.
Note that this is just an example code. You need to add your own site_key and secret_key to the app.py and index.html and also add a proper checking of user credentials for the registation like double entering password etc.

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