Skip to content

Instantly share code, notes, and snippets.

@thedod
Last active May 2, 2023 14:02
Show Gist options
  • Save thedod/eafad9458190755ce943e7aa58355934 to your computer and use it in GitHub Desktop.
Save thedod/eafad9458190755ce943e7aa58355934 to your computer and use it in GitHub Desktop.
Quick workaround for a flask-bootstrap navbar with items at the right side
# Welcome to the Flask-Bootstrap sample application. This will give you a
# guided tour around creating an application using Flask-Bootstrap.
#
# To run this application yourself, please install its requirements first:
#
# $ pip install -r sample_app/requirements.txt
#
# Then, you can actually run the application.
#
# $ flask --app=sample_app dev
#
# Afterwards, point your browser to http://localhost:5000, then check out the
# source.
from flask import Flask
from flask_appconfig import AppConfig
from flask_bootstrap import Bootstrap
from .frontend import frontend
from .nav import nav, init_custom_nav_renderer
def create_app(configfile=None):
# We are using the "Application Factory"-pattern here, which is described
# in detail inside the Flask docs:
# http://flask.pocoo.org/docs/patterns/appfactories/
app = Flask(__name__)
# We use Flask-Appconfig here, but this is not a requirement
AppConfig(app)
# Install our Bootstrap extension
Bootstrap(app)
# Our application uses blueprints as well; these go well with the
# application factory. We already imported the blueprint, now we just need
# to register it:
app.register_blueprint(frontend)
# Because we're security-conscious developers, we also hard-code disabling
# the CDN support (this might become a default in later versions):
app.config['BOOTSTRAP_SERVE_LOCAL'] = True
# We initialize the navigation as well
nav.init_app(app)
init_custom_nav_renderer(app)
return app

A quick workaround for a flask-bootstrap navbar with items at the right side, as requested at #126.

Bonus: ability to change navbar's style (navbar navbar-inverse in the example).

  • copy the flask-bootstrap sample app to a folder.
  • overwrite nav.py, __init__.py, and frontend.py with the files here.
  • create a virtual env, pip install -r requirements.txt, etc.

you should get this:

screenshot

Heads up: ExtendedNavbar has items=(...) in the constructor (unlike Navbar that receives *items).

I can make this a proper pull request (so that we don't need to tweak the renderer at __init__.py).

# This contains our frontend; since it is a bit messy to use the @app.route
# decorator style when using application factories, all of our routes are
# inside blueprints. This is the front-facing blueprint.
#
# You can find out more about blueprints at
# http://flask.pocoo.org/docs/blueprints/
from flask import Blueprint, render_template, flash, redirect, url_for
from flask_bootstrap import __version__ as FLASK_BOOTSTRAP_VERSION
from flask_nav.elements import Navbar, View, Subgroup, Link, Text, Separator
from markupsafe import escape
from .forms import SignupForm
from .nav import nav, ExtendedNavbar
frontend = Blueprint('frontend', __name__)
# We're adding a navbar as well through flask-navbar. In our example, the
# navbar has an usual amount of Link-Elements, more commonly you will have a
# lot more View instances.
nav.register_element('frontend_top', ExtendedNavbar(
title=View('Flask-Bootstrap', '.index'),
root_class='navbar navbar-inverse',
items=(
View('Home', '.index'),
View('Debug-Info', 'debug.debug_root'),
Subgroup(
'Docs',
Link('Flask-Bootstrap', 'http://pythonhosted.org/Flask-Bootstrap'),
Link('Flask-AppConfig', 'https://github.com/mbr/flask-appconfig'),
Link('Flask-Debug', 'https://github.com/mbr/flask-debug'),
Separator(),
Text('Bootstrap'),
Link('Getting started', 'http://getbootstrap.com/getting-started/'),
Link('CSS', 'http://getbootstrap.com/css/'),
Link('Components', 'http://getbootstrap.com/components/'),
Link('Javascript', 'http://getbootstrap.com/javascript/'),
Link('Customize', 'http://getbootstrap.com/customize/'),
),
),
right_items=(
Text('Using Flask-Bootstrap {}'.format(FLASK_BOOTSTRAP_VERSION)),
View('SignUp', '.example_form'),
)
))
# Our index-page just shows a quick explanation. Check out the template
# "templates/index.html" documentation for more details.
@frontend.route('/')
def index():
return render_template('index.html')
# Shows a long signup form, demonstrating form rendering.
@frontend.route('/example-form/', methods=('GET', 'POST'))
def example_form():
form = SignupForm()
if form.validate_on_submit():
# We don't have anything fancy in our application, so we are just
# flashing a message when a user completes the form successfully.
#
# Note that the default flashed messages rendering allows HTML, so
# we need to escape things if we input user values:
flash('Hello, {}. You have successfully signed up'
.format(escape(form.name.data)))
# In a real application, you may wish to avoid this tedious redirect.
return redirect(url_for('.index'))
return render_template('signup.html', form=form)
from flask_nav import Nav
from dominate import tags
from flask_nav.elements import NavigationItem
from flask_bootstrap.nav import BootstrapRenderer, sha1
class ExtendedNavbar(NavigationItem):
def __init__(self, title, root_class='navbar navbar-default', items=[], right_items=[]):
self.title = title
self.root_class = root_class
self.items = items
self.right_items = right_items
class CustomBootstrapRenderer(BootstrapRenderer):
def visit_ExtendedNavbar(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'] = node.root_class
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))
if node.right_items:
right_bar_list = bar.add(tags.ul(_class='nav navbar-nav navbar-right'))
for item in node.right_items:
right_bar_list.add(self.visit(item))
return root
def init_custom_nav_renderer(app):
# For some reason, this didn't seem to do anything...
app.extensions['nav_renderers']['bootstrap'] = (__name__, 'CustomBootstrapRenderer')
# ... but this worked. Weird.
app.extensions['nav_renderers'][None] = (__name__, 'CustomBootstrapRenderer')
nav = Nav()
@Trif4
Copy link

Trif4 commented Jan 13, 2017

This saved me a lot of time—thank you!

@willww64
Copy link

Thanks a lot!

@nitzanel
Copy link

nitzanel commented Nov 8, 2017

I have found it really usefull!
Can you make a pull request and add it to the flask-nav package? Im sure they would appreciate it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment