精彩文章免费看

Flask 实现导航栏

使用 Bootstrap

使用 Bootstrap 实现导航非常简单。鉴于前台界面的导航并不在 Flask 技术体系中,所以本文不打算对 Bootstrap 的细节进行探讨,仅演示基本要点。大家可自行参考下面的文章:

  • Bootstrap 导航栏
  • Bootstrap4 导航栏
  • 比如,一个最简单的 Flask 程序中,下面的代码就实现了一个漂亮的导航栏:

    <!DOCTYPE html>
        <title>Bootstrap 导航</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
        <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
        <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    </head>
        <nav class="navbar navbar-default">
            <div class="container">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"
                        aria-expanded="false">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand">
                        <img style="max-width:30px;margin-top:-6px;" class="logo" src="{{ url_for('static',filename='images/logo.jpg') }}">
                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">                    
                        <li class="active">
                            <a href="#">首页
                                <span class="sr-only">(current)</span>
                            <a href="#">发布问答</a>
                    <form class="navbar-form navbar-left">
                        <div class="form-group">
                            <input type="text" class="form-control" placeholder="Key Words">
                        <button type="submit" class="btn btn-default">搜索</button>
                    </form>
                    <ul class="nav navbar-nav navbar-right">
                            <a href="#">登录</a>
                            <a href="#">注册</a>
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">友情链接
                                <span class="caret"></span>
                            <ul class="dropdown-menu">
                                    <a href="mailto:dummy@qq.com">联系我</a>
                                    <a href="http://flask.pocoo.org" target="_blank">Flask官网</a>
                                    <a href="https://www.python.org/">Python官网</a>
                                <li role="separator" class="divider"></li>                          
                                <li role="separator" class="divider"></li>
                                    <a href="https://www.google.com.hk" target="_blank">Google Search</a>
                <!-- /.navbar-collapse -->
            <!-- /.container-fluid -->
    </body>
    </html>
    

    使用 flask-nav

    flask 插件 flask-nav 实现了使用 python 语言来操作 html 标签,并且实现一个简单的渲染器对这些标签进行渲染。flask-bootstrap 也支持 flask-nav, 并且实现了一个渲染器 -- BootstrapRenderer。下面说说 flask-nav 的用法。

    安装:pip install flask-nav

    使用 flask-nav 需要 4 步:

    # 导入 模块
    from flask_nav import Nav
    from flask_nav.elements import *
    # 1. 实例化 Nav
    nav = Nav()
    # 2. 注册 navigation item
    nav.register_element('top', Navbar(
        View('Home.', 'index'),
        Subgroup(
            'Products',
            View('Wg240-Series', 'products', product='wg240'),
            View('Wg250-Series', 'products', product='wg250'),
            Separator(),
            Text('Discontinued Products'),
            View('Wg10X', 'products', product='wg10x'),
        Link('Tech Support', 'http://techsupport.invalid/widgits_inc'),
        View('About', 'about')
    # 3. 初始化 app
    nav.init_app(app)
    

    第 4 步:在 html 文件中渲染 html tag:

    {{nav.top.render()}}
    

    我们先来看看效果, 先给出完整代码:

    # app.py
    from flask import Flask, render_template
    from flask_nav import Nav
    from flask_nav.elements import *
    app = Flask(__name__)
    nav = Nav()
    # registers the "top" menubar
    nav.register_element('top', Navbar(
        View('Home', 'index'),
        Subgroup(
            'Products',
            View('Wg240-Series', 'products', product='wg240'),
            View('Wg250-Series', 'products', product='wg250'),
            Separator(),
            Text('Discontinued Products'),
            View('Wg10X', 'products', product='wg10x'),
        Link('Tech Support', 'http://techsupport.invalid/widgits_inc'),
        View('About', 'about')
    nav.init_app(app)
    @app.route('/')
    def index():
        return render_template('index.html')
    @app.route('/about')
    def about():
        return 'This is the ABOUT page'
    @app.route('/products/<product>')
    def products(product):
        return 'product: ' + str(product)
    if __name__ == '__main__':
        app.run()
    

    index.html 文件:

    <title> Flask navigation using flask-nav </title> </head> {{nav.top.render()}} </body> </html>

    运行 Flask app的界面效果如下:

  • View: 对应到 html 超链接,需要 Flask 提供视图函数
  • Link: 对应到普通的 html 超链接
  • Subgroup: 用于对 Navigation item 进行分组
  • Navbar: 通常作为顶层 item
  • 这些元素都是使用 Python 的类来定义的,class diagram 如下:

    在 flask-nav 提供的渲染器中,这些 item 是这样渲染成 tag 的:

    class SimpleRenderer(Renderer):
        def __init__(self, **kwargs):
            self.kwargs = kwargs
        def visit_Link(self, node):
            return tags.a(node.text, href=node.get_url())
        def visit_Navbar(self, node):
            kwargs = {'_class': 'navbar'}
            kwargs.update(self.kwargs)
            cont = tags.nav(**kwargs)
            ul = cont.add(tags.ul())
            for item in node.items:
                ul.add(tags.li(self.visit(item)))
            return cont
        def visit_View(self, node):
            kwargs = {}
            if node.active:
                kwargs['_class'] = 'active'
            return tags.a(node.text,
                          href=node.get_url(),
                          title=node.text,
                          **kwargs)
        def visit_Subgroup(self, node):
            group = tags.ul(_class='subgroup')
            title = tags.span(node.title)
            if node.active:
                title.attributes['class'] = 'active'
            for item in node.items:
                group.add(tags.li(self.visit(item)))
            return tags.div(title, group)
        def visit_Separator(self, node):
            return tags.hr(_class='separator')
        def visit_Text(self, node):
            return tags.span(node.text, _class='nav-label')
    

    flask-bootstrap 对 flask-nav 的支持

    下面我们对代码稍作改变,在 app.py 中加入 flask-bootstrap 内容:

    from flask_bootstrap import Bootstrap
    app = Flask(__name__)
    Bootstrap(app)
    

    然后将 index.html 从 Bootstrap/base.html 继承:

    {% extends 'bootstrap/base.html' %}
    {% block content %}
        {{nav.top.render()}}
    {% endblock %}
    

    再次运行 app,index.html 页面的效果变成了很漂亮的导航条:

    flask-bootstrap 使用了自定义的 BootstrapRenderer 进行渲染。以下是 BootstrapRenderer 的主要代码:

    class BootstrapRenderer(Visitor):
        def __init__(self, html5=True, id=None):
            self.html5 = html5
            self._in_dropdown = False
            self.id = id
        def visit_Navbar(self, node):
            # create a navbar id that is somewhat fixed, but do not leak any
            # information about memory contents to the outside
            node_id = self.id or sha1(str(id(node)).encode()).hexdigest()
            root = tags.nav() if self.html5 else tags.div(role='navigation')
            root['class'] = 'navbar navbar-default'
            cont = root.add(tags.div(_class='container-fluid'))
            # collapse button
            header = cont.add(tags.div(_class='navbar-header'))
            btn = header.add(tags.button())
            btn['type'] = 'button'
            btn['class'] = 'navbar-toggle collapsed'
            btn['data-toggle'] = 'collapse'
            btn['data-target'] = '#' + node_id
            btn['aria-expanded'] = 'false'
            btn['aria-controls'] = 'navbar'
            btn.add(tags.span('Toggle navigation', _class='sr-only'))
            btn.add(tags.span(_class='icon-bar'))
            btn.add(tags.span(_class='icon-bar'))
            btn.add(tags.span(_class='icon-bar'))
            # title may also have a 'get_url()' method, in which case we render
            # a brand-link
            if node.title is not None:
                if hasattr(node.title, 'get_url'):
                    header.add(tags.a(node.title.text, _class='navbar-brand',
                                      href=node.title.get_url()))
                else:
                    header.add(tags.span(node.title, _class='navbar-brand'))
            bar = cont.add(tags.div(
                _class='navbar-collapse collapse',
                id=node_id,
            bar_list = bar.add(tags.ul(_class='nav navbar-nav'))
            for item in node.items:
                bar_list.add(self.visit(item))
            return root
        def visit_Text(self, node):
            if not self._in_dropdown:
                return tags.p(node.text, _class='navbar-text')
            return tags.li(node.text, _class='dropdown-header')
        def visit_Link(self, node):
            item = tags.li()
            item.add(tags.a(node.text, href=node.get_url()))
            return item
        def visit_Separator(self, node):
            if not self._in_dropdown:
                raise RuntimeError('Cannot render separator outside Subgroup.')
            return tags.li(role='separator', _class='divider')
        def visit_Subgroup(self, node):
            if not self._in_dropdown:
                li = tags.li(_class='dropdown')
                if node.active:
                    li['class'] = 'active'
                a = li.add(tags.a(node.title, href='#', _class='dropdown-toggle'))
                a['data-toggle'] = 'dropdown'
                a['role'] = 'button'
                a['aria-haspopup'] = 'true'
                a['aria-expanded'] = 'false'
                a.add(tags.span(_class='caret'))
                ul = li.add(tags.ul(_class='dropdown-menu'))
                self._in_dropdown = True
                for item in node.items:
                    ul.add(self.visit(item))
                self._in_dropdown = False
                return li
            else:
                raise RuntimeError('Cannot render nested Subgroups')
        def visit_View(self, node):
            item = tags.li()
            item.add(tags.a(node.text, href=node.get_url(), title=node.text))
            if node.active:
                item['class'] = 'active'
            return item
    

    init_app() 中将 flask-nav 替换成 BootsrapRenderer:

    # setup support for flask-nav
    renderers = app.extensions.setdefault('nav_renderers', {})
    renderer_name = (__name__ + '.nav', 'BootstrapRenderer')
    renderers['bootstrap'] = renderer_name
    # make bootstrap the default renderer
    renderers[None] = renderer_name
    
  • 模板的继承及Bootstrap实现导航条
  • 本文源代码

  • Flask-nav-01
  • Flask-nav-02
  •