Flask-Nav with dynamic 'secondary' navbar

2.5k views Asked by At

Flask-Nav allows dynamic construction; however, I cannot figure out how to do this from passing a dictionary or list to the function to build the Navbar.

@nav.navigation
def top_nav():
    # ...

According to the docs, this is called every time Navbar is needed; however, you can do something like top_nav(items) or anything like this.

In my Jinja2 templates, I create a dictionary with my submenu for that page (which I want to do as a side menu along with the top fixed navbar). I know it can be done in a way with macros but I was curious if there was a way to use Flask-Nav to create the secondary Navbar with dynamically passed items.

2

There are 2 answers

0
Erenor Paz On BEST ANSWER

Well, I'm really, really late for this question, but I hope this could help people passing by. I don't exactly understand how you want to proceed (a bit of code would have been helpful), but a dictionary is just a collection of items you could parse and add to the navbar using the method I'm about to describe. These are the steps I used to create a dynamic navbar for the aside menu in a project (navbar elements are added by the various modules):

  1. Create navbar: contextbar = Navbar('Context menu') (this requires the flask-nav extension, of course)
  2. Register element to the navbar: nav.register_element('contextbar', contextbar)
  3. Init app within the create_app() function (it is a standard Flask factory construct): nav.init_app(app)
  4. For each package/module I create, I add the following to the __init__.py file ("application" is the name of my app, of course):

    from flask import current_app as app
    from flask_nav.elements import View
    from application import contextbar 
    
  5. Finally, still into the __init__.py file of the modules, I use the @app.before_first_request decorator to make sure the navbar additions are made only once (and not for each request) to avoid duplicate items and the code is as follows

    # Add elements to the contextual navigation bar
    @app.before_first_request
    def before_first_request():
        contextbar.items.append(View('Meow', 'path.to.blueprint.meow'))
        contextbar.items.append(View('Bark', 'path.to.blueprint.bark'))
    

This way, each module can add their own menu items (even if that particular module's route is not requested). For the sake of completeness, in the jinja2 template, you would only need to add the code {{nav.contextbar.render()}} where you want the navbar to render.

I must admit I'm fairly new to Python, Flask and friends, but this is what I did in my (test/tutorial/example) project and it's working well.

UPDATE

I had some issues with login/logout, because before logging in, the user is not authenticated and the navbar will display "login", but after that the navbar does not change (the before_each_request won't fire again for the same "session") and this is not good. So, I switched to before_request with a little caveaut:

  1. Same as before
  2. Same as before
  3. Same as before
  4. Same as before
  5. Into the routes.py of my main application I add the following code to initialize the nav bar and start from "no items"

    @app.before_request
    def before_request():
        # Only perform adding items to contextbar for non-static GET requests
        if request.method == 'GET' and request.endpoint not in ('static',):
            # Reset bar's items to avoid duplicates after each request
            contextbar.items = []
    
            # Add first link to the top bar
            contextbar.items.append(View('Home', 'homepage'))
    
  6. Into each __init__.py file of the modules, I add the same code from point "5", but without the nav bar items reset:

    @app.before_request
    def before_request():
        # Only perform adding items to contextbar for non-static GET requests (POST requests usually do not need nav bars)
        if request.method == 'GET' and request.endpoint not in ('static',):
            # This is added to make sure these links are added only if the user is logged in
            if current_user.is_authenticated:
                contextbar.items.append(View('Bark', 'path.to.blueprint.bark'))
    
            # This link will be added to the navbar for logged in and out users
            contextbar.items.append(View('Meow', 'path.to.blueprint.meow'))
    
0
Matteo Boscolo On

I did in this way

from flask_nav import Nav
from flask_nav.elements import Navbar, View
from flask_login import current_user

nav = Nav()

@nav.navigation()
def mynavbar():
    if current_user.is_authenticated:
        return Navbar(
                    'Title',
                    View('Home', 'index'  ),
                    View('Servizi', 'servizi'  ),
                    View('Logout', 'logout'  )
                )
    else:
        return Navbar(
            'Title',
            View('Home', 'index'  ),
            View('Login', 'login'  )
        )

so if the user is logged I show more items