# Copyright (c) 2022 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
from typing import Set
from flask import render_template
[docs]class Module:
"""Base class for modules, the building blocks of the dashboard.
A module turns select information from the job directory or project
directory into cards displayed on the dashboard. The cards pull content from
the job directory or project directory depending on whether the module
``context`` is ``'JobContext'`` or ``'ProjectContext'``.
:param name: Name of the module for card titles.
:type name: str
:param context: Context in which this module's cards will be displayed, either
:code:`'JobContext'` *or* :code:`'ProjectContext'`.
:type context: str
:param template: Path to a template file for this module's cards (e.g.
:code:`cards/my_module.html`, without the template directory prefix
:code:`templates/`).
:type template: str
:param enabled: Whether the module's cards will be displayed. (default: :code:`True`)
:type enabled: bool
**Custom modules:** User-defined module classes should be a subclass of
:py:class:`~.Module` and define the function :py:meth:`~.Module.get_cards`.
Template files are written in HTML/Jinja-compatible syntax.
See `this example <https://github.com/glotzerlab/signac-dashboard/tree/main/examples/custom-modules>`_.
**Module assets:** If a module requires scripts or stylesheets to be
included for its content to be rendered, they must be handled by the
callback :py:meth:`.register`. For example, a module inheriting from
the base :py:class:`signac_dashboard.Module` class may implement this by
overriding the default method as follows:
.. code-block:: python
def register(self, dashboard):
assets = ['js/my-script.js', 'css/my-style.css']
for asset in assets:
dashboard.register_module_asset({
'file': 'templates/my-module/{}'.format(asset),
'url': '/module/my-module/{}'.format(asset)
})
Then, when the module is active, its assets will be included and a
route will be created that returns the asset file.
**Module routes:** The callback :py:meth:`.register` allows modules
to implement custom routes, such as methods that should be triggered by
:code:`POST` requests or custom APIs. For example, a module inheriting from
the base :py:class:`signac_dashboard.Module` class may implement this by
overriding the default method as follows:
.. code-block:: python
def register(self, dashboard):
@dashboard.app.route('/module/my-module/update', methods=['POST'])
@flask_login.login_required
def my_module_update():
# Perform update
return "Saved."
""" # noqa: E501
_supported_contexts: Set[str] = set()
def __init__(self, name, context, template, enabled=True):
self._module = self.__module__
self._moduletype = self.__class__.__name__
self.name = name
if len(self._supported_contexts) == 0:
raise ValueError(f"{self._moduletype} is not supported by any contexts.")
if context not in self._supported_contexts:
raise RuntimeError(
f"{self._moduletype} does not support the {context}, only "
f"{self._supported_contexts}."
)
self.context = context
self.template = template
self.enabled = enabled
[docs] def get_cards(self):
"""Return this module's cards for rendering.
The cards are returned as a list of dictionaries with keys
:code:`'name'` and :code:`'content'`.
:returns: List of module cards.
:rtype: list
"""
return [{"name": self.name, "content": render_template(self.template)}]
[docs] def enable(self):
"""Enable this module."""
self.enabled = True
[docs] def disable(self):
"""Disable this module."""
self.enabled = False
[docs] def toggle(self):
"""Toggle this module."""
self.enabled = not self.enabled
[docs] def register(self, dashboard):
"""Register this module with the dashboard.
This method is a callback used to register assets and routes, as well
as any other initialization that accesses or modifies the dashboard.
:param dashboard: The dashboard invoking this callback method.
:type dashboard: :py:class:`signac_dashboard.Dashboard`
"""
pass