Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add init function #355

Merged
merged 9 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
Changes
=======

Version 3.0.0 (released 2024-03-19)

- global: remove breadcrumb support
- global: introduce shared menu
- global: preparation for compatibility with Flask v2.3.x deprecations
- refactor: current_theme_icons without application context

Version 2.5.10 (released 2024-01-28)

- installation: fix sphinx dependency
Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@
"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),
}

Expand Down
52 changes: 11 additions & 41 deletions invenio_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# This file is part of Invenio.
# Copyright (C) 2015-2024 CERN.
# Copyright (C) 2022 Graz University of Technology.
# Copyright (C) 2022-2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
Expand All @@ -15,9 +15,9 @@
<https://getbootstrap.com/>`_ HTML, CSS and JS framework.
- `Error handlers` - for showing user-friendly 401, 402, 404, and 500 error
pages.
- `Menu and breadcrumbs` - for basic site navigation using `Flask-Menu
<https://flask-menu.readthedocs.io/>`_ and `Flask-Breadcrumbs
<https://flask-breadcrumbs.readthedocs.io/>`_.
- `Menu` - for basic site navigation using `Flask-Menu
<https://flask-menu.readthedocs.io/>`_



Initialization
Expand All @@ -27,7 +27,6 @@

>>> from flask import Flask, render_template_string
>>> from invenio_theme import InvenioTheme
>>> from flask_menu import register_menu
>>> app = Flask('myapp')
>>> theme = InvenioTheme(app)

Expand Down Expand Up @@ -152,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), flash messages and the breadcrumbs.
buttons) and flash messages

Change the template by updating
:data:`invenio_theme.config.THEME_HEADER_TEMPLATE`.
Expand All @@ -166,8 +165,6 @@
* ``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
Expand Down Expand Up @@ -423,9 +420,8 @@ def init_config(app):
For instance if you want to plug an menu item in the navigation bar you can do
it like this:

>>> from flask_menu import register_menu
>>> menu.submenu(".main.myitem").register("myview2", "My item", order=1)
>>> @app.route('/myview2')
... @register_menu(app, 'main.myitem', 'My item', order=1)
... def myview2():
... return ""

Expand All @@ -441,33 +437,6 @@ 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
Expand All @@ -488,9 +457,8 @@ def init_config(app):

.. code-block:: python

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')

Expand Down Expand Up @@ -520,7 +488,9 @@ def settings_item_1():
"""

from .ext import InvenioTheme
from .shared import menu

__version__ = "3.0.0"

__version__ = "2.5.10"

__all__ = ("__version__", "InvenioTheme")
__all__ = ("__version__", "InvenioTheme", "menu")
3 changes: 0 additions & 3 deletions invenio_theme/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@
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."""

Expand Down
65 changes: 13 additions & 52 deletions invenio_theme/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,17 @@
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 Graz University of Technology.
# Copyright (C) 2022-2023 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Invenio standard theme."""

from flask import Blueprint
from flask_breadcrumbs import Breadcrumbs
from flask_menu import Menu
from invenio_i18n import lazy_gettext as _

from . import config
from .views import (
blueprint,
insufficient_permissions,
internal_error,
page_not_found,
too_many_requests,
unauthorized,
)
from .icons import ThemeIcons


class InvenioTheme(object):
Expand All @@ -34,11 +24,10 @@ 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.menu_ext = Menu()
self.menu = None
self.breadcrumbs = Breadcrumbs()
self.app = None

if app:
self.app = app
self.init_app(app, **kwargs)

def init_app(self, app, **kwargs):
Expand All @@ -48,42 +37,9 @@ def init_app(self, app, **kwargs):
"""
self.init_config(app)

# 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)
self.menu_ext = Menu(app)

app.context_processor(lambda: {"current_theme_icons": self.icons})

# Save reference to self on object
app.extensions["invenio-theme"] = self
Expand All @@ -102,8 +58,13 @@ def init_config(self, app):
# Set THEME_<name>_TEMPLATE from <name>_TEMPLATE variables if not
# already set.
for varname in _vars:
theme_varname = "THEME_{}".format(varname)
theme_varname = f"THEME_{varname}"
if app.config[theme_varname] is None:
app.config[theme_varname] = app.config[varname]

app.config.setdefault("ADMIN_BASE_TEMPLATE", config.ADMIN_BASE_TEMPLATE)

@property
def icons(self):
"""Return icons."""
return ThemeIcons(self.app.config["APP_THEME"], self.app.config["THEME_ICONS"])
59 changes: 59 additions & 0 deletions invenio_theme/icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2020 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Theme Icons."""


class ThemeIcons:
"""Defines names for theme icons used in CSS classes.

This class is used via the proxy ``current_theme_icons``, which initialize
it with the right parameters. Also, the ``current_theme_icons`` proxy is
automatically available in templates because it is added via a template
context processor.

Examples of usage:

.. code-block:: python

current_theme_icons.cogs
current_theme_icons['cogs']

Note, that the proxy is dependent on the Flask application context, thus
it can often be useful to wrap it in lazy string, to only have it evaluated
when needed:

.. code-block:: python

from invenio_i18n import LazyString
LazyString(lambda: f'<i class="{current_theme_icons.cogs}"></i>')
"""

def __init__(self, app_themes, theme_icons):
"""Initialize."""
self._app_themes = app_themes or []
self._theme_icons = theme_icons or {}

def __getattr__(self, name):
"""Attribute support for getting an icon."""
return self.__getitem__(name)

def __getitem__(self, key):
"""Get a icon for a specific theme."""
for theme in self._app_themes:
if theme in self._theme_icons:
# Icon exists for theme
if key in self._theme_icons[theme]:
return self._theme_icons[theme][key]
# Pattern exists for theme
# If an icon is not defined we create it via a pattern -
# e.g. the smeantic ui pattern is ``{} icon`` and the
# bootstrap3 pattern is ``fa fa-{} fa-fw``.
elif "*" in self._theme_icons[theme]:
return self._theme_icons[theme]["*"].format(key)
return ""
52 changes: 2 additions & 50 deletions invenio_theme/proxies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,11 @@
from flask import current_app
from werkzeug.local import LocalProxy

from .icons import ThemeIcons

current_theme_icons = LocalProxy(
lambda: ThemeIcons(
current_app.config["APP_THEME"], current_app.config["THEME_ICONS"]
)
)
Comment on lines 16 to 20
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
current_theme_icons = LocalProxy(
lambda: ThemeIcons(
current_app.config["APP_THEME"], current_app.config["THEME_ICONS"]
)
)
current_theme_icons = LocalProxy(lambda: current_app.extensions["theme"].icons)

"""Proxy to the theme icon finder."""


class ThemeIcons:
"""Defines names for theme icons used in CSS classes.

This class is used via the proxy ``current_theme_icons``, which initialize
it with the right parameters. Also, the ``current_theme_icons`` proxy is
automatically available in templates because it is added via a template
context processor.

Examples of usage:

.. code-block:: python

current_theme_icons.cogs
current_theme_icons['cogs']

Note, that the proxy is dependent on the Flask application context, thus
it can often be useful to wrap it in lazy string, to only have it evaluated
when needed:

.. code-block:: python

from speaklater import make_lazy_string
make_lazy_string(lambda: f'<i class="{current_theme_icons.cogs}"></i>')
"""

def __init__(self, app_themes, theme_icons):
"""Initialize."""
self._app_themes = app_themes or []
self._theme_icons = theme_icons or {}

def __getattr__(self, name):
"""Attribute support for getting an icon."""
return self.__getitem__(name)

def __getitem__(self, key):
"""Get a icon for a specific theme."""
for theme in self._app_themes:
if theme in self._theme_icons:
# Icon exists for theme
if key in self._theme_icons[theme]:
return self._theme_icons[theme][key]
# Pattern exists for theme
# If an icon is not defined we create it via a pattern -
# e.g. the smeantic ui pattern is ``{} icon`` and the
# bootstrap3 pattern is ``fa fa-{} fa-fw``.
elif "*" in self._theme_icons[theme]:
return self._theme_icons[theme]["*"].format(key)
return ""
13 changes: 13 additions & 0 deletions invenio_theme/shared.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: we're now establishing a naming convention for these types of "shared extensions" files. I'm aware that we have the same name in invenio-db, but e.g. the flask-cookiecutter uses extensions.py.

@ntarocco @zzacharo WDYT about the name?

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2023 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Invenio standard theme."""

from flask_menu.menu import MenuNode

menu = MenuNode("", None)
1 change: 1 addition & 0 deletions invenio_theme/templates/invenio_theme/admin_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

This file is part of Invenio.
Copyright (C) 2015-2018 CERN.
Copyright (C) 2023 Graz University of Technology.

Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
Expand Down
Loading
Loading