Restructure apis, to the dedicated directory
Implement dashboards
This commit is contained in:
parent
aed84e0f36
commit
581598c208
7 changed files with 456 additions and 20 deletions
|
@ -32,12 +32,14 @@ class User(UserBase, table=True):
|
|||
password: str | None = None
|
||||
|
||||
def can_view(self, model) -> bool:
|
||||
viewable_role = getattr(model, 'viewable_role', None)
|
||||
if viewable_role:
|
||||
return viewable_role in (role.name for role in self.roles)
|
||||
role = getattr(model, 'viewable_role', None)
|
||||
if role:
|
||||
return self.has_role(role)
|
||||
else:
|
||||
return True
|
||||
|
||||
def has_role(self, role: str) -> bool:
|
||||
return role in (role.name for role in self.roles)
|
||||
|
||||
class RoleBase(SQLModel):
|
||||
name: str = Field(unique=True)
|
||||
|
|
296
src/gisaf/models/dashboard.py
Normal file
296
src/gisaf/models/dashboard.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
from io import BytesIO
|
||||
from pickle import loads
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from sqlmodel import Field, Relationship, String
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
except ImportError:
|
||||
plt = None
|
||||
|
||||
|
||||
class DashboardPageSource(Model, table=True):
|
||||
__tablename__ = 'dashboard_page_source'
|
||||
__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
|
||||
"""
|
||||
|
||||
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}'
|
||||
|
||||
def save_plot(self, plot):
|
||||
"""
|
||||
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
|
||||
fig = None
|
||||
if plt:
|
||||
if isinstance(plot, plt.Axes):
|
||||
fig = plot.figure
|
||||
elif isinstance(plot, plt.Figure):
|
||||
fig = plot
|
||||
if fig:
|
||||
fig.savefig(self.get_plot_file_path(), bbox_inches='tight')
|
||||
plt.close(fig)
|
||||
|
||||
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()
|
||||
|
||||
def save_attachment(self, attached, name=None):
|
||||
"""
|
||||
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
|
||||
fig = None
|
||||
if plt:
|
||||
if isinstance(attached, plt.Axes):
|
||||
fig = attached.figure
|
||||
elif isinstance(attached, plt.Figure):
|
||||
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)')
|
||||
return
|
||||
|
||||
#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):
|
||||
return loads(self.plot)
|
||||
|
||||
|
||||
class DashboardPage(Model, DashboardPageCommon, table=True):
|
||||
__tablename__ = 'dashboard_page'
|
||||
__table_args__ = gisaf.table_args
|
||||
|
||||
class Admin:
|
||||
menu = 'Dashboard'
|
||||
|
||||
id: int = Field(primary_key=True)
|
||||
name: str
|
||||
notebook: str
|
||||
group: str
|
||||
description: str
|
||||
attachment: str
|
||||
html: str
|
||||
viewable_role: str
|
||||
df: bytes
|
||||
plot: bytes
|
||||
source_id: int = Field(foreign_key=gisaf.table('dashboard_page_source.id'))
|
||||
time: datetime
|
||||
expanded_panes: str
|
||||
source: DashboardPageSource = Relationship()
|
||||
|
||||
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')
|
||||
|
||||
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):
|
||||
__tablename__ = 'dashboard_page_section'
|
||||
__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'))
|
||||
dashboard_page: DashboardPage = Relationship()
|
||||
|
||||
description: str
|
||||
attachment: str
|
||||
html: str
|
||||
df: bytes
|
||||
plot: 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):
|
||||
__tablename__ = 'widget'
|
||||
__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);
|
||||
name: str = Field(primary_key=True, sa_type=String(50))
|
||||
title: str
|
||||
subtitle: str
|
||||
content: str
|
||||
time: datetime
|
||||
notebook: str
|
||||
|
||||
class Admin:
|
||||
menu = 'Dashboard'
|
||||
|
||||
|
||||
class DashboadPageSectionType(BaseModel):
|
||||
name: str
|
||||
plot: str
|
||||
|
||||
|
||||
class DashboardPage_(BaseModel):
|
||||
name: str
|
||||
group: str
|
||||
description: str
|
||||
time: datetime | None = Field(default_factory=datetime.now)
|
||||
html: str | None = None
|
||||
attachment: str | None = None
|
||||
dfData: str | None = None
|
||||
plotData: str | None = None
|
||||
notebook: str | None = None
|
||||
expandedPanes: list[str] | None = None
|
||||
sections: list[DashboadPageSectionType] | None = None
|
||||
|
||||
|
||||
class DashboardGroup(BaseModel):
|
||||
name: str
|
||||
pages: list[DashboardPage_]
|
||||
|
||||
|
||||
class DashboardHome(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
footer: str
|
Loading…
Add table
Add a link
Reference in a new issue