readyplayer77
readyplayer77

Reputation: 2159

Flask - Perform Similar Actions for Multiple Functions/Endpoints

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.

Sample: logged in

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

Answers (1)

arshovon
arshovon

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.

  • I passed user_info to the template. If user_info is not available in session, None will be sent.
  • I checked if the 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:

  • View after login: logged in view

  • View before login: view before login

Alternative approach:

  1. Use view decorators. Details can be found in official documentation
  2. Use a login manager package, e.g. Flask-Login

Upvotes: 1

Related Questions