diff --git a/docs/conf.py b/docs/conf.py index a179fe70..f76e5ffe 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -319,6 +319,7 @@ "https://docs.python.org/": None, "flask": ("http://flask.pocoo.org/docs/latest/", None), "flask_menu": ("https://flask-menu.readthedocs.io/en/latest/", None), + "flask_breadcrumbs": ("https://flask-breadcrumbs.readthedocs.io/en/latest/", None), "invenio_assets": ("https://invenio-assets.readthedocs.io/en/latest/", None), } diff --git a/invenio_theme/__init__.py b/invenio_theme/__init__.py index 9575315f..b9231bc8 100644 --- a/invenio_theme/__init__.py +++ b/invenio_theme/__init__.py @@ -15,9 +15,9 @@ `_ HTML, CSS and JS framework. - `Error handlers` - for showing user-friendly 401, 402, 404, and 500 error pages. -- `Menu` - for basic site navigation using `Flask-Menu - `_ - +- `Menu and breadcrumbs` - for basic site navigation using `Flask-Menu + `_ and `Flask-Breadcrumbs + `_. Initialization @@ -151,7 +151,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~ The header template (``invenio_theme/header.html``) is reponsible for rendering the navigation bar (including logo, search bar, menu and login/sign up -buttons) and flash messages +buttons), flash messages and the breadcrumbs. Change the template by updating :data:`invenio_theme.config.THEME_HEADER_TEMPLATE`. @@ -165,6 +165,8 @@ * ``flashmessages`` - Displays small notification messages such as "Successfully logged in." +* ``breadcrumbs`` - Displays the breadcrumbs. + Header login section template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The header login template (``invenio_theme/header_login.html``) is responsible @@ -437,6 +439,33 @@ def init_config(app): To read more about the creation and usage of menus, see :mod:`~flask_menu`. +Breadcrumbs +~~~~~~~~~~~ +Breadcrumbs works similar to menus, just use the +:func:`~flask_breadcrumbs.register_breadcrumb` instead. + +>>> from flask_breadcrumbs import register_breadcrumb + +Using this decorator, you can specify the position of the view in a +hierarchical manner, as well as the title in the breadcrumb. By default, the +current breadcrumb is displayed inside the ``page_header`` block in the base +template. + +>>> @app.route('/part1') +... @register_breadcrumb(app, '.', 'Index') +... def part1(): +... return "" +>>> @app.route('/part2') +... @register_breadcrumb(app, '.p2', 'Part 2') +... def part2(): +... return "" +>>> @app.route('/part3') +... @register_breadcrumb(app, '.p2.p3', 'Part 3') +... def part3(): +... return "" + +To learn more about the usage of breadcrumbs, see :mod:`~flask_breadcrumbs`. + User settings ~~~~~~~~~~~~~ If your module allows your users to configure some settings, you can provide @@ -459,6 +488,8 @@ def init_config(app): menu.submenu(".settings.item1").register("settings_item_1", "Item 1", order=2) @app.route('/settings/') + @register_breadcrumb(app, '.settings', 'Settings page') + @register_menu(app, 'settings.item1', 'Item 1', order=2) def settings_item_1(): return render_template('invenio_foo/foo_settings.html') diff --git a/invenio_theme/config.py b/invenio_theme/config.py index e32e40b5..4e6a08e4 100644 --- a/invenio_theme/config.py +++ b/invenio_theme/config.py @@ -112,6 +112,9 @@ THEME_SEARCH_ENDPOINT = "/search" """The endpoint for the search bar.""" +THEME_BREADCRUMB_ROOT_ENDPOINT = "" +"""The endpoint for the Home view in the breadcrumbs.""" + THEME_SITENAME = _("Invenio") """The name of the site to be used on the header and as a title.""" diff --git a/invenio_theme/ext.py b/invenio_theme/ext.py index e37c7875..92e0f8ee 100644 --- a/invenio_theme/ext.py +++ b/invenio_theme/ext.py @@ -9,6 +9,8 @@ """Invenio standard theme.""" +from flask import Blueprint +from flask_breadcrumbs import Breadcrumbs from flask_menu import Menu from . import config @@ -24,7 +26,9 @@ def __init__(self, app=None, **kwargs): :param app: An instance of :class:`~flask.Flask`. :param \**kwargs: Keyword arguments are passed to ``init_app`` method. """ - self.app = None + self.menu_ext = Menu() + self.menu = None + self.breadcrumbs = Breadcrumbs() if app: self.app = app @@ -40,6 +44,42 @@ def init_app(self, app, **kwargs): self.menu_ext = Menu(app) app.context_processor(lambda: {"current_theme_icons": self.icons}) + # Initialize extensions + self.menu_ext.init_app(app) + self.menu = app.extensions["menu"] + self.breadcrumbs.init_app(app) + + # Register blueprint in order to register template and static folder. + app.register_blueprint( + Blueprint( + "invenio_theme", + __name__, + template_folder="templates", + static_folder="static", + ) + ) + + # Register frontpage blueprint if enabled. + if app.config["THEME_FRONTPAGE"]: + app.register_blueprint(blueprint) + + # Initialize breadcrumbs. + item = self.menu.submenu("breadcrumbs") + item.register(app.config["THEME_BREADCRUMB_ROOT_ENDPOINT"], _("Home")) + + # Register errors handlers. + app.register_error_handler(401, unauthorized) + app.register_error_handler(403, insufficient_permissions) + app.register_error_handler(404, page_not_found) + app.register_error_handler(429, too_many_requests) + app.register_error_handler(500, internal_error) + + # Register context processor + @app.context_processor + def _theme_icon_ctx_processor(): + from invenio_theme.proxies import current_theme_icons + + return dict(current_theme_icons=current_theme_icons) # Save reference to self on object app.extensions["invenio-theme"] = self diff --git a/setup.cfg b/setup.cfg index 2419d7af..4a25dad8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ python_requires = >=3.7 zip_safe = False install_requires = Flask-Menu>=1.0.0 + Flask-Breadcrumbs>=0.4.0 invenio-assets>=1.2.7 invenio-base>=1.2.5 invenio-i18n>=2.0.0 diff --git a/tests/test_invenio_theme.py b/tests/test_invenio_theme.py index 8c0da25c..75c6c6f2 100644 --- a/tests/test_invenio_theme.py +++ b/tests/test_invenio_theme.py @@ -140,6 +140,7 @@ def test_header_template_blocks(app): "brand", "navbar_inner", "navbar_right", + "breadcrumbs", "flashmessages", "navbar_nav", "navbar_search",