Question about Dynamic Routing with Flask - python

I'm trying to create a logged area in my Flaks app where each user will have its own routes to access their information.
/user/<userid>/dashboard
/user/<userid>/profile
/user/<userid>/operations
/user/<userid>/analytics
What is the best way to handle that? In the example below, I'm passing the userId variable after the login inside the url_for. Once the redirect will come from the html template don't know how to pass the userId to the other routes.
I was reading something about having multiple routes with a single method but couldn't understand if it fits in what I need. Sry for the noob question and thanks in advance.
#app.route('/logon', methods=['POST'])
def logon():
username = request.form['username']
passwd = request.form['password']
user = login_verified(username, passwd)
session['token'] = user['idToken']
return redirect(url_for('dashboard', usrId=user['userId']))
#app.route('/user/<usrId>/dashboard', methods=['GET'])
def dashboard(usrId):
if 'token' in session:
print('User ID = %s' % usrId)
return render_template('dashboard.html')
else:
return redirect(url_for('login'))

Guys just found a way to do what I was looking for (don't know if this is the best one but works). Below the solution I found:
#app.route('/logon', methods=['POST'])
def logon():
username = request.form['username']
passwd = request.form['password']
user = login_verified(username, passwd)
session['token'] = user['idToken']
return redirect(url_for('dashboard', usrId=user['userId']))
#app.route('/user/<usrId>/dashboard', methods=['GET'])
def dashboard(usrId):
if 'token' in session:
print('User ID = %s' % usrId)
return render_template('dashboard.html', user=usrId)
else:
return redirect(url_for('login'))
#app.route('/user/<usrId>/operations', methods=['GET', 'POST'])
def operations(usrId):
if 'token' in session:
return render_template('operations.html', user=usrId)
else:
return redirect(url_for('login'))
and the template...
<body>
<h1>Dashboard</h1>
<div>
Operações
Perfil
</div>
<br />
<input type="button" value="Logout" onclick="location.href='/logout'" />
</body>

Related

TypeError: 'NoneType' object is not iterable when using session["user_id"] inside app.context_processor

I just read about context processors in Flask and I learned that it's useful in re-using variables in multiple templates. But when I used it with the session["user_id"], it doesn't seem to work. It maybe because session["user_id"] doesn't exist yet but Python is reading it already. How do I make my context_processor work after a user has already logged in?
Here is my code for login:
#app.route("/login", methods = ["GET", "POST"])
def login():
session.clear()
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if not username:
return ("error")
elif not password:
return ("error")
user = db.query(Users).filter_by(username = username).scalar()
if not user or not check_password_hash(user.hash, password):
return "error"
session["user_id"] = user.id
flash("successful Log-in")
return redirect("/")
else:
return render_template("login.html")
And here's my code for my context processor:
#app.context_processor
def homepage_items():
if session.get("user_id") is None:
pass
else:
user = db.query(Users).filter_by(id = session["user_id"]).scalar()
return dict(user = user)
The problem, as you have correctly identified, is that the value within the session is empty when the user is not logged in. In your implementation, within the contextprocessor, it then returns none using pass. However, the application requires an iterable dict type.
I recommend the following version, in which the user object is either set or None, but a result is returned anyway. I also used a different query variant for the database, because your formulation led to an error due to the missing database session within the command.
#app.context_processor
def homepage_items():
user = None
if 'user_id' in session:
# user = db.session.query(User).filter_by(id=session['user_id']).scalar()
user = User.query.get(session['user_id'])
return dict(user=user)
#app.route('/login', methods=['GET', 'POST'])
def login():
session.clear()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# user = db.session.query(User).filter_by(username=username).scalar()
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.hash, password):
session['user_id'] = user.id
return redirect('/')
return render_template('login.html')
It can now be used in this way within the template.
{% if user -%}
<p>{{ user.username }}<p>
{% endif -%}

Loopback Access Token To Flask

I have setup a loopback API, and I plan to use the login as such flask would make requests to loopback and loopback returns an accessToken
For example login to dashboard:
# Login route
#app.route("/login", methods=['GET', 'POST'])
def login():
status = ""
url_login = 'http://localhost:3000/api/Users/login'
try:
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
payload_login = {
"username": str(username),
"password":str(password)
}
print(payload_login)
r = requests.post(url_login, data=payload_login).text
access_token = json.loads(r)
# access_token = r['id']
# access_token = json.loads(access_token)
print("Access Token: " + str(access_token['id']))
return redirect('/') #CHANGE TO 404 PAGE
except Exception as e:
print(e)
return redirect('/') #CHANGE TO 404 PAGE
return render_template('login.html')
#app.route('/dashboard', methods=['GET', 'POST'])
def logged_in_dashboard():
return render_template('index.html')
How do I set it up so that login to dashboard requires accessToken from loopback? In the past I've used app.config['ACCESS_KEY'] ='key' and have set that if it contains a token it would allow the user to login.
But I'm not sure if this is a good practice. Anything you would like to recommend that could handle lots of user logins?
Don't create requests to the API from within the API. To share functionality between endpoints, use functions. You need at least two functions here:
a function returns a token for valid credentials
a function that requires the token being present in the session or in the request Authorization header, for example
Check the approach that chans linked to for more implementation details: How do you implement token authentication in Flask?
Or the official tutorial for how to implement sessions: https://flask.palletsprojects.com/en/1.1.x/quickstart/#sessions
Which has something like this:
#app.route('/')
def index():
# this if is the login requirement
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# Add logic for validating username and password here.
# If credentials are ok, set username to session.
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''

Flask throwing "Could not build url for endpoint 'index'" after adding a decorator

I've a custom app written with flask and I'm trying to add an authentication decorator (d_auth), so that I don't have to check whether the user has authenticated or not within each routing function. The decorator works fine, but the problem is that url_for("index") fails after the user signs in. Here is my code for the decorator and the index routing function where I've added that decorator:
def d_auth(func):
wraps(func)
def decorated(*ags, **kgs):
#print("DECORATOR_RUNNING")
login_valid = (flask.session.get('auth_email') != None)
if not login_valid:
return redirect(url_for("login"))
else:
#func(*args, **kwargs)
return func(*ags, *kgs)
#pass
return decorated
#app.route("/", methods=["GET", "POST"])
#d_auth
def index():
creds = gdrive.get_gdrive_credentials(session['auth_user_id'])
if not creds:
info = "<h2 id='lblGAuthStatus' class='text-center text-danger'> <span class='glyphicon glyphicon-alert'></span> NOT Authenticated. <a href='/gdrive_auth'>Click here to Authenticate.</a></h2>"
elif creds.access_token_expired:
info = "<h2 id='lblGAuthStatus' class='text-center text-danger'> <span class='glyphicon glyphicon-alert'></span> Access Token EXPIRED. <a href='/gdrive_auth'>Click here to Authenticate.</a></h2>"
else:
info = "<h2 id='lblGAuthStatus' class='text-center text-success'> <span class='glyphicon glyphicon-ok'></span> Successfully Authenticated.</h2>"
return render_template('static/index.html', info=info)
What the decorator basically does is check whether the user has logged-in or not (not login_valid) and redirects them to login page if they haven't. This works perfectly. Problem is that once the user logs in and the login page tries to redirect them to the index page back again, it throws this error:
werkzeug.routing.BuildError: Could not build url for endpoint 'index'. Did you mean 'view' instead?
Here is the code for the /login route:
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == 'GET':
return render_template("static/login.html")
elif request.method == 'POST':
email = request.form['email']
password = request.form['password']
conn, cursor = db.opendb()
cursor.execute("select id, is_admin, first_name, last_name from user where email=? and password=?", (email, password))
row = cursor.fetchone()
if row == None:
return render_template("static/login.html", error="Invalid Credentials")
else:
session['auth_user_id'] = str(row['id'])
session['auth_email'] = email
session['auth_first_name'] = row['first_name']
session['auth_last_name'] = row['last_name']
session['auth_is_admin'] = row['is_admin']
return redirect(url_for("index"))
On that last line, url_for("index") is being called and that's where the error is coming. I know I can workaround with url_for("/") instead which is working, but I want to fix this permanently so that something else may not stop working in my relatively large code-base.
I just found an answer to my question here. Turns out that I've to wrap a decorator function using the #wraps(func), not simply wraps(func) like I'd done. Wonder why it didn't throw an error!

How to use url_for in Flask [duplicate]

When the user accesses this URL running on my flask app, I want the web service to be able to handle the parameters specified after the question mark:
http://10.1.1.1:5000/login?username=alex&password=pw1
#I just want to be able to manipulate the parameters
#app.route('/login', methods=['GET', 'POST'])
def login():
username = request.form['username']
print(username)
password = request.form['password']
print(password)
Use request.args to get parsed contents of query string:
from flask import request
#app.route(...)
def login():
username = request.args.get('username')
password = request.args.get('password')
The URL parameters are available in request.args, which is an ImmutableMultiDict that has a get method, with optional parameters for default value (default) and type (type) - which is a callable that converts the input value to the desired format. (See the documentation of the method for more details.)
from flask import request
#app.route('/my-route')
def my_route():
page = request.args.get('page', default = 1, type = int)
filter = request.args.get('filter', default = '*', type = str)
Examples with the code above:
/my-route?page=34 -> page: 34 filter: '*'
/my-route -> page: 1 filter: '*'
/my-route?page=10&filter=test -> page: 10 filter: 'test'
/my-route?page=10&filter=10 -> page: 10 filter: '10'
/my-route?page=*&filter=* -> page: 1 filter: '*'
You can also use brackets <> on the URL of the view definition and this input will go into your view function arguments
#app.route('/<name>')
def my_view_func(name):
return name
If you have a single argument passed in the URL you can do it as follows
from flask import request
#url
http://10.1.1.1:5000/login/alex
from flask import request
#app.route('/login/<username>', methods=['GET'])
def login(username):
print(username)
In case you have multiple parameters:
#url
http://10.1.1.1:5000/login?username=alex&password=pw1
from flask import request
#app.route('/login', methods=['GET'])
def login():
username = request.args.get('username')
print(username)
password= request.args.get('password')
print(password)
What you were trying to do works in case of POST requests where parameters are passed as form parameters and do not appear in the URL. In case you are actually developing a login API, it is advisable you use POST request rather than GET and expose the data to the user.
In case of post request, it would work as follows:
#url
http://10.1.1.1:5000/login
HTML snippet:
<form action="http://10.1.1.1:5000/login" method="POST">
Username : <input type="text" name="username"><br>
Password : <input type="password" name="password"><br>
<input type="submit" value="submit">
</form>
Route:
from flask import request
#app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
print(username)
password= request.form.get('password')
print(password)
url:
http://0.0.0.0:5000/user/name/
code:
#app.route('/user/<string:name>/', methods=['GET', 'POST'])
def user_view(name):
print(name)
(Edit: removed spaces in format string)
Use request.args.get(param), for example:
http://10.1.1.1:5000/login?username=alex&password=pw1
#app.route('/login', methods=['GET', 'POST'])
def login():
username = request.args.get('username')
print(username)
password = request.args.get('password')
print(password)
Here is the referenced link to the code.
this should work
#app.route('/login$username=<username>$password=<password>', methods=['GET', 'POST'])
def login(username, password):
# you can add stuff
return f"Username: {username}\nPassword: {password}"
It's really simple. Let me divide this process into two simple steps.
On the html template you will declare name attribute for username and password like this:
<form method="POST">
<input type="text" name="user_name"></input>
<input type="text" name="password"></input>
</form>
Then, modify your code like this:
from flask import request
#app.route('/my-route', methods=['POST'])
# you should always parse username and
# password in a POST method not GET
def my_route():
username = request.form.get("user_name")
print(username)
password = request.form.get("password")
print(password)
#now manipulate the username and password variables as you wish
#Tip: define another method instead of methods=['GET','POST'], if you want to
# render the same template with a GET request too

How can I get the named parameters from a URL using Flask?

When the user accesses this URL running on my flask app, I want the web service to be able to handle the parameters specified after the question mark:
http://10.1.1.1:5000/login?username=alex&password=pw1
#I just want to be able to manipulate the parameters
#app.route('/login', methods=['GET', 'POST'])
def login():
username = request.form['username']
print(username)
password = request.form['password']
print(password)
Use request.args to get parsed contents of query string:
from flask import request
#app.route(...)
def login():
username = request.args.get('username')
password = request.args.get('password')
The URL parameters are available in request.args, which is an ImmutableMultiDict that has a get method, with optional parameters for default value (default) and type (type) - which is a callable that converts the input value to the desired format. (See the documentation of the method for more details.)
from flask import request
#app.route('/my-route')
def my_route():
page = request.args.get('page', default = 1, type = int)
filter = request.args.get('filter', default = '*', type = str)
Examples with the code above:
/my-route?page=34 -> page: 34 filter: '*'
/my-route -> page: 1 filter: '*'
/my-route?page=10&filter=test -> page: 10 filter: 'test'
/my-route?page=10&filter=10 -> page: 10 filter: '10'
/my-route?page=*&filter=* -> page: 1 filter: '*'
You can also use brackets <> on the URL of the view definition and this input will go into your view function arguments
#app.route('/<name>')
def my_view_func(name):
return name
If you have a single argument passed in the URL you can do it as follows
from flask import request
#url
http://10.1.1.1:5000/login/alex
from flask import request
#app.route('/login/<username>', methods=['GET'])
def login(username):
print(username)
In case you have multiple parameters:
#url
http://10.1.1.1:5000/login?username=alex&password=pw1
from flask import request
#app.route('/login', methods=['GET'])
def login():
username = request.args.get('username')
print(username)
password= request.args.get('password')
print(password)
What you were trying to do works in case of POST requests where parameters are passed as form parameters and do not appear in the URL. In case you are actually developing a login API, it is advisable you use POST request rather than GET and expose the data to the user.
In case of post request, it would work as follows:
#url
http://10.1.1.1:5000/login
HTML snippet:
<form action="http://10.1.1.1:5000/login" method="POST">
Username : <input type="text" name="username"><br>
Password : <input type="password" name="password"><br>
<input type="submit" value="submit">
</form>
Route:
from flask import request
#app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
print(username)
password= request.form.get('password')
print(password)
url:
http://0.0.0.0:5000/user/name/
code:
#app.route('/user/<string:name>/', methods=['GET', 'POST'])
def user_view(name):
print(name)
(Edit: removed spaces in format string)
Use request.args.get(param), for example:
http://10.1.1.1:5000/login?username=alex&password=pw1
#app.route('/login', methods=['GET', 'POST'])
def login():
username = request.args.get('username')
print(username)
password = request.args.get('password')
print(password)
Here is the referenced link to the code.
this should work
#app.route('/login$username=<username>$password=<password>', methods=['GET', 'POST'])
def login(username, password):
# you can add stuff
return f"Username: {username}\nPassword: {password}"
It's really simple. Let me divide this process into two simple steps.
On the html template you will declare name attribute for username and password like this:
<form method="POST">
<input type="text" name="user_name"></input>
<input type="text" name="password"></input>
</form>
Then, modify your code like this:
from flask import request
#app.route('/my-route', methods=['POST'])
# you should always parse username and
# password in a POST method not GET
def my_route():
username = request.form.get("user_name")
print(username)
password = request.form.get("password")
print(password)
#now manipulate the username and password variables as you wish
#Tip: define another method instead of methods=['GET','POST'], if you want to
# render the same template with a GET request too

Categories