"""Panel class."""
from __future__ import annotations
from chameleon import PageTemplateFile
from pyramid.registry import Registry
from pyramid.request import Request
from pyramid.asset import abspath_from_asset_spec
from ..lib.utils import tostr
from ..lib.form import get_action, Form
from ..lib.navigation import NavPanel
from ..helpers.literal import Literal
from ..includes.themes import theme_static_prefix
from .i18n import _
PANEL_ITEM_PREFIX = 'panel:'
# =============================================================================
[docs]
class Panel():
"""Class to manage side panel.
:param list area: (optional)
List of route names which determine the area where the panel is
visible.
"""
uid: str = ''
label: str = ''
nav_location: tuple[str | None, int] = (None, 0) # (menu_id, index)
nav_button_class: str = ''
nav_entry = NavPanel
template = 'chrysalio:Templates/panel.pt'
css = ()
javascripts = ()
constants: dict = {}
need_form: bool = False
can_reopen: bool = False
area: tuple[str, ...] = ()
# -------------------------------------------------------------------------
def __init__(self, area: tuple[str, ...] | None = None):
"""Constructor method."""
if not self.label:
self.label = self.uid
if area is not None:
self.area = area
# -------------------------------------------------------------------------
[docs]
@classmethod
def register(
cls,
registry: Registry,
panel_class,
area: tuple[str, ...] | None = None) -> Panel | None:
"""Method to register the panel and possibly add it to a navigation
menu.
:type registry: pyramid.registry.Registry
:param registry:
Application registry.
:param panel_class:
Panel class.
:param list area: (optional)
List of route names which determine the area where the panel is
visible.
:param bool add2systray: (default=True)
If ``True`` add the panel to systray.
:rtype: :class:`.lib.panel.Panel` or ``None``
"""
if not panel_class.uid:
return None
if 'panels' not in registry:
registry['panels'] = {}
if panel_class.uid in registry['panels']:
return registry['panels'][panel_class.uid]
registry['panels'][panel_class.uid] = panel_class(area)
if panel_class.nav_location[0] is None \
or not panel_class.nav_button_class:
return registry['panels'][panel_class.uid]
if panel_class.nav_location[0] not in registry['navigation']:
registry['navigation'][panel_class.nav_location[0]] = []
registry['navigation'][panel_class.nav_location[0]].insert(
panel_class.nav_location[1],
panel_class.nav_entry(
panel_class.uid, panel_class.label,
panel_class.nav_button_class, area))
return registry['panels'][panel_class.uid]
# -------------------------------------------------------------------------
[docs]
@classmethod
def has_open_panel(cls, request: Request) -> str | None:
"""Return ``'cioHasOpenPanel'`` if almost one panel is open.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: class:`str` or ``None``
"""
if 'panels' not in request.registry or 'panels' not in request.session:
return None
for panel in request.registry['panels'].values():
if panel.is_open(request):
return 'cioHasOpenPanel'
return None
# -------------------------------------------------------------------------
[docs]
def is_open(self, request: Request) -> bool:
"""``True`` if the panel is open.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: bool
"""
if self.area and (request.matched_route is None
or request.matched_route.name not in self.area):
return False
self._prepare_session(request)
return request.session['panels'][self.uid]['is_open']
# -------------------------------------------------------------------------
[docs]
def was_open(self, request: Request) -> bool:
"""``True`` if the panel was previously open.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: bool
"""
self._prepare_session(request)
return request.session['panels'][self.uid]['was_open']
# -------------------------------------------------------------------------
[docs]
def open(self, request: Request):
"""Open the panel and memorize the state.
:type request: pyramid.request.Request
:param request:
Current request.
"""
if 'panel' in request.GET:
del request.GET['panel']
self._prepare_session(request)
request.session['panels'][self.uid]['was_open'] = \
request.session['panels'][self.uid]['is_open']
request.session['panels'][self.uid]['is_open'] = True
request.session['panels'][self.uid]['can_reopen'] = False
if 'panels' in request.registry:
for other in request.registry['panels'].values():
if other.uid != self.uid:
other.close(request, self.can_reopen)
# -------------------------------------------------------------------------
[docs]
@classmethod
def reopen_panel(cls, request: Request) -> Panel | None:
"""Return a possible panel which can be reopened.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: .lib.panel.Panel
"""
if 'panels' not in request.registry or 'panels' not in request.session:
return None
for panel in request.registry['panels'].values():
if panel.can_reopen and panel.uid in request.session['panels'] \
and (request.session['panels'][panel.uid]['can_reopen'] or
request.session['panels'][panel.uid]['is_open']):
return panel
return None
# -------------------------------------------------------------------------
[docs]
@classmethod
def reopen(cls, request: Request):
"""Reopen a panel.
:type request: pyramid.request.Request
:param request:
Current request.
"""
if 'panels' not in request.registry or 'panels' not in request.session:
return
for panel in request.registry['panels'].values():
if panel.can_reopen and panel.uid in request.session['panels'] \
and request.session['panels'][panel.uid]['can_reopen'] \
and panel.refresh(request):
panel.open(request)
return
# -------------------------------------------------------------------------
[docs]
def refresh(self, request: Request) -> bool:
"""Try to refresh the content of the panel.
:type request: pyramid.request.Request
:param request:
Current request.
"""
# pylint: disable = unused-argument
return True
# -------------------------------------------------------------------------
[docs]
def close(self, request: Request, prevent_reopen: bool = False):
"""Close the panel and memorize the state.
:type request: pyramid.request.Request
:param request:
Current request.
:param bool prevent_reopen:
If ``True``, prevent the panel to be reopened.
"""
if 'panels' not in request.session \
or self.uid not in request.session['panels']:
return
can_reopen = not prevent_reopen and self.can_reopen \
and 'values' in request.session['panels'][self.uid]
if not can_reopen and 'values' in request.session['panels'][self.uid]:
del request.session['panels'][self.uid]['values']
request.session['panels'][self.uid]['was_open'] = False
request.session['panels'][self.uid]['is_open'] = False
request.session['panels'][self.uid]['can_reopen'] = can_reopen
# -------------------------------------------------------------------------
[docs]
def clear_values(self, request: Request):
"""Clear all values of this panel.
:type request: pyramid.request.Request
:param request:
Current request.
"""
if 'panels' in request.session \
and self.uid in request.session['panels'] \
and 'values' in request.session['panels'][self.uid]:
del request.session['panels'][self.uid]['values']
request.session['panels'][self.uid]['can_reopen'] = False
# -------------------------------------------------------------------------
[docs]
def set_values(self, request: Request, values: dict):
"""Set values of this panel.
:type request: pyramid.request.Request
:param request:
Current request.
:param dict values:
Values to set.
"""
self._prepare_session(request)
request.session['panels'][self.uid]['values'] = values
# -------------------------------------------------------------------------
[docs]
def values(self, request: Request) -> dict:
"""Return values of this panel.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: dict
"""
self._prepare_session(request)
return request.session['panels'][self.uid].get('values', {})
# -------------------------------------------------------------------------
[docs]
def set_value(self, request: Request, value_id: str, value):
"""Set a value of this panel.
:type request: pyramid.request.Request
:param request:
Current request.
:param str value_id:
Value ID.
:param value:
Value to set.
"""
self._prepare_session(request)
if 'values' not in request.session['panels'][self.uid]:
request.session['panels'][self.uid]['values'] = {}
request.session['panels'][self.uid]['values'][value_id] = value
# -------------------------------------------------------------------------
[docs]
def value(self, request: Request, value_id: str) -> str | None:
"""Return the value ``value_id`` of this panel.
:type request: pyramid.request.Request
:param request:
Current request.
:param str value_id:
ID of the requested value.
:rtype: str
"""
return self.values(request).get(value_id)
# -------------------------------------------------------------------------
[docs]
def render(
self,
request: Request,
ts_factory=_,
panel_class: str = 'cioPanel') -> str:
"""Return the content of the panel.
:type request: pyramid.request.Request
:param request:
Current request.
:param ts_factory: (default=_)
Translation String Factory fucntion.
:param str panel_class: (default = 'cioPanel')
CSS class for the panel.
:rtype: helpers.literal.Literal
"""
if self.area and (request.matched_route is None
or request.matched_route.name not in self.area):
return ''
my_domain = self.template.partition(':')[0] \
if ':' in self.template else 'chrysalio'
def _translate(
msgid,
domain=my_domain,
mapping=None,
default=None,
context=None,
target_language=None):
"""Translation for Chameleon."""
# pylint: disable = unused-argument
return request.localizer.translate(
msgid, domain=domain or my_domain, mapping=mapping)
params = { # yapf: disable
'request': request,
'_': ts_factory,
'panel_class': panel_class,
'route': lambda name, *elts, **kwargs: tostr(
request.route_path(name, *elts, **kwargs)),
'theme': theme_static_prefix(request),
'get_action': lambda request: get_action(request, True),
'PANEL_ITEM_PREFIX': PANEL_ITEM_PREFIX,
'panel': self}
params.update(self.constants)
params.update(self.values(request))
if self.need_form:
params['form'] = Form(
request,
defaults=params.get('form_defaults'),
force_defaults='form_defaults' in params)
params['form'].forget(PANEL_ITEM_PREFIX)
return Literal(
PageTemplateFile(
abspath_from_asset_spec(self.template),
translate=_translate).render(**params))
# -------------------------------------------------------------------------
[docs]
def route(self, request: Request) -> str:
"""Return the route to toggle the state of the panel.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: str
"""
if request.matched_route is None:
self.close(request)
return ''
query_string = dict(request.GET)
query_string.update({'panel': self.uid})
if 'close' in query_string:
del query_string['close']
return request.current_route_path(_query=query_string)
# -------------------------------------------------------------------------
[docs]
@classmethod
def open_panel_css(cls, request: Request) -> tuple:
"""Return a list of URL of CSS used by the open panel.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: tuple
"""
for panel in request.registry.get('panels', {}).values():
if request.session.get( # yapf: disable
'panels', {}).get(panel.uid, {}).get('is_open'):
return panel.css
return ()
# -------------------------------------------------------------------------
[docs]
@classmethod
def open_panel_js(cls, request: Request) -> tuple:
"""Return a list of URL of Javascript used by the open panel.
:type request: pyramid.request.Request
:param request:
Current request.
:rtype: tuple
"""
for panel in request.registry.get('panels', {}).values():
if request.session.get( # yapf: disable
'panels', {}).get(panel.uid, {}).get('is_open'):
return panel.javascripts
return ()
# -------------------------------------------------------------------------
[docs]
@classmethod
def manage_panels(cls, request: Request):
"""Possibly, toggle the current panel state.
:type request: pyramid.request.Request
:param request:
Current request.
"""
if 'panels' not in request.registry:
return
# Was open
for panel in request.registry['panels'].values():
if request.session.get( # yapf: disable
'panels', {}).get(panel.uid, {}).get('is_open'):
request.session['panels'][panel.uid]['was_open'] = True
# Close or open a panel
panel_id = request.GET.get('panel')
if panel_id and panel_id in request.registry['panels']:
panel = request.registry['panels'][panel_id]
if request.GET.get('close'):
panel.close(request)
else:
panel.open(request)
# -------------------------------------------------------------------------
def _prepare_session(self, request: Request):
"""Prepare session to memorize panel state.
:type request: pyramid.request.Request
:param request:
Current request.
"""
if 'panels' not in request.session:
request.session['panels'] = {}
if self.uid not in request.session['panels']:
request.session['panels'][self.uid] = {
'was_open': False,
'is_open': False,
'can_reopen': False
}