Search code examples
sessionflaskflask-loginflask-navflask-session

Accessing Flask Session variables from Flask Navigation for dynamic navigation menu


I want to have a dynamic navigation menu that shows "Login" if the user is not currently logged on, and "Logout" if the user is logged in.

I'm using code similar to the following:

import flask
import flask_nav
import flask_nav.elements as fne

frontend = flask.Blueprint('frontend', __name__)

application = flask.Flask(__name__)
mySess = flask_session.Session()

flask_appconfig.AppConfig(application)
flask_bootstrap.Bootstrap(application)
application.register_blueprint(frontend)
application.config['BOOTSTRAP_SERVE_LOCAL'] = True
application.config['SSL'] = True
application.secret_key = SECRET_KEY
application.config['SESSION_TYPE'] = SESSION_TYPE

mySess.init_app(application)

nav = flask_nav.Nav()

class CustomRenderer(flask_bootstrap.nav.BootstrapRenderer):
    def visit_Navbar(self, node):
        nav_tag = super(CustomRenderer, self).visit_Navbar(node)
        nav_tag['class'] = 'navbar navbar-default navbar-fixed-top'
        return nav_tag

flask_nav.register_renderer(application, 'custom', CustomRenderer)

nav.init_app(application)

@nav.navigation()
def top_nav():
    items = [ fne.View('Home',              '.index') ]

    if 'google_token' in flask.session:
        items.append(fne.View('Logout',         '.logout'))
    elif 'auth_url' in flask.session:
        items.append(fne.View('Login',          flask.session['auth_url']))
    else:
        items.append(fne.View('Login',          '.login'))

    items.append(fne.View('About',              '.about'))
    items.append(fne.View('Contact',            '.contact'))
    items.append(fne.View('Shop',               '.shop'))
    items.append(fne.View('Help & Feedback',    '.help'))

    return fne.Navbar('', *items)

nav.register_element('frontend_top', top_nav())

Unfortunately, the Flask session variables are out-of-scope for the nav object, so I cannot access flask.session from within top_nav.

I have the same difficulty when I make any stand-alone function for accessing flask-session outside of my application, for example

def user_is_logged_in():
    if 'google_token' in flask.session:
        return True
    else:
        return False
    return False

These functions give the expected error "RuntimeError: Working outside of request context."

I do NOT want to use a global variable in my application.py code for the user for security reasons and so multiple people can access the application at the same time without errors. I believe the SESSION should be storing whether the user is currently logged in or not.

How do I get my flask_nav.Nav() to see my application's flask.session?


Solution

  • flask_nav registers extensions at a stage in the application lifecycle before requests start to be processed.

    You can overwrite the registration of the template_global to later when a request context exists in the application.

    Factor out common navigation items.

    nav = Nav()
    
    # registers the "top" menubar
    navitems = [
        View('Widgits, Inc.', 'index'),
        View('Our Mission', 'about'),
    ]
    

    Set a function to return an appropriate View/Link based on value in session

    def with_user_session_action(items):
        return (
            items 
            + [ View('Login', 'login') if not session.get('logged') else View('Logout', 'logout')]
        )
    

    Use this in a function that delegates to nav.register_element

    def register_element(nav, navitems):
        navitems = with_user_session_action(navitems)
        return nav.register_element('top', 
            Navbar(*navitems)
        )
    

    Supersede render_template to always pass down the computed navigation

    _render_template = render_template
    
    def render_template(*args, **kwargs):
        register_element(nav, navitems)
    
        return _render_template(*args, nav=nav.elems, **kwargs)
    

    Bonus:

    You can cache the computed nav for login/logout so that it isn't only computed once for each case.