Search code examples
pythonflasknavigation

Flask Nav / Navigation Alternative for Python 3.10


I am currently making a Flask project, but I need the ability to dynamically generate nav bars that can be different based on a variable when the render_template() returns the page.

More specifically, I am aiming for a way to dynamically create/ return a nav bar when 1. A user logs in change Login to an Accout menu 2. A self-hierarchy of categories (parent categories FK ref in child node IF parent is root then FK is root) on a menu.

I am still new to Flask, but I have been researching this matter and found Flask-Nav or Flask Navigation. Both of which do not work for me because I am running on Python 3.10 and both of those libraries only are supported until Python 3.9. The reason for this is because Python 3.10 does not support mutable sequences.

lass ItemCollection(collections.MutableSequence, AttributeError: module 'collections' has no attribute 'MutableSequence'

Is there any other libraries I can use or methods. I am more than down to generate my own HTML and return that, I am stuck on how to per say return the nav html code on top of the actual template and put that code into the nav location near the top, but below the head info.

string nav = '<NAV class='NavBar'><LINKS></NAV>'
return render_template('index.html', nav)

Solution

  • The way to approach this is probably with a context processor which can inject variables into every route...

    from flask import Flask, render_template, url_for
    app = Flask(__name__)
    
    @app.context_processor 
    def inject_dict_for_all_templates():
        """ Build the navbar and pass this to Jinja for every route
        """
        # Build the Navigation Bar
        nav = [
        {"text": "Homepage", "url": url_for('index')},
        {"text": "About Page", "url": url_for('about')},
        {
            "text": "More",
            "sublinks": [
                {"text": "Stack Overflow", "url": "https://stackoverflow.com"},
                {"text": "Google", "url": "https://google.com/"},
            ],
        },
        # {'text':'Github','url':'https://github.com/'}
        ]
    
        # sticking with the Flask login example
    
        if current_user.is_authenticated:
            # Show the user's name
            nav.append({'text': f'Logged in as {current_user.name}', 'url': url_for('profile')})
        else:
            # Show the login link
            nav.append({'text': 'Login', url_for('login')})
    
        return dict(navbar = nav)
    

    Of course, here the url_for function is used to generate some of the links, so you'd need those corresponding routes also...

    @app.route('/')
    def index(): return render_template('index.html')
    
    @app.route('/about')
    def about(): return render_template('about.html')
    

    If you're using something like Flask-Login and have access to a currentuser variable, you'd probably be able to include this somewhere within that nav list of dictionaries. (perhaps @NoCommandLine's answer has better info on the variable names)

    This is compatible with Bootstrap's navbar component for which the template code would look like this...

            <nav class="navbar navbar-expand-lg navbar-light bg-light">
              <a class="navbar-brand" href="#">{{config['SITE_TITLE']}}</a>
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
              </button>
    
              <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav mr-auto">
                  
                 {% for item in navbar %}
                    {% if item.sublinks %}
                      <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                          {{item.text}}
                        </a>
    
                        <div class="dropdown-menu" aria-labelledby="navbarDropdown">
    
                            {% for sublink in item.sublinks %}
                                  <a class='dropdown-item' href="{{sublink.url}}">{{sublink.text}}</a>
                            {% endfor %}
                        </div>
                      </li>
    
                  {% elif item.url %}
                      <li class="nav-item">
                        <a class="nav-link" href="{{item.url}}">{{item.text}}</a>
                      </li>
                  {% endif %}
                  {% endfor %}
                  </ul>
    
              </div>
            </nav>
    
    

    When rendered, this gives you something like:

    nav screencap

    I have created a gist which contains the base and index template, along with the correct CDN links to make this work.