"""CioDashboard, a module for Chrysalio."""
from __future__ import annotations
from sys import exit as sys_exit
from os.path import dirname, join
from pyramid.config import Configurator
from chrysalio.initialize import Initialize
from chrysalio.includes.cache import cache_user_access, cache_global_item
from chrysalio.includes.cache import cache_namespace
from chrysalio.includes.modules.models import DBModule
from chrysalio.modules import Module
from chrysalio.lib.navigation import NavEntry
from chrysalio.scripts import ScriptRegistry
from .relaxng import RELAXNG_CIODASHBOARD
from .security import PRINCIPALS_CIODASHBOARD
from .menu import MENU_CIODASHBOARD
from .lib.i18n import _, translate
from .lib.dashboard import Dashboard
from .models.populate import xml2db as _xml2db, db2xml as _db2xml
from .models.dbdashboard import DBDashboard, DBDashboardUser, DBDashboardGroup
from .lib.utils import CIODASHBOARD_NS, CACHE_REGION_USER, CACHE_REGION_GLOBAL
# =============================================================================
[docs]
def includeme(configurator: Configurator | ScriptRegistry):
"""Function to include module.
:type configurator: pyramid.config.Configurator
:param configurator:
Object used to do configuration declaration within the application.
"""
# Registration
Module.register(configurator, ModuleCioDashboard)
if not isinstance(configurator, Configurator):
return
# Cache
if 'cache_user' not in configurator.registry or \
'cache_global' not in configurator.registry:
sys_exit(translate(_('*** You must register a cache manager.')))
# Permissions
configurator.include('ciodashboard.security')
# Routes
configurator.include('ciodashboard.routes')
# Translation
configurator.add_translation_dirs(join(dirname(__file__), 'Locale'))
# Views
static_dir = join(dirname(__file__), 'Static')
Initialize(configurator).add_static_views(
__package__, (
('css', join(static_dir, 'Css')),
('images', join(static_dir, 'Images'))))
configurator.scan('ciodashboard.views')
# =============================================================================
[docs]
class NavDashboards(NavEntry):
"""Entry for jobs."""
uid = 'dashboards'
label = _('Dashboards')
route = 'dashboard_index'
permission = 'dashboard-view'
# =============================================================================
[docs]
class ModuleCioDashboard(Module):
"""Class for CioDashboard module.
:param str config_ini:
Absolute path to the configuration file (e.g. development.ini).
"""
name = _('Dashboard')
implements = ('dashboard', )
dependencies = ('warehouse', )
relaxng = RELAXNG_CIODASHBOARD
xml2db = (_xml2db, )
db2xml = (_db2xml, )
_DBModule = DBModule
# -------------------------------------------------------------------------
def __init__(self, config_ini):
"""Constructor method."""
super(ModuleCioDashboard, self).__init__(config_ini)
self._dashboards = {}
self._nav_entry = NavDashboards()
# -------------------------------------------------------------------------
[docs]
def activate(self, registry, dbsession):
"""Method to activate the module.
See: :meth:`chrysalio.modules.Module.activate`
"""
# Security
if PRINCIPALS_CIODASHBOARD[0] not in registry['principals']:
registry['principals'].append(PRINCIPALS_CIODASHBOARD[0])
# Navigation
if 'navigation' in registry and 'main' in registry['navigation'] \
and self._nav_entry not in registry['navigation']['main']:
registry['navigation']['main'].insert(1, self._nav_entry)
# Menu (DEPRECATED)
if 'menu' in registry and MENU_CIODASHBOARD not in registry['menu']:
registry['menu'].insert(1, MENU_CIODASHBOARD)
# -------------------------------------------------------------------------
[docs]
def deactivate(self, registry, dbsession):
"""Method to deactivate the module.
See: :meth:`chrysalio.modules.Module.deactivate`
"""
# Security
if PRINCIPALS_CIODASHBOARD[0] in registry['principals']:
registry['principals'].remove(PRINCIPALS_CIODASHBOARD[0])
# Navigation
if 'navigation' in registry and 'main' in registry['navigation'] \
and self._nav_entry in registry['navigation']['main']:
registry['navigation']['main'].remove(self._nav_entry)
# Menu (DEPRECATED)
if 'menu' in registry and MENU_CIODASHBOARD in registry['menu']:
registry['menu'].remove(MENU_CIODASHBOARD)
# Dashboards
for dashboard_id in self._dashboards:
registry['cache_global'].clear(
namespace=cache_namespace(CIODASHBOARD_NS, dashboard_id))
self._dashboards = {}
# -------------------------------------------------------------------------
@cache_user_access(CIODASHBOARD_NS, CACHE_REGION_USER)
@classmethod
def dashboard_access(cls, request, dashboard):
"""Return a dictionary defining the authorization of the user on the
dashboard.
:type request: pyramid.request.Request
:param request:
Current request.
:type dashboard: .lib.dashboard.Dashboard
:param dashboard:
Current dashboard object.
:rtype: tuple
:return:
A tuple such as ``(True,)``.
Thanks to the decorator, the user cache ``ciodsh-<dashboard_id>``
stores the access to the dashboard.
"""
if dashboard is None:
return (None, )
# Compute rights
user_id = request.session['user']['user_id'] \
if 'user' in request.session else ''
rights = request.dbsession.query(DBDashboardUser)\
.filter_by(dashboard_id=dashboard.uid, user_id=user_id).first()
rights = rights is not None
if not rights:
groups = request.session['user']['groups'] \
if 'user' in request.session else ()
rights = request.dbsession.query(DBDashboardGroup)\
.filter_by(dashboard_id=dashboard.uid)\
.filter(DBDashboardGroup.group_id.in_(groups)).first()
rights = rights is not None
i_creator = request.has_permission('dashboard-create')
# Compute access
if dashboard.access == 'closed':
access = (True if i_creator else None, )
elif dashboard.access == 'free' or i_creator:
access = (True, )
else:
access = (rights or None, )
return access
# -------------------------------------------------------------------------
@cache_global_item(CIODASHBOARD_NS, CACHE_REGION_GLOBAL, dashboard_access)
@classmethod
def dashboard(cls, request, dashboard_id):
"""Return the dashboard with ID ``dashboard_id`` or ``None``.
:type request: pyramid.request.Request
:param request:
Current request.
:param str dashboard_id:
ID of the dashboard to return.
:rtype: :class:`.lib.dashboard.Dashboard` or ``None``
Thanks to the decorator, the global cache ``ciodsh-<dashboard_id>``
stores the current state of the dashboard.
"""
if not dashboard_id:
return None
dbdashboard = request.dbsession.query(DBDashboard).filter_by(
dashboard_id=dashboard_id).first()
if dbdashboard is None:
return None
return Dashboard(request, dbdashboard)