Reputation: 2159
I have a flask application with multiple functions that I would like to return the same arguments to create a navbar that shows the user's information. I'm making a navbar that's similar to the top navigation bar from StackOverflow - whichever page you visit on this site, it shows the user's profile picture, reputation, number of badges, etc.
I would like to implement this functionality in my flask application. When the user logs in, I will store the user_info in a flask session
. However, it appears that I have to repeat the same process of checking if "user_info" exists in session for every single function, so that for each page the user visits, the navbar will be rendered correctly.
@app.route('/')
def index():
...
if "user_info" in session:
return render_template("index.html", user_info=session["user_info"], data=data)
else:
return render_template("index.html", data=data)
@app.route('/info')
def info():
...
if "user_info" in session:
return render_template("index.html", user_info=session["user_info"], data=data, info=info)
else:
return render_template("index.html", data=data, info=info)
In HTML I created a parent Jinja template with the navbar, and each child page (index and info) inherits the navbar.
Jinja Parent Template:
<nav>
{% if user_info is defined %}
<li>
<img class="rounded-circle profile" width="30" src="{{ user_info['picture'] }}" alt="User">
</li>
<li>
<span>{{ username }}</span>
</li>
{% else %}
<li class="nav-item">
<a href="/login"><button>Sign In</button></a>
</li>
{% endif %}
</nav>
Is it possible to simplify this, so that I wouldn't have to write the same code for each endpoint?
EDIT: based on @arsho's answer, I returned None
if the user is not logged in, and in Jinja I changed the if-statement to {% if user_info is not none %}
- but I would still like to simplify the repeated render_template(user_info=session.get("user_info", None), data=data)
for each function, is there a way to achieve this? Thanks in advance!
UPDATE: I finally figured it out! The templating decorator from the flask documentation helped me a lot!
What I did was I created their sample templated
function and made a slight modification to return the specific arguments I need for my application:
def templated(template=None):
def decorator(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
template_name = template
if template_name is None:
template_name = request.endpoint \
.replace('.', '/') + '.html'
ctx = f(*args, **kwargs)
if ctx is None:
ctx = {}
elif not isinstance(ctx, dict):
return ctx
return render_template(template_name,
data=data, # added argument
user_info=session.get('user_info', None), # added argument
**ctx)
return decorated_function
return decorator
and for each function, I simply added the view decorator templated('[TEMPLATE_NAME]')
and returned a dict
object for the additional arguments I would like to return, and it worked perfectly! Hope this would also help others with the same issue.
Now the endpoints look like:
@app.route('/')
@templated('index.html')
def index():
...
# No need to return any additional arguments - already specified in the templated view decorator
@app.route('/info')
@templated('info.html')
def info():
...
return dict(info=info) # Additional argument: info; user_info and data already included in view decorator
Jinja Parent Template:
<nav>
{% if user_info is not none %}
<li>
<img class="rounded-circle profile" width="30" src="{{ user_info['picture'] }}" alt="User">
</li>
<li>
<span>{{ username }}</span>
</li>
{% else %}
<li class="nav-item">
<a href="/login"><button>Sign In</button></a>
</li>
{% endif %}
</nav>
Upvotes: 3
Views: 831
Reputation: 13651
You do not need to check if a user is logged in to the application in every view function. Here is my approach to removing the repetitive check of a logged user in each view method.
user_info
to the template. If user_info
is not available in session
, None
will be sent.user_info
value is not None
in template. If it contains value, then show the user_info
.app.py
:
from flask import Flask, session, redirect, url_for, request, render_template
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
data = "Index Page Data"
return render_template("index.html", user_info=session.get("user_info", None), data=data)
@app.route('/login', methods=['GET', 'POST'])
def login():
session["user_info"] = {"username": "dummy", "user_email": "[email protected]"}
return 'Login Successful. <a href="'+url_for('index')+'">Click here to go to home</a>.'
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('user_info', None)
return redirect(url_for('index'))
layout.html
:
<!doctype html>
<html>
<head>
<title>Flask Session Example</title>
</head>
<body>
<div id="header">{% block header %}{% endblock %}</div>
<div id="content">{% block content %}{% endblock %}</div>
</body>
</html>
index.html
:
{% extends "layout.html" %}
{% block header %}
<h1>
User Data:
{% if user_info is not none %}
{{ user_info["username"] }}, {{ user_info["user_email"] }}
{% else %}
Not Found
{% endif %}
</h1>
{% endblock %}
{% block content %}
{% if data %}
{{data}}
{% endif %}
{% endblock %}
Output:
Alternative approach:
Upvotes: 1