2024-01-09 17:46:18 +05:30
|
|
|
from io import BytesIO
|
|
|
|
from pickle import loads
|
|
|
|
from pathlib import Path
|
|
|
|
from datetime import datetime
|
|
|
|
import logging
|
2024-03-24 11:21:11 +05:30
|
|
|
# from typing import Any
|
2024-01-09 17:46:18 +05:30
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
from matplotlib.figure import Figure
|
|
|
|
from sqlmodel import Field, Relationship, String, JSON
|
2024-01-09 17:46:18 +05:30
|
|
|
from pydantic import BaseModel
|
|
|
|
import pandas as pd
|
|
|
|
|
|
|
|
from gisaf.config import conf
|
|
|
|
from gisaf.models.metadata import gisaf
|
|
|
|
from gisaf.models.models_base import Model
|
|
|
|
from gisaf.models.misc import NotADataframeError
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
2024-01-09 17:46:18 +05:30
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardPageSource(Model, table=True):
|
2024-02-13 12:47:07 +05:30
|
|
|
__tablename__ = 'dashboard_page_source' # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
__table_args__ = gisaf.table_args
|
|
|
|
|
|
|
|
id: str = Field(primary_key=True)
|
|
|
|
name: str
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardPageCommon:
|
|
|
|
"""
|
|
|
|
Base class for DashboardPage and DashboardPageSection, where some methods
|
|
|
|
are common, eg. attachments
|
|
|
|
"""
|
2024-03-24 11:21:11 +05:30
|
|
|
name: str
|
|
|
|
df: bytes
|
|
|
|
plot: bytes
|
|
|
|
#plot: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
|
|
|
|
attachment: str | None
|
|
|
|
html: str | None = None
|
2024-01-09 17:46:18 +05:30
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
def ensure_dir_exists(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def get_plot_file_path(self) -> Path:
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def get_attachment_file_name(self) -> str:
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def get_plot_file_name(self):
|
|
|
|
raise NotImplementedError()
|
2024-01-09 17:46:18 +05:30
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
def save_plot(self, plot: plt.Axes | plt.Figure): # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
"""
|
|
|
|
Render the matplotlib plot (or figure) and save it in the filesystem
|
|
|
|
:param plot: matplotlib plot or figure...
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
self.ensure_dir_exists()
|
|
|
|
|
|
|
|
## Different types of figures supported
|
2024-03-24 11:21:11 +05:30
|
|
|
fig: Figure | None = None
|
|
|
|
if isinstance(plot, plt.Axes): # type: ignore
|
|
|
|
fig = plot.figure # type: ignore
|
|
|
|
elif isinstance(plot, plt.Figure): # type: ignore
|
|
|
|
fig = plot
|
|
|
|
if fig:
|
|
|
|
fig.savefig(self.get_plot_file_path(), bbox_inches='tight')
|
|
|
|
plt.close(fig)
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
if plot and not fig:
|
|
|
|
logger.warning('Cannot save dashboard attachment (unknown attachment type)')
|
|
|
|
return
|
|
|
|
|
|
|
|
#logger.info(f'Saved attachment of dashboard page {self.group}/{self.name} '
|
|
|
|
#f'in {self.get_attachment_file_name()}')
|
|
|
|
return self.get_plot_file_name()
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
def save_attachment(self, attached, name=None) -> str | None:
|
2024-01-09 17:46:18 +05:30
|
|
|
"""
|
|
|
|
Save the attachment in the filesystem
|
|
|
|
:param attached: matplotlib plot or figure...
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if not self.attachment:
|
|
|
|
## Not set yet (creation)
|
|
|
|
self.attachment = f'{self.name}.png'
|
|
|
|
|
|
|
|
self.ensure_dir_exists()
|
|
|
|
|
|
|
|
## Different types of figures supported
|
2024-03-24 11:21:11 +05:30
|
|
|
fig: Figure | None = None
|
2024-01-09 17:46:18 +05:30
|
|
|
if plt:
|
2024-03-24 11:21:11 +05:30
|
|
|
if isinstance(attached, plt.Axes): # type: ignore
|
|
|
|
fig = attached.figure # type: ignore
|
|
|
|
elif isinstance(attached, plt.Figure): # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
fig = attached
|
|
|
|
if fig:
|
|
|
|
fig.savefig(self.get_attachment_file_name(), bbox_inches='tight')
|
|
|
|
plt.close(fig)
|
|
|
|
|
|
|
|
if attached and not fig:
|
|
|
|
logger.warning('Cannot save dashboard attachment (unknown attachment type)')
|
2024-03-24 11:21:11 +05:30
|
|
|
return None
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
#logger.info(f'Saved attachment of dashboard page {self.group}/{self.name} '
|
|
|
|
#f'in {self.get_attachment_file_name()}')
|
|
|
|
return self.attachment
|
|
|
|
|
|
|
|
def get_page_df(self):
|
|
|
|
"""
|
|
|
|
Get the dataframe of the page
|
|
|
|
"""
|
|
|
|
if not self.df:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
return pd.read_pickle(BytesIO(self.df), compression=None)
|
|
|
|
except KeyError:
|
|
|
|
raise NotADataframeError()
|
|
|
|
|
|
|
|
def get_plot(self):
|
2024-03-24 11:21:11 +05:30
|
|
|
if self.plot is not None:
|
|
|
|
return loads(self.plot)
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
class DashboardPageMetaData(BaseModel):
|
|
|
|
name: str
|
|
|
|
group: str
|
|
|
|
description: str
|
|
|
|
viewable_role: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardPage(Model, DashboardPageCommon, DashboardPageMetaData, table=True):
|
2024-02-13 12:47:07 +05:30
|
|
|
__tablename__ = 'dashboard_page' # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
__table_args__ = gisaf.table_args
|
|
|
|
|
|
|
|
class Admin:
|
|
|
|
menu = 'Dashboard'
|
|
|
|
|
|
|
|
id: int = Field(primary_key=True)
|
2024-03-24 11:21:11 +05:30
|
|
|
time: datetime | None = Field(default_factory=datetime.now)
|
|
|
|
notebook: str | None = None
|
|
|
|
source_id: int | None = Field(foreign_key=gisaf.table('dashboard_page_source.id'))
|
|
|
|
expanded_panes: str | None
|
2024-01-09 17:46:18 +05:30
|
|
|
source: DashboardPageSource = Relationship()
|
2024-03-24 11:21:11 +05:30
|
|
|
sections: list['DashboardPageSection'] = Relationship()
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return f'{self.group:s}/{self.name:s}'
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return f'<models.DashboardPage {self.group:s}/{self.name:s}>'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def selectinload(cls):
|
|
|
|
return [cls.source]
|
|
|
|
|
|
|
|
def ensure_dir_exists(self):
|
|
|
|
"""
|
|
|
|
Make sure the directory exists, before saving the file
|
|
|
|
"""
|
|
|
|
dir_name = Path(conf.dashboard.base_storage_dir) / self.group
|
|
|
|
dir_name.mkdir(exist_ok=True)
|
|
|
|
return dir_name
|
|
|
|
|
|
|
|
def get_attachment_file_name(self):
|
|
|
|
"""
|
|
|
|
Get the file name of the attachment
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if self.attachment:
|
|
|
|
base_dir = Path(conf.dashboard.base_storage_dir)
|
|
|
|
if base_dir:
|
|
|
|
return base_dir/self.group/self.attachment
|
|
|
|
else:
|
|
|
|
raise UserWarning('Cannot save attachment: no notebook/base_storage_dir in gisaf config')
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
def get_attachment_url(self):
|
|
|
|
## Serve through web front-end (nginx static file)
|
|
|
|
if not self.attachment:
|
|
|
|
return
|
|
|
|
base_storage_url = conf.dashboard.base_storage_url
|
|
|
|
if not base_storage_url:
|
|
|
|
base_storage_url = '/dashboard-attachment/'
|
|
|
|
return f'{base_storage_url}{self.group}/{self.attachment}'
|
|
|
|
|
2024-01-09 17:46:18 +05:30
|
|
|
def get_notebook_url(self):
|
|
|
|
if self.notebook:
|
|
|
|
base_url = conf.dashboard.base_source_url
|
|
|
|
if base_url:
|
|
|
|
return f'{base_url}{self.notebook}'
|
|
|
|
else:
|
|
|
|
logger.debug('Notebook: no base_url in gisaf config')
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardPageSection(Model, DashboardPageCommon, table=True):
|
2024-02-13 12:47:07 +05:30
|
|
|
__tablename__ = 'dashboard_page_section' # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
__table_args__ = gisaf.table_args
|
|
|
|
|
|
|
|
class Admin:
|
|
|
|
menu = 'Dashboard'
|
|
|
|
|
|
|
|
id: str = Field(primary_key=True)
|
|
|
|
name: str
|
|
|
|
dashboard_page_id: int = Field(foreign_key=gisaf.table('dashboard_page.id'))
|
2024-03-24 11:21:11 +05:30
|
|
|
dashboard_page: DashboardPage = Relationship(back_populates='sections')
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
description: str
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return f'{self.name} for dashboard page #{self.dashboard_page_id}'
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return f'<models.DashboardPageSection #{self.id}>'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def selectinload(cls):
|
|
|
|
return [cls.dashboard_page]
|
|
|
|
|
|
|
|
def get_plot_url(self):
|
|
|
|
## Serve through web front-end (nginx static file)
|
|
|
|
if not self.plot:
|
|
|
|
return
|
|
|
|
return conf.dashboard.base_storage_url \
|
|
|
|
+ self.dashboard_page.group + '/' \
|
|
|
|
+ self.dashboard_page.name + '/' \
|
|
|
|
+ self.name + '.png'
|
|
|
|
|
|
|
|
def ensure_dir_exists(self):
|
|
|
|
"""
|
|
|
|
Make sure the directory exists, before saving the file
|
|
|
|
"""
|
|
|
|
dir_name = Path(conf.dashboard.base_storage_dir) / self.page.group / self.page.name
|
|
|
|
dir_name.mkdir(exist_ok=True)
|
|
|
|
return dir_name
|
|
|
|
|
|
|
|
def get_attachment_file_name(self):
|
|
|
|
"""
|
|
|
|
Get the file name of the attachment
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if not self.attachment:
|
|
|
|
return
|
|
|
|
base_dir = Path(conf.dashboard.base_storage_dir)
|
|
|
|
if not base_dir:
|
|
|
|
raise UserWarning('Cannot save attachment: no notebook/base_storage_dir in gisaf config')
|
|
|
|
return base_dir/self.page.group/self.page.name/self.attachment
|
|
|
|
|
|
|
|
def get_plot_file_name(self):
|
|
|
|
return f'{self.name}.png'
|
|
|
|
|
|
|
|
def get_plot_file_path(self):
|
|
|
|
"""
|
|
|
|
Get the file name of the plot
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
if not self.plot:
|
|
|
|
return
|
|
|
|
base_dir = Path(conf.dashboard.base_storage_dir)
|
|
|
|
if not base_dir:
|
|
|
|
raise UserWarning('Cannot save attachment: no notebook/base_storage_dir in gisaf config')
|
|
|
|
return base_dir/self.page.group/self.page.name/self.get_plot_file_name()
|
|
|
|
|
|
|
|
|
|
|
|
class Widget(Model, table=True):
|
2024-02-13 12:47:07 +05:30
|
|
|
__tablename__ = 'widget' # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
__table_args__ = gisaf.table_args
|
|
|
|
## CREATE TABLE gisaf.widget (name char(50) not null PRIMARY KEY, title varchar, subtitle varchar, notebook varchar, content varchar, time timestamp);
|
2024-02-13 12:47:07 +05:30
|
|
|
name: str = Field(primary_key=True, sa_type=String(50)) # type: ignore
|
2024-01-09 17:46:18 +05:30
|
|
|
title: str
|
|
|
|
subtitle: str
|
|
|
|
content: str
|
|
|
|
time: datetime
|
|
|
|
notebook: str
|
|
|
|
|
|
|
|
class Admin:
|
|
|
|
menu = 'Dashboard'
|
|
|
|
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
class DashboardSection(BaseModel):
|
2024-01-09 17:46:18 +05:30
|
|
|
name: str
|
|
|
|
plot: str
|
|
|
|
|
|
|
|
|
2024-03-24 11:21:11 +05:30
|
|
|
class Dashboard(BaseModel):
|
2024-01-09 17:46:18 +05:30
|
|
|
name: str
|
|
|
|
group: str
|
|
|
|
description: str
|
|
|
|
time: datetime | None = Field(default_factory=datetime.now)
|
|
|
|
html: str | None = None
|
|
|
|
attachment: str | None = None
|
2024-03-24 11:21:11 +05:30
|
|
|
dfData: list = []
|
2024-01-09 17:46:18 +05:30
|
|
|
plotData: str | None = None
|
|
|
|
notebook: str | None = None
|
|
|
|
expandedPanes: list[str] | None = None
|
2024-03-24 11:21:11 +05:30
|
|
|
sections: list[DashboardSection] | None = None
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
|
|
|
|
class DashboardGroup(BaseModel):
|
|
|
|
name: str
|
2024-03-24 11:21:11 +05:30
|
|
|
pages: list[DashboardPageMetaData]
|
2024-01-09 17:46:18 +05:30
|
|
|
|
|
|
|
|
|
|
|
class DashboardHome(BaseModel):
|
|
|
|
title: str
|
|
|
|
content: str
|
|
|
|
footer: str
|