From c9396d6c4198e43c4c0942d0e3f7ed8f6bb6b278 Mon Sep 17 00:00:00 2001 From: lwark Date: Fri, 5 Sep 2025 15:23:56 -0500 Subject: [PATCH] Frontend code cleanup finished. --- src/submissions/backend/__init__.py | 2 +- src/submissions/backend/db/models/__init__.py | 3 +- .../backend/db/models/procedures.py | 2 +- .../backend/db/models/submissions.py | 2 +- .../backend/excel/parsers/__init__.py | 2 +- .../backend/validators/__init__.py | 2 +- src/submissions/backend/validators/pydant.py | 4 +- .../frontend/visualizations/__init__.py | 8 +- .../visualizations/concentrations_chart.py | 4 +- .../frontend/visualizations/irida_charts.py | 4 +- src/submissions/frontend/widgets/app.py | 32 ++-- .../frontend/widgets/concentrations.py | 2 - .../frontend/widgets/controls_chart.py | 1 - .../frontend/widgets/date_type_picker.py | 9 +- .../frontend/widgets/equipment_usage.py | 139 +++++++++--------- .../frontend/widgets/equipment_usage_2.py | 131 ----------------- src/submissions/frontend/widgets/functions.py | 2 +- .../frontend/widgets/gel_checker.py | 3 +- src/submissions/frontend/widgets/info_tab.py | 1 - src/submissions/frontend/widgets/misc.py | 3 +- .../frontend/widgets/procedure_creation.py | 17 +-- .../frontend/widgets/sample_checker.py | 18 +-- .../frontend/widgets/submission_details.py | 29 ++-- .../frontend/widgets/submission_table.py | 81 +--------- .../frontend/widgets/submission_widget.py | 113 +++----------- 25 files changed, 154 insertions(+), 460 deletions(-) delete mode 100644 src/submissions/frontend/widgets/equipment_usage_2.py diff --git a/src/submissions/backend/__init__.py b/src/submissions/backend/__init__.py index a34cd2b..e0a0e2e 100644 --- a/src/submissions/backend/__init__.py +++ b/src/submissions/backend/__init__.py @@ -3,4 +3,4 @@ Contains database, validators and excel operations. """ from .db import * from .excel import * -from .validators import * \ No newline at end of file +from .validators import * diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index 1bddfd6..af8f025 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -484,7 +484,8 @@ class BaseClass(Base): if check: try: value = json.dumps(value) - except TypeError: + except TypeError as e: + logger.error(f"Error json dumping value: {e}") value = str(value) try: self._misc_info.update({key: value}) diff --git a/src/submissions/backend/db/models/procedures.py b/src/submissions/backend/db/models/procedures.py index 7ee12a2..6404c8c 100644 --- a/src/submissions/backend/db/models/procedures.py +++ b/src/submissions/backend/db/models/procedures.py @@ -917,7 +917,7 @@ class Procedure(BaseClass): obj (_type_): parent widget """ logger.info(f"Add equipment") - from frontend.widgets.equipment_usage_2 import EquipmentUsage + from frontend.widgets.equipment_usage import EquipmentUsage dlg = EquipmentUsage(parent=obj, procedure=self.to_pydantic()) if dlg.exec(): dlg.save_procedure() diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 418eb6b..d9b4be1 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -646,7 +646,7 @@ class Run(BaseClass, LogMixin): 'permission', "clientsubmission"] output['sample_count'] = self.sample_count output['clientsubmission'] = self.clientsubmission.name - output['clientlab'] = self.clientsubmission.clientlab + # output['clientlab'] = self.clientsubmission.clientlab output['started_date'] = self.started_date output['completed_date'] = self.completed_date return output diff --git a/src/submissions/backend/excel/parsers/__init__.py b/src/submissions/backend/excel/parsers/__init__.py index 892443f..f3748dc 100644 --- a/src/submissions/backend/excel/parsers/__init__.py +++ b/src/submissions/backend/excel/parsers/__init__.py @@ -126,7 +126,7 @@ class DefaultTABLEParser(DefaultParser): df = df.dropna(axis=1, how='all') for ii, row in enumerate(df.iterrows()): output = {} - for key, value in row[1].details_dict().items(): + for key, value in row[1].to_dict().items(): if isinstance(key, str): key = key.lower().replace(" ", "_") key = re.sub(r"_(\(.*\)|#)", "", key) diff --git a/src/submissions/backend/validators/__init__.py b/src/submissions/backend/validators/__init__.py index a32bdca..7e9512e 100644 --- a/src/submissions/backend/validators/__init__.py +++ b/src/submissions/backend/validators/__init__.py @@ -265,5 +265,5 @@ class RSLNamer(object): return "" -from .pydant import PydRun, PydKitType, PydContact, PydClientLab, PydSample, PydReagent, PydReagentRole, \ +from .pydant import PydRun, PydContact, PydClientLab, PydSample, PydReagent, PydReagentRole, \ PydEquipment, PydEquipmentRole, PydTips, PydProcess, PydElastic, PydClientSubmission, PydProcedure, PydResults diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 5b75d7b..491701f 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -1497,9 +1497,11 @@ class PydClientSubmission(PydBaseClass): @field_validator("sample_count") @classmethod def enforce_integer(cls, value): + if not value['value']: + value['value'] = 0 try: value['value'] = int(value['value']) - except ValueError: + except (ValueError, TypeError): raise f"sample count value must be an integer" return value diff --git a/src/submissions/frontend/visualizations/__init__.py b/src/submissions/frontend/visualizations/__init__.py index 16f2a3a..3d4541b 100644 --- a/src/submissions/frontend/visualizations/__init__.py +++ b/src/submissions/frontend/visualizations/__init__.py @@ -1,10 +1,9 @@ -''' +""" Contains all operations for creating charts, graphs and visual effects. -''' +""" from datetime import timedelta, date from pathlib import Path from typing import Generator - import plotly from PyQt6.QtWidgets import QWidget import pandas as pd, logging @@ -128,13 +127,10 @@ class CustomFigure(Figure): html = f'' if self is not None: # NOTE: Just cannot get this load from string to freaking work. - # html += self.to_html(include_plotlyjs='cdn', full_html=False) html += plotly.offline.plot(self, output_type='div', include_plotlyjs="cdn") else: html += "

No data was retrieved for the given parameters.

" html += '' - # with open("test.html", "w", encoding="utf-8") as f: - # f.write(html) return html diff --git a/src/submissions/frontend/visualizations/concentrations_chart.py b/src/submissions/frontend/visualizations/concentrations_chart.py index bd26db1..bf248e9 100644 --- a/src/submissions/frontend/visualizations/concentrations_chart.py +++ b/src/submissions/frontend/visualizations/concentrations_chart.py @@ -3,10 +3,9 @@ Construct BC control concentration charts """ from pprint import pformat from . import CustomFigure -import plotly.express as px +import logging, sys, plotly.express as px import pandas as pd from PyQt6.QtWidgets import QWidget -import logging from operator import itemgetter logger = logging.getLogger(f"submissions.{__name__}") @@ -31,7 +30,6 @@ class ConcentrationsChart(CustomFigure): self.df = self.df.sort_values(['submitted_date', 'procedure'], ascending=[True, True]).reset_index( drop=True) self.df = self.df.reset_index().rename(columns={"index": "idx"}) - # logger.debug(f"DF after changes:\n{self.df}") scatter = px.scatter(data_frame=self.df, x='procedure', y="concentration", hover_data=["name", "procedure", "submitted_date", "concentration"], color="positive", color_discrete_map={"positive": "red", "negative": "green", "sample":"orange"} diff --git a/src/submissions/frontend/visualizations/irida_charts.py b/src/submissions/frontend/visualizations/irida_charts.py index eb6336d..7df50b8 100644 --- a/src/submissions/frontend/visualizations/irida_charts.py +++ b/src/submissions/frontend/visualizations/irida_charts.py @@ -3,11 +3,9 @@ Functions for constructing irida control graphs using plotly. """ from datetime import date from pprint import pformat -import plotly.express as px -import pandas as pd +import logging, plotly.express as px, pandas as pd from PyQt6.QtWidgets import QWidget from . import CustomFigure -import logging from tools import get_unique_values_in_df_column logger = logging.getLogger(f"submissions.{__name__}") diff --git a/src/submissions/frontend/widgets/app.py b/src/submissions/frontend/widgets/app.py index b4e5705..ad7960f 100644 --- a/src/submissions/frontend/widgets/app.py +++ b/src/submissions/frontend/widgets/app.py @@ -22,9 +22,9 @@ from .date_type_picker import DateTypePicker from .functions import select_save_file from .pop_ups import HTMLPop from .misc import Pagifier -from .submission_table import SubmissionsSheet, SubmissionsTree, ClientSubmissionRunModel +from .submission_table import SubmissionsTree, ClientSubmissionRunModel from .submission_widget import SubmissionFormContainer -from .controls_chart import ControlsViewer +# from .controls_chart import ControlsViewer from .summary import Summary from .turnaround import TurnaroundTime from .concentrations import Concentrations @@ -132,7 +132,7 @@ class App(QMainWindow): self.table_widget.pager.current_page.textChanged.connect(self.update_data) self.editReagentAction.triggered.connect(self.edit_reagent) self.manageOrgsAction.triggered.connect(self.manage_orgs) - self.manageKitsAction.triggered.connect(self.manage_kits) + # self.manageKitsAction.triggered.connect(self.manage_kits) def showAbout(self): """ @@ -195,24 +195,23 @@ class App(QMainWindow): new_org = dlg.parse_form() new_org.save() - def manage_kits(self, *args, **kwargs): - from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd - dlg = ManagerWindowPyd(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set()) - if dlg.exec(): - # logger.debug("\n\nBeginning parsing\n\n") - output = dlg.parse_form() - # logger.debug(f"Kit output: {pformat(output.__dict__)}") - # logger.debug("\n\nBeginning transformation\n\n") - sql = output.to_sql() - assert isinstance(sql, KitType) - sql.save() + # def manage_kits(self, *args, **kwargs): + # from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd + # dlg = ManagerWindowPyd(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set()) + # if dlg.exec(): + # output = dlg.parse_form() + # sql = output.to_sql() + # assert isinstance(sql, KitType) + # sql.save() @under_development def submissions_to_excel(self, *args, **kwargs): + from backend.db.models import Run dlg = DateTypePicker(self) if dlg.exec(): output = dlg.parse_form() - df = BasicRun.archive_submissions(**output) + # TODO: Move to ClientSubmissions + df = Run.archive_submissions(**output) filepath = select_save_file(self, f"Submissions {output['start_date']}-{output['end_date']}", "xlsx") writer = ExcelWriter(filepath, "openpyxl") df.to_excel(writer) @@ -254,7 +253,6 @@ class AddSubForm(QWidget): self.sheetwidget = QWidget(self) self.sheetlayout = QVBoxLayout(self) self.sheetwidget.setLayout(self.sheetlayout) - # self.sub_wid = SubmissionsSheet(parent=parent) self.sub_wid = SubmissionsTree(parent=parent, model=ClientSubmissionRunModel(self)) self.pager = Pagifier(page_max=self.sub_wid.total_count / page_size) self.sheetlayout.addWidget(self.sub_wid) @@ -265,12 +263,10 @@ class AddSubForm(QWidget): self.tab1.layout.addWidget(self.interior) self.tab1.layout.addWidget(self.sheetwidget) self.tab2.layout = QVBoxLayout(self) - # self.irida_viewer = ControlsViewer(self, archetype="Irida Control") self.irida_viewer = None self.tab2.layout.addWidget(self.irida_viewer) self.tab2.setLayout(self.tab2.layout) self.tab3.layout = QVBoxLayout(self) - # self.pcr_viewer = ControlsViewer(self, archetype="PCR Control") self.pcr_viewer = None self.tab3.layout.addWidget(self.pcr_viewer) self.tab3.setLayout(self.tab3.layout) diff --git a/src/submissions/frontend/widgets/concentrations.py b/src/submissions/frontend/widgets/concentrations.py index bc11c33..1c48f7f 100644 --- a/src/submissions/frontend/widgets/concentrations.py +++ b/src/submissions/frontend/widgets/concentrations.py @@ -43,10 +43,8 @@ class Concentrations(InfoPane): None """ include = self.pos_neg.get_checked() - # logger.debug(f"Include: {include}") super().update_data() months = self.diff_month(self.start_date, self.end_date) - # logger.debug(f"Box checked: {self.all_box.isChecked()}") chart_settings = dict(start_date=self.start_date, end_date=self.end_date, include=include) self.report_obj = ConcentrationMaker(**chart_settings) diff --git a/src/submissions/frontend/widgets/controls_chart.py b/src/submissions/frontend/widgets/controls_chart.py index afd9ffa..2915880 100644 --- a/src/submissions/frontend/widgets/controls_chart.py +++ b/src/submissions/frontend/widgets/controls_chart.py @@ -108,7 +108,6 @@ class ControlsViewer(InfoPane): parent=self, months=months ) - # logger.debug(f"Chart settings: {chart_settings}") self.fig = self.archetype.instance_class.make_chart(chart_settings=chart_settings, parent=self, ctx=self.app.ctx) self.report_obj = ChartReportMaker(df=self.fig.df, sheet_name=self.archetype.name) if issubclass(self.fig.__class__, CustomFigure): diff --git a/src/submissions/frontend/widgets/date_type_picker.py b/src/submissions/frontend/widgets/date_type_picker.py index 604e1de..9ca8395 100644 --- a/src/submissions/frontend/widgets/date_type_picker.py +++ b/src/submissions/frontend/widgets/date_type_picker.py @@ -1,8 +1,14 @@ +""" + +""" from PyQt6.QtWidgets import ( QVBoxLayout, QDialog, QDialogButtonBox ) from .misc import CheckableComboBox, StartEndDatePicker from backend.db.models.procedures import SubmissionType +import logging + +logger = logging.getLogger(f"submissions.{__name__}") class DateTypePicker(QDialog): @@ -27,10 +33,7 @@ class DateTypePicker(QDialog): self.setLayout(self.layout) def parse_form(self): - # sub_types = [self.typepicker.itemText(i) for i in range(self.typepicker.count()) if self.typepicker.itemChecked(i)] sub_types = self.typepicker.get_checked() start_date = self.datepicker.start_date.date().toPyDate() end_date = self.datepicker.end_date.date().toPyDate() return dict(submissiontype=sub_types, start_date=start_date, end_date=end_date) - - diff --git a/src/submissions/frontend/widgets/equipment_usage.py b/src/submissions/frontend/widgets/equipment_usage.py index 919f48e..be2759d 100644 --- a/src/submissions/frontend/widgets/equipment_usage.py +++ b/src/submissions/frontend/widgets/equipment_usage.py @@ -1,91 +1,97 @@ -''' +""" Creates forms that the user can enter equipment info into. -''' +""" +import sys, logging from pprint import pformat -from PyQt6.QtCore import Qt, QSignalBlocker -from PyQt6.QtWidgets import ( - QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout -) -from backend.db.models import Equipment, Run, Process, Procedure -from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips -import logging from typing import Generator +from PyQt6.QtCore import Qt, pyqtSlot, QSignalBlocker +from PyQt6.QtWebChannel import QWebChannel +from PyQt6.QtWebEngineWidgets import QWebEngineView +from PyQt6.QtWidgets import ( + QDialog, QVBoxLayout, QDialogButtonBox, QGridLayout, QWidget, QCheckBox, QComboBox, QLabel +) +from backend import Process +from backend.db.models import Equipment +from backend.validators.pydant import PydProcedure, PydEquipmentRole, PydTips, PydEquipment +from tools import get_application_from_parent, render_details_template logger = logging.getLogger(f"submissions.{__name__}") class EquipmentUsage(QDialog): - def __init__(self, parent, procedure: Procedure): + def __init__(self, parent, procedure: PydProcedure): super().__init__(parent) self.procedure = procedure self.setWindowTitle(f"Equipment Checklist - {procedure.name}") self.used_equipment = self.procedure.equipment - # self.kit = self.procedure.kittype + self.kit = self.procedure.kittype self.opt_equipment = procedure.proceduretype.get_equipment() self.layout = QVBoxLayout() + self.app = get_application_from_parent(parent) + self.webview = QWebEngineView(parent=self) + self.webview.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) + self.webview.setMinimumSize(1200, 800) + self.webview.setMaximumWidth(1200) + # NOTE: Decide if exporting should be allowed. + self.layout = QGridLayout() + # NOTE: button to export a pdf version + self.layout.addWidget(self.webview, 1, 0, 10, 10) self.setLayout(self.layout) - self.populate_form() - - def populate_form(self): - """ - Create form widgets - """ + self.setFixedWidth(self.webview.width() + 20) + # NOTE: setup channel + self.channel = QWebChannel() + self.channel.registerObject('backend', self) + html = self.construct_html(procedure=procedure) + self.webview.setHtml(html) + self.webview.page().setWebChannel(self.channel) QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel self.buttonBox = QDialogButtonBox(QBtn) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) - label = self.LabelRow(parent=self) - self.layout.addWidget(label) - for equipment in self.opt_equipment: - widg = equipment.to_form(parent=self, used=self.used_equipment) - self.layout.addWidget(widg) - widg.update_processes() - self.layout.addWidget(self.buttonBox) + self.layout.addWidget(self.buttonBox, 11, 1, 1, 1) - def parse_form(self) -> Generator[PydEquipment, None, None]: - """ - Pull info from all RoleComboBox widgets + @classmethod + def construct_html(cls, procedure: PydProcedure, child: bool = False): + proceduretype = procedure.proceduretype + proceduretype_dict = proceduretype.details_dict() + run = procedure.run + html = render_details_template( + template_name="support/equipment_usage", + css_in=[], + js_in=[], + proceduretype=proceduretype_dict, + run=run.details_dict(), + procedure=procedure.__dict__, + child=child + ) + return html - Returns: - Generator[PydEquipment, None, None]: All equipment pulled from widgets - """ - for widget in self.findChildren(QWidget): - match widget: - case RoleComboBox(): - if widget.check.isChecked(): - item = widget.parse_form() - if item: - yield item - else: - continue - else: - continue - case _: - continue + @pyqtSlot(str, str, str, str) + def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str): + try: + equipment_of_interest = next( + (item for item in self.procedure.equipment if item.equipmentrole == equipmentrole)) + except StopIteration: + equipment_of_interest = None + equipment = Equipment.query(name=equipment) + if equipment_of_interest: + eoi = self.procedure.equipment.pop(self.procedure.equipment.index(equipment_of_interest)) + else: + eoi = equipment.to_pydantic(proceduretype=self.procedure.proceduretype) + eoi.name = equipment.name + eoi.asset_number = equipment.asset_number + eoi.nickname = equipment.nickname + process = next((prcss for prcss in equipment.process if prcss.name == process)) + eoi.process = process.to_pydantic() + tips = next((tps for tps in equipment.tips if tps.name == tips)) + eoi.tips = tips.to_pydantic() + self.procedure.equipment.append(eoi) + logger.debug(f"Updated equipment: {self.procedure.equipment}") - class LabelRow(QWidget): - """Provides column headers""" - - def __init__(self, parent) -> None: - super().__init__(parent) - self.layout = QGridLayout() - self.check = QCheckBox() - self.layout.addWidget(self.check, 0, 0) - self.check.stateChanged.connect(self.check_all) - for iii, item in enumerate(["Role", "Equipment", "Process", "Tips"], start=1): - label = QLabel(item) - label.setMaximumWidth(200) - label.setMinimumWidth(200) - self.layout.addWidget(label, 0, iii, alignment=Qt.AlignmentFlag.AlignRight) - self.setLayout(self.layout) - - def check_all(self): - """ - Toggles all checkboxes in the form - """ - for object in self.parent().findChildren(QCheckBox): - object.setChecked(self.check.isChecked()) + def save_procedure(self): + sql, _ = self.procedure.to_sql() + sql.save() class RoleComboBox(QWidget): @@ -124,7 +130,6 @@ class RoleComboBox(QWidget): """ equip = self.box.currentText() equip2 = next((item for item in self.role.equipment if item.name == equip), self.role.equipment[0]) - logger.debug(f"Equip2: {equip2}") with QSignalBlocker(self.process) as blocker: self.process.clear() self.process.addItems([item for item in equip2.process if item in self.role.process]) @@ -180,7 +185,7 @@ class RoleComboBox(QWidget): def toggle_checked(self): """ If this equipment is disabled, the input fields will be disabled. - """ + """ for widget in self.findChildren(QWidget): match widget: case QCheckBox(): diff --git a/src/submissions/frontend/widgets/equipment_usage_2.py b/src/submissions/frontend/widgets/equipment_usage_2.py deleted file mode 100644 index 7dc4755..0000000 --- a/src/submissions/frontend/widgets/equipment_usage_2.py +++ /dev/null @@ -1,131 +0,0 @@ -''' -Creates forms that the user can enter equipment info into. -''' -import sys -from pprint import pformat -from PyQt6.QtCore import Qt, QSignalBlocker, pyqtSlot -from PyQt6.QtWebChannel import QWebChannel -from PyQt6.QtWebEngineWidgets import QWebEngineView -from PyQt6.QtWidgets import ( - QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout -) -from backend.db.models import Equipment, Run, Process, Procedure, Tips -from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips, PydProcedure -import logging -from typing import Generator - -from tools import get_application_from_parent, render_details_template, flatten_list - -logger = logging.getLogger(f"submissions.{__name__}") - - -class EquipmentUsage(QDialog): - - def __init__(self, parent, procedure: PydProcedure): - super().__init__(parent) - self.procedure = procedure - self.setWindowTitle(f"Equipment Checklist - {procedure.name}") - self.used_equipment = self.procedure.equipment - self.kit = self.procedure.kittype - self.opt_equipment = procedure.proceduretype.get_equipment() - self.layout = QVBoxLayout() - self.app = get_application_from_parent(parent) - self.webview = QWebEngineView(parent=self) - self.webview.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) - self.webview.setMinimumSize(1200, 800) - self.webview.setMaximumWidth(1200) - # NOTE: Decide if exporting should be allowed. - # self.webview.loadFinished.connect(self.activate_export) - self.layout = QGridLayout() - # NOTE: button to export a pdf version - self.layout.addWidget(self.webview, 1, 0, 10, 10) - self.setLayout(self.layout) - self.setFixedWidth(self.webview.width() + 20) - # NOTE: setup channel - self.channel = QWebChannel() - self.channel.registerObject('backend', self) - html = self.construct_html(procedure=procedure) - self.webview.setHtml(html) - self.webview.page().setWebChannel(self.channel) - QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel - self.buttonBox = QDialogButtonBox(QBtn) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) - self.layout.addWidget(self.buttonBox, 11, 1, 1, 1) - - @classmethod - def construct_html(cls, procedure: PydProcedure, child: bool = False): - proceduretype = procedure.proceduretype - proceduretype_dict = proceduretype.details_dict() - run = procedure.run - # proceduretype_dict['equipment_json'] = flatten_list([item['equipment_json'] for item in proceduretype_dict['equipment']]) - # proceduretype_dict['equipment_json'] = [ - # {'name': 'Liquid Handler', 'equipment': [ - # {'name': 'Other', 'asset_number': 'XXX', 'processes': [ - # {'name': 'Trust Me', 'tips': ['Blah']}, - # {'name': 'No Me', 'tips': ['Blah', 'Crane']} - # ] - # }, - # {'name': 'Biomek', 'asset_number': '5015530', 'processes': [ - # {'name': 'Sample Addition', 'tips': ['Axygen 20uL'] - # } - # ] - # } - # ] - # } - # ] - # if procedure.equipment: - # for equipmentrole in proceduretype_dict['equipment']: - # # NOTE: Check if procedure equipment is present and move to head of the list if so. - # try: - # relevant_procedure_item = next((equipment for equipment in procedure.equipment if - # equipment.equipmentrole == equipmentrole['name'])) - # except StopIteration: - # continue - # item_in_er_list = next((equipment for equipment in equipmentrole['equipment'] if - # equipment['name'] == relevant_procedure_item.name)) - # equipmentrole['equipment'].insert(0, equipmentrole['equipment'].pop( - # equipmentrole['equipment'].index(item_in_er_list))) - html = render_details_template( - template_name="support/equipment_usage", - css_in=[], - js_in=[], - proceduretype=proceduretype_dict, - run=run.details_dict(), - procedure=procedure.__dict__, - child=child - ) - return html - - @pyqtSlot(str, str, str, str) - def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str): - - try: - equipment_of_interest = next( - (item for item in self.procedure.equipment if item.equipmentrole == equipmentrole)) - except StopIteration: - equipment_of_interest = None - equipment = Equipment.query(name=equipment) - if equipment_of_interest: - eoi = self.procedure.equipment.pop(self.procedure.equipment.index(equipment_of_interest)) - else: - eoi = equipment.to_pydantic(proceduretype=self.procedure.proceduretype) - eoi.name = equipment.name - eoi.asset_number = equipment.asset_number - eoi.nickname = equipment.nickname - process = next((prcss for prcss in equipment.process if prcss.name == process)) - eoi.process = process.to_pydantic() - tips = next((tps for tps in equipment.tips if tps.name == tips)) - eoi.tips = tips.to_pydantic() - self.procedure.equipment.append(eoi) - logger.debug(f"Updated equipment: {self.procedure.equipment}") - - def save_procedure(self): - sql, _ = self.procedure.to_sql() - logger.debug(pformat(sql.__dict__)) - # import pickle - # with open("sql.pickle", "wb") as f: - # pickle.dump(sql, f) - # with open("pyd.pickle", "wb") as f: - # pickle.dump(self.procedure, f) - sql.save() diff --git a/src/submissions/frontend/widgets/functions.py b/src/submissions/frontend/widgets/functions.py index cb97621..3a564b0 100644 --- a/src/submissions/frontend/widgets/functions.py +++ b/src/submissions/frontend/widgets/functions.py @@ -39,7 +39,7 @@ def select_open_file(obj: QMainWindow, file_extension: str | None = None) -> Pat logger.warning(f"No file selected, cancelling.") return obj.last_dir = fname.parent - logger.debug(f"File selected: {fname}") + logger.info(f"File selected: {fname}") return fname diff --git a/src/submissions/frontend/widgets/gel_checker.py b/src/submissions/frontend/widgets/gel_checker.py index a3c23a1..a2fc9d5 100644 --- a/src/submissions/frontend/widgets/gel_checker.py +++ b/src/submissions/frontend/widgets/gel_checker.py @@ -5,10 +5,9 @@ from operator import itemgetter from PyQt6.QtWidgets import ( QWidget, QDialog, QGridLayout, QLabel, QLineEdit, QDialogButtonBox, QTextEdit, QComboBox ) -import pyqtgraph as pg from PyQt6.QtGui import QIcon from PIL import Image -import logging, numpy as np +import logging, numpy as np, pyqtgraph as pg from pprint import pformat from typing import Tuple, List from pathlib import Path diff --git a/src/submissions/frontend/widgets/info_tab.py b/src/submissions/frontend/widgets/info_tab.py index 69e80ee..49765ca 100644 --- a/src/submissions/frontend/widgets/info_tab.py +++ b/src/submissions/frontend/widgets/info_tab.py @@ -34,7 +34,6 @@ class InfoPane(QWidget): report = Report() self.start_date = self.datepicker.start_date.date().toPyDate() self.end_date = self.datepicker.end_date.date().toPyDate() - # logger.debug(f"Start date: {self.start_date}, End date: {self.end_date}") if self.datepicker.start_date.date() > self.datepicker.end_date.date(): lastmonth = self.datepicker.end_date.date().addDays(-31) msg = f"Start date after end date is not allowed! Setting to {lastmonth.toString()}." diff --git a/src/submissions/frontend/widgets/misc.py b/src/submissions/frontend/widgets/misc.py index 71eb0e0..ec39af1 100644 --- a/src/submissions/frontend/widgets/misc.py +++ b/src/submissions/frontend/widgets/misc.py @@ -1,7 +1,7 @@ """ Contains miscellaneous widgets for frontend functions """ -import math +import math, logging from PyQt6.QtGui import QStandardItem, QIcon from PyQt6.QtWidgets import ( QLabel, QLineEdit, QComboBox, QDateEdit, QPushButton, QWidget, @@ -10,7 +10,6 @@ from PyQt6.QtWidgets import ( from PyQt6.QtCore import Qt, QDate, QSize from tools import jinja_template_loading from backend.db.models import * -import logging logger = logging.getLogger(f"submissions.{__name__}") diff --git a/src/submissions/frontend/widgets/procedure_creation.py b/src/submissions/frontend/widgets/procedure_creation.py index 99b79fe..3ec2a32 100644 --- a/src/submissions/frontend/widgets/procedure_creation.py +++ b/src/submissions/frontend/widgets/procedure_creation.py @@ -8,7 +8,7 @@ from PyQt6.QtCore import pyqtSlot, Qt from PyQt6.QtWebChannel import QWebChannel from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWidgets import QDialog, QGridLayout, QDialogButtonBox -from typing import TYPE_CHECKING, Any, List +from typing import TYPE_CHECKING, List if TYPE_CHECKING: from backend.validators import PydProcedure, PydEquipment from tools import get_application_from_parent, render_details_template, sanitize_object_for_json @@ -52,7 +52,7 @@ class ProcedureCreation(QDialog): def set_html(self): - from .equipment_usage_2 import EquipmentUsage + from .equipment_usage import EquipmentUsage proceduretype_dict = self.proceduretype.details_dict() # NOTE: Add --New-- as an option for reagents. for key, value in self.procedure.reagentrole.items(): @@ -88,11 +88,6 @@ class ProcedureCreation(QDialog): @pyqtSlot(str, str, str, str) def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str): from backend.db.models import Equipment, ProcessVersion, TipsLot - logger.debug(f"Updating equipment with" - f"\n\tEquipment role: {equipmentrole}" - f"\n\tEquipment: {equipment}" - f"\n\tProcess: {process}" - f"\n\tTips: {tips}") try: equipment_of_interest = next( (item for item in self.procedure.equipment if item.equipmentrole == equipmentrole)) @@ -109,19 +104,13 @@ class ProcedureCreation(QDialog): process_name, version = process.split("-v") process = ProcessVersion.query(name=process_name, version=version, limit=1) eoi.process = process - # sys.exit(f"Process:\n{pformat(eoi.process.__dict__)}") try: tips_manufacturer, tipsref, lot = [item if item != "" else None for item in tips.split("-")] - logger.debug(f"Querying with '{tips_manufacturer}', '{tipsref}', '{lot}'") tips = TipsLot.query(manufacturer=tips_manufacturer, ref=tipsref, lot=lot) - logger.debug(f"Found tips: {tips}") eoi.tips = tips except ValueError: logger.warning(f"No tips info to unpack") - # tips = TipsLot.query(manufacturer=tips_manufacturer, ref=tipsref, lot=lot) - # eoi.tips = tips self.procedure.equipment.append(eoi) - logger.debug(f"Updated equipment:\n{pformat([item.__dict__ for item in self.procedure.equipment])}") @pyqtSlot(str, str) def text_changed(self, key: str, new_value: str): @@ -167,11 +156,9 @@ class ProcedureCreation(QDialog): @pyqtSlot(str, str) def update_reagent(self, reagentrole: str, name_lot_expiry: str): - logger.debug(f"{reagentrole}: {name_lot_expiry}") try: name, lot, expiry = name_lot_expiry.split(" - ") except ValueError as e: - logger.debug(f"Couldn't perform split due to {e}") return self.procedure.update_reagents(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry) diff --git a/src/submissions/frontend/widgets/sample_checker.py b/src/submissions/frontend/widgets/sample_checker.py index 368ec6d..761aa63 100644 --- a/src/submissions/frontend/widgets/sample_checker.py +++ b/src/submissions/frontend/widgets/sample_checker.py @@ -1,5 +1,7 @@ +""" + +""" import logging -from pathlib import Path from typing import List from PyQt6.QtCore import Qt, pyqtSlot from PyQt6.QtWebChannel import QWebChannel @@ -22,7 +24,6 @@ class SampleChecker(QDialog): self.rsl_plate_number = RSLNamer.construct_new_plate_name(clientsubmission.to_dict()) else: self.rsl_plate_number = clientsubmission - logger.debug(f"RSL Plate number: {self.rsl_plate_number}") self.samples = samples self.setWindowTitle(title) self.app = get_application_from_parent(parent) @@ -35,16 +36,11 @@ class SampleChecker(QDialog): self.channel = QWebChannel() self.channel.registerObject('backend', self) # NOTE: Used to maintain javascript functions. - # template = env.get_template("sample_checker.html") - # template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) - # with open(template_path.joinpath("css", "styles.css"), "r") as f: - # css = [f.read()] try: samples = self.formatted_list except AttributeError as e: logger.error(f"Problem getting sample list: {e}") samples = [] - # html = template.render(samples=samples, css=css, rsl_plate_number=self.rsl_plate_number) html = render_details_template(template_name="sample_checker", samples=samples, rsl_plate_number=self.rsl_plate_number) self.webview.setHtml(html) self.webview.page().setWebChannel(self.channel) @@ -55,13 +51,8 @@ class SampleChecker(QDialog): self.layout.addWidget(self.buttonBox, 11, 9, 1, 1, alignment=Qt.AlignmentFlag.AlignRight) self.setLayout(self.layout) - # with open("sample_checker_rendered.html", "w") as f: - # f.write(html) - logger.debug(f"HTML sample checker written!") - @pyqtSlot(str, str, str) def text_changed(self, submission_rank: str, key: str, new_value: str): - logger.debug(f"Name: {submission_rank}, Key: {key}, Value: {new_value}") try: item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank)) except StopIteration: @@ -71,7 +62,6 @@ class SampleChecker(QDialog): @pyqtSlot(int, bool) def enable_sample(self, submission_rank: int, enabled: bool): - logger.debug(f"Name: {submission_rank}, Enabled: {enabled}") try: item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank)) except StopIteration: @@ -81,14 +71,12 @@ class SampleChecker(QDialog): @pyqtSlot(str) def set_rsl_plate_number(self, rsl_plate_number: str): - logger.debug(f"RSL plate num: {rsl_plate_number}") self.rsl_plate_number = rsl_plate_number @property def formatted_list(self) -> List[dict]: output = [] for sample in self.samples: - # logger.debug(sample) s = sample.improved_dict(dictionaries=False) if s['sample_id'] in [item['sample_id'] for item in output]: s['color'] = "red" diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index 27f52c6..ebbc890 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -62,14 +62,9 @@ class SubmissionDetails(QDialog): css = f.read() key = object.__class__.__name__.lower() d = {key: details} - # logger.debug(f"Using details: {pformat(d['procedure']['equipment'])}") html = template.render(**d, css=[css]) self.webview.setHtml(html) self.setWindowTitle(f"{object.__class__.__name__} Details - {object.name}") - # with open(f"{object.__class__.__name__}_details_rendered.html", "w") as f: - # f.write(html) - # pass - def activate_export(self) -> None: """ @@ -96,10 +91,10 @@ class SubmissionDetails(QDialog): @pyqtSlot(str) def equipment_details(self, equipment: str | Equipment): - logger.debug(f"Equipment details") if isinstance(equipment, str): equipment = Equipment.query(name=equipment) - base_dict = equipment.to_sub_dict(full_data=True) + # base_dict = equipment.to_sub_dict(full_data=True) + base_dict = equipment.details_dict() template = equipment.details_template template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) with open(template_path.joinpath("css", "styles.css"), "r") as f: @@ -110,10 +105,10 @@ class SubmissionDetails(QDialog): @pyqtSlot(str) def process_details(self, process: str | Process): - logger.debug(f"Process details") if isinstance(process, str): process = Process.query(name=process) - base_dict = process.to_sub_dict(full_data=True) + # base_dict = process.to_sub_dict(full_data=True) + base_dict = process.details_dict() template = process.details_template template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) with open(template_path.joinpath("css", "styles.css"), "r") as f: @@ -124,10 +119,10 @@ class SubmissionDetails(QDialog): @pyqtSlot(str) def tips_details(self, tips: str | Tips): - logger.debug(f"Equipment details: {tips}") if isinstance(tips, str): tips = Tips.query(lot=tips) - base_dict = tips.to_sub_dict(full_data=True) + # base_dict = tips.to_sub_dict(full_data=True) + base_dict = tips.details_dict() template = tips.details_template template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) with open(template_path.joinpath("css", "styles.css"), "r") as f: @@ -144,10 +139,10 @@ class SubmissionDetails(QDialog): Args: sample (str): Submitter Id of the sample. """ - logger.debug(f"Sample details.") if isinstance(sample, str): sample = Sample.query(sample_id=sample) - base_dict = sample.to_sub_dict(full_data=True) + # base_dict = sample.to_sub_dict(full_data=True) + base_dict = sample.details_dict() exclude = ['procedure', 'excluded', 'colour', 'tooltip'] base_dict['excluded'] = exclude template = sample.details_template @@ -155,8 +150,6 @@ class SubmissionDetails(QDialog): with open(template_path.joinpath("css", "styles.css"), "r") as f: css = f.read() html = template.render(sample=base_dict, css=css) - # with open(f"{sample.sample_id}.html", 'w') as f: - # f.write(html) self.webview.setHtml(html) self.setWindowTitle(f"Sample Details - {sample.sample_id}") @@ -169,13 +162,13 @@ class SubmissionDetails(QDialog): kit (str | KitType): Name of kittype. reagent (str | Reagent): Lot number of the reagent """ - logger.debug(f"Reagent details.") if isinstance(reagent, str): reagent = Reagent.query(lot=reagent) if isinstance(proceduretype, str): self.proceduretype = ProcedureType.query(name=proceduretype) - base_dict = reagent.to_sub_dict(proceduretype=self.proceduretype, full_data=True) + # base_dict = reagent.to_sub_dict(proceduretype=self.proceduretype, full_data=True) # base_dict = reagent.details_dict(proceduretype=self.proceduretype, full_data=True) + base_dict = reagent.details_dict() env = jinja_template_loading() temp_name = "reagent_details.html" try: @@ -221,7 +214,6 @@ class SubmissionDetails(QDialog): Args: run (str | BasicRun): Submission of interest. """ - logger.debug(f"Run details.") if isinstance(run, str): run = Run.query(name=run) self.rsl_plate_number = run.rsl_plate_number @@ -234,7 +226,6 @@ class SubmissionDetails(QDialog): template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0]) with open(template_path.joinpath("css", "styles.css"), "r") as f: css = f.read() - # logger.debug(f"Base dictionary of procedure {self.name}: {pformat(self.base_dict)}") self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css) self.webview.setHtml(self.html) diff --git a/src/submissions/frontend/widgets/submission_table.py b/src/submissions/frontend/widgets/submission_table.py index 4230cd7..b6c827e 100644 --- a/src/submissions/frontend/widgets/submission_table.py +++ b/src/submissions/frontend/widgets/submission_table.py @@ -4,13 +4,11 @@ Contains widgets specific to the procedure summary and procedure details. import sys, logging, re from pprint import pformat - from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, QStyle, QStyleOptionViewItem, \ QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor, QContextMenuEvent from typing import Dict, List - # from backend import Procedure from backend.db.models.submissions import Run, ClientSubmission from backend.db.models.procedures import Procedure @@ -91,7 +89,6 @@ class SubmissionsSheet(QTableView): """ sets data in model """ - # self.data = ClientSubmission.submissions_to_df(page=page, page_size=page_size) self.data = Run.submissions_to_df(page=page, page_size=page_size) try: self.data['Id'] = self.data['Id'].apply(str) @@ -232,29 +229,6 @@ class SubmissionsSheet(QTableView): return report -# class ClientSubmissionDelegate(QStyledItemDelegate): -# -# def __init__(self, parent=None): -# super(ClientSubmissionDelegate, self).__init__(parent) -# pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton -# icon1 = QWidget().style().standardIcon(pixmapi) -# pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton -# icon2 = QWidget().style().standardIcon(pixmapi) -# self._plus_icon = icon1 -# self._minus_icon = icon2 -# -# def initStyleOption(self, option, index): -# super(ClientSubmissionDelegate, self).initStyleOption(option, index) -# if not index.parent().isValid(): -# is_open = bool(option.state & QStyle.StateFlag.State_Open) -# option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration -# option.icon = self._minus_icon if is_open else self._plus_icon - - -# class RunDelegate(ClientSubmissionDelegate): -# pass - - class SubmissionsTree(QTreeView): """ https://stackoverflow.com/questions/54385437/how-can-i-make-a-table-that-can-collapse-its-rows-into-categories-in-qt @@ -264,20 +238,12 @@ class SubmissionsTree(QTreeView): super(SubmissionsTree, self).__init__(parent) self.app = get_application_from_parent(parent) self.total_count = ClientSubmission.__database_session__.query(ClientSubmission).count() - # self.setIndentation(0) self.setExpandsOnDoubleClick(False) - # self.clicked.connect(self.on_clicked) - # delegate1 = ClientSubmissionDelegate(self) - # self.setItemDelegateForColumn(0, delegate1) self.model = model self.setModel(self.model) - # self.header().setSectionResizeMode(0, QHeaderView.sectionResizeMode(self,0).ResizeToContents) self.setSelectionBehavior(QAbstractItemView.selectionBehavior(self).SelectRows) - # self.setStyleSheet("background-color: #0D1225;") self.set_data() self.doubleClicked.connect(self.show_details) - # self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) - # self.customContextMenuRequested.connect(self.open_menu) self.setStyleSheet(""" QTreeView { background-color: #f5f5f5; @@ -294,20 +260,13 @@ class SubmissionsTree(QTreeView): } """) - # Enable alternating row colors + # Note: Enable alternating row colors self.setAlternatingRowColors(True) self.setIndentation(20) self.setItemsExpandable(True) - # self.expanded.connect(self.expand_item) - for ii in range(2): self.resizeColumnToContents(ii) - # @pyqtSlot(QModelIndex) - # def on_clicked(self, index): - # if not index.parent().isValid() and index.column() == 0: - # self.setExpanded(index, not self.isExpanded(index)) - def expand_item(self, event: QModelIndex): logger.debug(f"Data: {event.data()}") logger.debug(f"Parent {event.parent().data()}") @@ -327,18 +286,11 @@ class SubmissionsTree(QTreeView): """ indexes = self.selectedIndexes() dicto = next((item.data(1) for item in indexes if item.data(1))) - logger.debug(f"Dicto: {pformat(dicto)}") query_obj = dicto['item_type'].query(name=dicto['query_str'], limit=1) - logger.debug(f"Querying: {query_obj}") # NOTE: Convert to data in id column (i.e. column 0) - # id = id.sibling(id.row(), 0).data() - # logger.debug(id.model().query_group_object(id.row())) - # clientsubmission = id.model().query_group_object(id.row()) self.menu = QMenu(self) self.con_actions = query_obj.custom_context_events - logger.debug(f"Context menu actions: {self.con_actions}") for key in self.con_actions.keys(): - logger.debug(key) match key.lower(): case "add procedure": action = QMenu(self.menu) @@ -362,7 +314,7 @@ class SubmissionsTree(QTreeView): action = QAction(key, self) action.triggered.connect(lambda _, action_name=key: self.con_actions[action_name](obj=self)) self.menu.addAction(action) - # # NOTE: add other required actions + # NOTE: add other required actions self.menu.popup(QCursor.pos()) def set_data(self, page: int = 1, page_size: int = 250) -> None: @@ -372,8 +324,6 @@ class SubmissionsTree(QTreeView): self.clear() self.data = [item.to_dict(full_data=True) for item in ClientSubmission.query(chronologic=True, page=page, page_size=page_size)] - # logger.debug(f"setting data:\n {pformat(self.data)}") - # sys.exit() root = self.model.invisibleRootItem() for submission in self.data: group_str = f"{submission['submissiontype']}-{submission['submitter_plate_id']}-{submission['submitted_date']}" @@ -382,26 +332,21 @@ class SubmissionsTree(QTreeView): query_str=submission['submitter_plate_id'], item_type=ClientSubmission )) - # logger.debug(f"Added {submission_item}") for run in submission['run']: - # self.model.append_element_to_group(group_item=group_item, element=run) run_item = self.model.add_child(parent=submission_item, child=dict( name=run['plate_number'], query_str=run['plate_number'], item_type=Run )) - # logger.debug(f"Added {run_item}") for procedure in run['procedures']: procedure_item = self.model.add_child(parent=run_item, child=dict( name=procedure['name'], query_str=procedure['name'], item_type=Procedure )) - # logger.debug(f"Added {procedure_item}") def _populateTree(self, children, parent): for child in children: - logger.debug(child) child_item = QStandardItem(child['name']) parent.appendRow(child_item) if isinstance(children, List): @@ -409,22 +354,13 @@ class SubmissionsTree(QTreeView): def clear(self): if self.model != None: - # self.model.clear() # works self.model.setRowCount(0) # works def show_details(self, sel: QModelIndex): - # id = self.selectionModel().currentIndex() # NOTE: Convert to data in id column (i.e. column 0) - # id = id.sibling(id.row(), 1) indexes = self.selectedIndexes() dicto = next((item.data(1) for item in indexes if item.data(1))) - # try: - # id = int(id.data()) - # except ValueError: - # return - # Run.query(id=id).show_details(self) obj = dicto['item_type'].query(name=dicto['query_str'], limit=1) - logger.debug(obj) obj.show_details(self) def link_extractions(self): @@ -437,14 +373,9 @@ class SubmissionsTree(QTreeView): class ClientSubmissionRunModel(QStandardItemModel): - def __init__(self, parent=None): - super(ClientSubmissionRunModel, self).__init__(parent) - # headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"] - # self.setColumnCount(len(headers)) - # self.setHorizontalHeaderLabels(headers) - - - + # def __init__(self, parent=None): + # super(ClientSubmissionRunModel, self).__init__(parent) + # def add_child(self, parent: QStandardItem, child:dict): item = QStandardItem(child['name']) item.setData(dict(item_type=child['item_type'], query_str=child['query_str']), 1) @@ -453,4 +384,4 @@ class ClientSubmissionRunModel(QStandardItemModel): return item def edit_item(self): - pass \ No newline at end of file + pass diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index 37c19f5..5843526 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -1,15 +1,13 @@ """ Contains all procedure related frontend functions """ -import sys - +import sys, logging from PyQt6.QtWidgets import ( QWidget, QPushButton, QVBoxLayout, QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout ) from PyQt6.QtCore import pyqtSignal, Qt, QSignalBlocker from .functions import select_open_file, select_save_file -import logging from pathlib import Path from tools import Report, Result, check_not_nan, main_form_style, report_result, get_application_from_parent from backend.validators import PydReagent, PydClientSubmission, PydSample @@ -121,37 +119,16 @@ class SubmissionFormContainer(QWidget): report.add_result(Result(msg=f"File {fname.__str__()} not found.", status="critical")) return report # NOTE: create sheetparser using excel sheet and context from gui - # try: - # self.clientsubmissionparser = ClientSubmissionInfoParser(filepath=fname) - # except PermissionError: - # logger.error(f"Couldn't get permission to access file: {fname}") - # return - # except AttributeError: - # self.clientsubmissionparser = ClientSubmissionInfoParser(filepath=fname) - # try: - # # self.prsr = SheetParser(filepath=fname) - # self.sampleparser = ClientSubmissionSampleParser(filepath=fname) - # except PermissionError: - # logger.error(f"Couldn't get permission to access file: {fname}") - # return - # except AttributeError: - # self.sampleparser = ClientSubmissionSampleParser(filepath=fname) - - # self.pydclientsubmission = self.clientsubmissionparser.to_pydantic() - # self.pydsamples = self.sampleparser.to_pydantic() - # logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}") self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, input_object=fname) self.pydclientsubmission = self.clientsubmission_manager.to_pydantic() checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.sample) if checker.exec(): - # logger.debug(pformat(self.pydclientsubmission.sample)) try: assert isinstance(self.pydclientsubmission, PydClientSubmission) except AssertionError as e: logger.error(f"Got wrong type for {self.pydclientsubmission}: {type(self.pydclientsubmission)}") raise e self.form = self.pydclientsubmission.to_form(parent=self) - # self.form.samples = self.pydsamples self.layout().addWidget(self.form) else: message = "Submission cancelled." @@ -195,14 +172,11 @@ class SubmissionFormWidget(QWidget): self.pyd = pyd self.missing_info = [] self.submissiontype = SubmissionType.query(name=self.pyd.submissiontype['value']) - # basic_submission_class = self.submission_type.submission_class - # logger.debug(f"Basic procedure class: {basic_submission_class}") defaults = Run.get_default_info("form_recover", "form_ignore", submissiontype=self.pyd.submissiontype['value']) self.recover = defaults['form_recover'] self.ignore = defaults['form_ignore'] self.layout = QVBoxLayout() for k in list(self.pyd.model_fields.keys()):# + list(self.pyd.model_extra.keys()): - logger.debug(f"Pydantic field: {k}") if k in self.ignore: logger.warning(f"{k} in form_ignore {self.ignore}, not creating widget") continue @@ -218,7 +192,6 @@ class SubmissionFormWidget(QWidget): value = self.pyd.model_extra[k] except KeyError: value = dict(value=None, missing=True) - logger.debug(f"Pydantic value: {value}") add_widget = self.create_widget(key=k, value=value, submission_type=self.submissiontype, run_object=Run(), disable=check) if add_widget is not None: @@ -230,7 +203,6 @@ class SubmissionFormWidget(QWidget): self.layout.addWidget(self.disabler) self.disabler.checkbox.checkStateChanged.connect(self.disable_reagents) self.setStyleSheet(main_form_style) - # self.scrape_reagents(self.kittype) self.setLayout(self.layout) def disable_reagents(self): @@ -298,7 +270,6 @@ class SubmissionFormWidget(QWidget): if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton): reagent.setParent(None) reagents, integrity_report, missing_reagents = self.pyd.check_kit_integrity(extraction_kit=self.extraction_kit) - # logger.debug(f"Reagents: {reagents}") expiry_report = self.pyd.check_reagent_expiries(exempt=missing_reagents) for reagent in reagents: add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.extraction_kit) @@ -364,34 +335,6 @@ class SubmissionFormWidget(QWidget): return report base_submission = self.pyd.to_sql() # NOTE: check output message for issues - # try: - # trigger = result.results[-1] - # code = trigger.code - # except IndexError as e: - # logger.error(result.results) - # logger.error(f"Problem getting error code: {e}") - # code = 0 - # match code: - # # NOTE: code 0: everything is fine. - # case 0: - # pass - # # NOTE: code 1: ask for overwrite - # case 1: - # dlg = QuestionAsker(title=f"Review {base_submission.rsl_plate_number}?", message=trigger.msg) - # if dlg.exec(): - # # NOTE: Do not add duplicate reagents. - # pass - # else: - # self.app.ctx.database_session.rollback() - # report.add_result(Result(msg="Overwrite cancelled", status="Information")) - # return report - # # NOTE: code 2: No RSL plate number given - # case 2: - # report.add_result(result) - # return report - # case _: - # pass - # NOTE: add reagents to procedure object if base_submission is None: return for reagent in base_submission.reagents: @@ -450,7 +393,6 @@ class SubmissionFormWidget(QWidget): if field is not None: info[field] = value self.pyd.reagents = reagents - # logger.debug(f"Reagents from form: {reagents}") for item in self.recover: if hasattr(self, item): value = getattr(self, item) @@ -558,29 +500,29 @@ class SubmissionFormWidget(QWidget): # NOTE: set combobox values to lookedup values add_widget.addItems(labs) add_widget.setToolTip("Select submitting lab.") - case 'kittype': - # NOTE: if extraction kittype not available, all other values fail - if not check_not_nan(value): - msg = AlertPop(message="Make sure to check your extraction kittype in the excel sheet!", - status="warning") - msg.exec() - # NOTE: create combobox to hold looked up kits - add_widget = MyQComboBox(scrollWidget=parent) - # NOTE: lookup existing kits by 'proceduretype' decided on by sheetparser - uses = [item.name for item in submission_type.kit_types] - obj.uses = uses - if check_not_nan(value): - try: - uses.insert(0, uses.pop(uses.index(value))) - except ValueError: - logger.warning(f"Couldn't find kittype in list, skipping move to top of list.") - obj.ext_kit = value - else: - logger.error(f"Couldn't find {obj.prsr.sub['kittype']}") - obj.ext_kit = uses[0] - add_widget.addItems(uses) - add_widget.setToolTip("Select extraction kittype.") - parent.extraction_kit = add_widget.currentText() + # case 'kittype': + # # NOTE: if extraction kittype not available, all other values fail + # if not check_not_nan(value): + # msg = AlertPop(message="Make sure to check your extraction kittype in the excel sheet!", + # status="warning") + # msg.exec() + # # NOTE: create combobox to hold looked up kits + # add_widget = MyQComboBox(scrollWidget=parent) + # # NOTE: lookup existing kits by 'proceduretype' decided on by sheetparser + # uses = [item.name for item in submission_type.kit_types] + # obj.uses = uses + # if check_not_nan(value): + # try: + # uses.insert(0, uses.pop(uses.index(value))) + # except ValueError: + # logger.warning(f"Couldn't find kittype in list, skipping move to top of list.") + # obj.ext_kit = value + # else: + # logger.error(f"Couldn't find {obj.prsr.sub['kittype']}") + # obj.ext_kit = uses[0] + # add_widget.addItems(uses) + # add_widget.setToolTip("Select extraction kittype.") + # parent.extraction_kit = add_widget.currentText() case 'submission_category': add_widget = MyQComboBox(scrollWidget=parent) categories = ['Diagnostic', "Surveillance", "Research"] @@ -813,11 +755,8 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): self.disabler.setHidden(True) except AttributeError: pass - # save_btn = QPushButton("Save") self.sample = samples - logger.debug(f"Samples: {self.sample}") start_run_btn = QPushButton("Save") - # self.layout.addWidget(save_btn) self.layout.addWidget(start_run_btn) start_run_btn.clicked.connect(self.create_new_submission) @@ -846,7 +785,6 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): field, value = widget.parse_form() if field is not None: info[field] = value - # logger.debug(f"Reagents from form: {reagents}") for item in self.recover: if hasattr(self, item): value = getattr(self, item) @@ -865,7 +803,6 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): @report_result def create_new_submission(self, *args) -> Report: pyd = self.to_pydantic() - logger.debug(f"Pydantic: {pyd}") sql = pyd.to_sql() for sample in pyd.sample: if isinstance(sample, PydSample): @@ -874,9 +811,7 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): if sample.sample_id.lower() in ["", "blank"]: continue sample.save() - # if sample not in sql.sample: sql.add_sample(sample=sample) - logger.debug(pformat(sql.__dict__)) try: del sql._misc_info['sample'] except KeyError: