From 3ec79fdcfed7502fd07784a17af58c9c3d56ed30 Mon Sep 17 00:00:00 2001 From: lwark Date: Tue, 25 Mar 2025 15:25:14 -0500 Subject: [PATCH] Pre-code cleanup. --- CHANGELOG.md | 1 + src/submissions/backend/db/models/__init__.py | 41 ++++--- src/submissions/backend/db/models/controls.py | 5 +- .../backend/db/models/submissions.py | 10 +- .../frontend/visualizations/__init__.py | 2 + src/submissions/frontend/widgets/info_tab.py | 4 +- src/submissions/frontend/widgets/misc.py | 105 +----------------- 7 files changed, 40 insertions(+), 128 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c750528..7dae9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 202503.05 - Added concentrations chart tab. +- Saving report xlsx/pdf now inserts report class name in file name. # 202503.04 diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index f04a2fb..4f95261 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -56,23 +56,23 @@ class BaseClass(Base): omni_inheritable = [] searchables = [] - @classproperty - def skip_on_edit(cls): - if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount": - return True - else: - return False + # @classproperty + # def skip_on_edit(cls): + # if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount": + # return True + # else: + # return False @classproperty def aliases(cls): return [cls.query_alias] - @classproperty - def level(cls): - if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount": - return 2 - else: - return 1 + # @classproperty + # def level(cls): + # if "association" in cls.__name__.lower() or cls.__name__.lower() == "discount": + # return 2 + # else: + # return 1 @classproperty def query_alias(cls): @@ -187,8 +187,9 @@ class BaseClass(Base): return query.limit(50).all() @classmethod - def results_to_df(cls, objects: list, **kwargs) -> DataFrame: + def results_to_df(cls, objects: list|None=None, **kwargs) -> DataFrame: """ + Converts class sub_dicts into a Dataframe for all instances of the class. Args: objects (list): Objects to be converted to dataframe. @@ -197,10 +198,16 @@ class BaseClass(Base): Returns: Dataframe """ - try: - records = [obj.to_sub_dict(**kwargs) for obj in objects] - except AttributeError: - records = [{k: v['instance_attr'] for k, v in obj.omnigui_instance_dict.items()} for obj in objects] + if not objects: + try: + records = [obj.to_sub_dict(**kwargs) for obj in cls.query()] + except AttributeError: + records = [obj.to_dict(**kwargs) for obj in cls.query(page_size=0)] + else: + try: + records = [obj.to_sub_dict(**kwargs) for obj in objects] + except AttributeError: + records = [{k: v['instance_attr'] for k, v in obj.omnigui_instance_dict.items()} for obj in objects] return DataFrame.from_records(records) @classmethod diff --git a/src/submissions/backend/db/models/controls.py b/src/submissions/backend/db/models/controls.py index 037335a..ff1b057 100644 --- a/src/submissions/backend/db/models/controls.py +++ b/src/submissions/backend/db/models/controls.py @@ -262,7 +262,7 @@ class Control(BaseClass): try: model = next(subclass for subclass in cls.__subclasses__() if all([hasattr(subclass, attr) for attr in attrs.keys()])) - except StopIteration as e: + except StopIteration: raise AttributeError( f"Couldn't find existing class/subclass of {cls} with all attributes:\n{pformat(attrs.keys())}") return model @@ -286,6 +286,7 @@ class Control(BaseClass): Dummy operation to be overridden by child classes. Args: + parent (QWidget): widget to add chart to. chart_settings (dict): settings passed down from chart widget ctx (Settings): settings passed down from gui """ @@ -663,7 +664,7 @@ class IridaControl(Control): return df, previous_dates # NOTE: if date was changed, rerun with new date else: - logger.warning(f"Date check failed, running recursion") + logger.warning(f"Date check failed, running recursion.") df, previous_dates = cls.check_date(df, item, previous_dates) return df, previous_dates diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index e663198..bf875fb 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -31,7 +31,6 @@ from jinja2.exceptions import TemplateNotFound from jinja2 import Template from PIL import Image - logger = logging.getLogger(f"submissions.{__name__}") @@ -1584,7 +1583,7 @@ class Wastewater(BasicSubmission): continue thing['tooltip'] = f"Sample Name: {thing['name']}\nWell: {thing['sample_location']}" dummy_samples.append(thing) - logger.debug(f"Dummy samples for 24 well: {pformat(dummy_samples)}") + # logger.debug(f"Dummy samples for 24 well: {pformat(dummy_samples)}") output['origin_plate'] = self.__class__.make_plate_map(sample_list=dummy_samples, plate_rows=4, plate_columns=6) # logger.debug(f"PCR info: {output['pcr_info']}") @@ -2772,7 +2771,12 @@ class BacterialCultureSample(BasicSample): sample['concentration'] = self.concentration if self.control is not None: sample['colour'] = [0, 128, 0] - sample['tooltip'] = f"Control: {self.control.controltype.name} - {self.control.controltype.targets}" + target = next((v for k,v in self.control.controltype.targets.items() if k == self.control.subtype), "Not Available") + try: + target = ", ".join(target) + except: + target = "None" + sample['tooltip'] = f"\nControl: {self.control.controltype.name} - {target}" return sample diff --git a/src/submissions/frontend/visualizations/__init__.py b/src/submissions/frontend/visualizations/__init__.py index 62fe408..ec73cbf 100644 --- a/src/submissions/frontend/visualizations/__init__.py +++ b/src/submissions/frontend/visualizations/__init__.py @@ -134,3 +134,5 @@ class CustomFigure(Figure): from .irida_charts import IridaFigure from .pcr_charts import PCRFigure +from .concentrations_chart import ConcentrationsChart +from .turnaround_chart import TurnaroundChart diff --git a/src/submissions/frontend/widgets/info_tab.py b/src/submissions/frontend/widgets/info_tab.py index 608cd01..1ae3fa1 100644 --- a/src/submissions/frontend/widgets/info_tab.py +++ b/src/submissions/frontend/widgets/info_tab.py @@ -60,12 +60,12 @@ class InfoPane(QWidget): return abs((d1.year - d2.year) * 12 + d1.month - d2.month) def save_excel(self): - fname = select_save_file(self, default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="xlsx") + fname = select_save_file(self, default_name=f"{self.__class__.__name__} Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="xlsx") self.report_obj.write_report(fname, obj=self) def save_pdf(self): fname = select_save_file(obj=self, - default_name=f"Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", + default_name=f"{self.__class__.__name__} Report {self.start_date.strftime('%Y%m%d')} - {self.end_date.strftime('%Y%m%d')}", extension="pdf") save_pdf(obj=self.webview, filename=fname) diff --git a/src/submissions/frontend/widgets/misc.py b/src/submissions/frontend/widgets/misc.py index 6c270f2..86e78e3 100644 --- a/src/submissions/frontend/widgets/misc.py +++ b/src/submissions/frontend/widgets/misc.py @@ -2,13 +2,12 @@ Contains miscellaneous widgets for frontend functions """ import math -from datetime import date from PyQt6.QtGui import QStandardItem, QIcon from PyQt6.QtWidgets import ( QLabel, QLineEdit, QComboBox, QDateEdit, QPushButton, QWidget, QHBoxLayout, QSizePolicy ) -from PyQt6.QtCore import Qt, QDate, QSize, QMarginsF +from PyQt6.QtCore import Qt, QDate, QSize from tools import jinja_template_loading from backend.db.models import * import logging @@ -18,98 +17,6 @@ logger = logging.getLogger(f"submissions.{__name__}") env = jinja_template_loading() -# class AddReagentForm(QDialog): -# """ -# dialog to add gather info about new reagent (Defunct) -# """ -# -# def __init__(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None, -# reagent_name: str | None = None, kit: str | KitType | None = None) -> None: -# super().__init__() -# if reagent_name is None: -# reagent_name = reagent_role -# self.setWindowTitle("Add Reagent") -# QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel -# self.buttonBox = QDialogButtonBox(QBtn) -# self.buttonBox.accepted.connect(self.accept) -# self.buttonBox.rejected.connect(self.reject) -# # NOTE: widget to get lot info -# self.name_input = QComboBox() -# self.name_input.setObjectName("name") -# self.name_input.setEditable(True) -# self.name_input.setCurrentText(reagent_name) -# self.lot_input = QLineEdit() -# self.lot_input.setObjectName("lot") -# self.lot_input.setText(reagent_lot) -# # NOTE: widget to get expiry info -# self.expiry_input = QDateEdit(calendarPopup=True) -# self.expiry_input.setObjectName('expiry') -# # NOTE: if expiry is not passed in from gui, use today -# if expiry is None: -# logger.warning(f"Did not receive expiry, setting to 1970, 1, 1") -# self.expiry_input.setDate(QDate(1970, 1, 1)) -# else: -# try: -# self.expiry_input.setDate(expiry) -# except TypeError: -# self.expiry_input.setDate(QDate(1970, 1, 1)) -# # NOTE: widget to get reagent type info -# self.role_input = QComboBox() -# self.role_input.setObjectName('role') -# if kit: -# match kit: -# case str(): -# kit = KitType.query(name=kit) -# case _: -# pass -# self.role_input.addItems([item.name for item in ReagentRole.query() if kit in item.kit_types]) -# else: -# self.role_input.addItems([item.name for item in ReagentRole.query()]) -# # NOTE: convert input to user-friendly string? -# try: -# reagent_role = reagent_role.replace("_", " ").title() -# except AttributeError: -# reagent_role = None -# # NOTE: set parsed reagent type to top of list -# index = self.role_input.findText(reagent_role, Qt.MatchFlag.MatchEndsWith) -# if index >= 0: -# self.role_input.setCurrentIndex(index) -# self.layout = QVBoxLayout() -# self.layout.addWidget(QLabel("Name:")) -# self.layout.addWidget(self.name_input) -# self.layout.addWidget(QLabel("Lot:")) -# self.layout.addWidget(self.lot_input) -# self.layout.addWidget( -# QLabel("Expiry:\n(use exact date on reagent.\nEOL will be calculated from kit automatically)") -# ) -# self.layout.addWidget(self.expiry_input) -# self.layout.addWidget(QLabel("Type:")) -# self.layout.addWidget(self.role_input) -# self.layout.addWidget(self.buttonBox) -# self.setLayout(self.layout) -# self.role_input.currentTextChanged.connect(self.update_names) -# -# def parse_form(self) -> dict: -# """ -# Converts information in form to dict. -# -# Returns: -# dict: Output info -# """ -# return dict(name=self.name_input.currentText().strip(), -# lot=self.lot_input.text().strip(), -# expiry=self.expiry_input.date().toPyDate(), -# role=self.role_input.currentText().strip()) -# -# def update_names(self): -# """ -# Updates reagent names form field with examples from reagent type -# """ -# self.name_input.clear() -# lookup = Reagent.query(role=self.role_input.currentText()) -# self.name_input.addItems(list(set([item.name for item in lookup]))) -# -# class StartEndDatePicker(QWidget): """ custom widget to pick start and end dates for controls graphs @@ -135,16 +42,6 @@ class StartEndDatePicker(QWidget): return QSize(80, 20) -# def save_pdf(obj: QWebEngineView, filename: Path): -# page_layout = QPageLayout() -# page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4)) -# page_layout.setOrientation(QPageLayout.Orientation.Portrait) -# page_layout.setMargins(QMarginsF(25, 25, 25, 25)) -# obj.page().printToPdf(filename.absolute().__str__(), page_layout) - - -# NOTE: subclass - class CheckableComboBox(QComboBox): # once there is a checkState set, it is rendered # here we assume default Unchecked