Files
Submissions-App/src/submissions/frontend/widgets/misc.py
2023-11-14 11:16:54 -06:00

750 lines
35 KiB
Python

'''
Contains miscellaneous widgets for frontend functions
'''
from datetime import date
from pprint import pformat
from PyQt6 import QtCore
from PyQt6.QtWidgets import (
QLabel, QVBoxLayout,
QLineEdit, QComboBox, QDialog,
QDialogButtonBox, QDateEdit, QSizePolicy, QWidget,
QGridLayout, QPushButton, QSpinBox, QDoubleSpinBox,
QHBoxLayout, QScrollArea, QFormLayout
)
from PyQt6.QtCore import Qt, QDate, QSize, pyqtSignal
from tools import check_not_nan, jinja_template_loading, Settings, Result
from backend.db.models import *
from sqlalchemy import FLOAT, INTEGER
import logging
import numpy as np
from .pop_ups import AlertPop, QuestionAsker
from backend.validators import PydReagent, PydKit, PydReagentType, PydSubmission
from typing import Tuple, List
from pprint import pformat
import difflib
logger = logging.getLogger(f"submissions.{__name__}")
env = jinja_template_loading()
class AddReagentForm(QDialog):
"""
dialog to add gather info about new reagent
"""
def __init__(self, ctx:dict, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None, reagent_name:str|None=None) -> None:
super().__init__()
self.ctx = ctx
if reagent_lot == None:
reagent_lot = reagent_type
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)
# 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.name_input.setText(reagent_name)
self.lot_input = QLineEdit()
self.lot_input.setObjectName("lot")
self.lot_input.setText(reagent_lot)
# widget to get expiry info
self.exp_input = QDateEdit(calendarPopup=True)
self.exp_input.setObjectName('expiry')
# if expiry is not passed in from gui, use today
if expiry == None:
self.exp_input.setDate(QDate.currentDate())
else:
self.exp_input.setDate(expiry)
# widget to get reagent type info
self.type_input = QComboBox()
self.type_input.setObjectName('type')
# self.type_input.addItems([item.name for item in lookup_reagent_types(ctx=ctx)])
self.type_input.addItems([item.name for item in ReagentType.query()])
logger.debug(f"Trying to find index of {reagent_type}")
# convert input to user friendly string?
try:
reagent_type = reagent_type.replace("_", " ").title()
except AttributeError:
reagent_type = None
# set parsed reagent type to top of list
index = self.type_input.findText(reagent_type, Qt.MatchFlag.MatchEndsWith)
if index >= 0:
self.type_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.exp_input)
self.layout.addWidget(QLabel("Type:"))
self.layout.addWidget(self.type_input)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
self.type_input.currentTextChanged.connect(self.update_names)
def parse_form(self):
return dict(name=self.name_input.currentText(),
lot=self.lot_input.text(),
expiry=self.exp_input.date().toPyDate(),
type=self.type_input.currentText())
def update_names(self):
"""
Updates reagent names form field with examples from reagent type
"""
logger.debug(self.type_input.currentText())
self.name_input.clear()
# lookup = lookup_reagents(ctx=self.ctx, reagent_type=self.type_input.currentText())
lookup = Reagent.query(reagent_type=self.type_input.currentText())
self.name_input.addItems(list(set([item.name for item in lookup])))
class ReportDatePicker(QDialog):
"""
custom dialog to ask for report start/stop dates
"""
def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Select Report Date Range")
# make confirm/reject buttons
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
# widgets to ask for dates
self.start_date = QDateEdit(calendarPopup=True)
self.start_date.setObjectName("start_date")
self.start_date.setDate(QDate.currentDate())
self.end_date = QDateEdit(calendarPopup=True)
self.end_date.setObjectName("end_date")
self.end_date.setDate(QDate.currentDate())
self.layout = QVBoxLayout()
self.layout.addWidget(QLabel("Start Date"))
self.layout.addWidget(self.start_date)
self.layout.addWidget(QLabel("End Date"))
self.layout.addWidget(self.end_date)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
def parse_form(self):
return dict(start_date=self.start_date.date().toPyDate(), end_date = self.end_date.date().toPyDate())
# class KitAdder(QWidget):
# """
# dialog to get information to add kit
# """
# def __init__(self) -> None:
# super().__init__()
# # self.ctx = parent_ctx
# main_box = QVBoxLayout(self)
# scroll = QScrollArea(self)
# main_box.addWidget(scroll)
# scroll.setWidgetResizable(True)
# scrollContent = QWidget(scroll)
# self.grid = QGridLayout()
# # self.setLayout(self.grid)
# scrollContent.setLayout(self.grid)
# # insert submit button at top
# self.submit_btn = QPushButton("Submit")
# self.grid.addWidget(self.submit_btn,0,0,1,1)
# self.grid.addWidget(QLabel("Kit Name:"),2,0)
# # widget to get kit name
# kit_name = QLineEdit()
# kit_name.setObjectName("kit_name")
# self.grid.addWidget(kit_name,2,1)
# self.grid.addWidget(QLabel("Used For Submission Type:"),3,0)
# # widget to get uses of kit
# used_for = QComboBox()
# used_for.setObjectName("used_for")
# # Insert all existing sample types
# # used_for.addItems([item.name for item in lookup_submission_type(ctx=parent_ctx)])
# used_for.addItems([item.name for item in SubmissionType.query()])
# used_for.setEditable(True)
# self.grid.addWidget(used_for,3,1)
# # Get all fields in SubmissionTypeKitTypeAssociation
# self.columns = [item for item in SubmissionTypeKitTypeAssociation.__table__.columns if len(item.foreign_keys) == 0]
# for iii, column in enumerate(self.columns):
# idx = iii + 4
# # convert field name to human readable.
# field_name = column.name.replace("_", " ").title()
# self.grid.addWidget(QLabel(field_name),idx,0)
# match column.type:
# case FLOAT():
# add_widget = QDoubleSpinBox()
# add_widget.setMinimum(0)
# add_widget.setMaximum(9999)
# case INTEGER():
# add_widget = QSpinBox()
# add_widget.setMinimum(0)
# add_widget.setMaximum(9999)
# case _:
# add_widget = QLineEdit()
# add_widget.setObjectName(column.name)
# self.grid.addWidget(add_widget, idx,1)
# self.add_RT_btn = QPushButton("Add Reagent Type")
# self.grid.addWidget(self.add_RT_btn)
# self.add_RT_btn.clicked.connect(self.add_RT)
# self.submit_btn.clicked.connect(self.submit)
# scroll.setWidget(scrollContent)
# self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
# "qt_scrollarea_vcontainer", "submit_btn"
# ]
# def add_RT(self) -> None:
# """
# insert new reagent type row
# """
# # get bottommost row
# maxrow = self.grid.rowCount()
# reg_form = ReagentTypeForm()
# reg_form.setObjectName(f"ReagentForm_{maxrow}")
# # self.grid.addWidget(reg_form, maxrow + 1,0,1,2)
# self.grid.addWidget(reg_form, maxrow,0,1,4)
# def submit(self) -> None:
# """
# send kit to database
# """
# # get form info
# info, reagents = self.parse_form()
# # info, reagents = extract_form_info(self)
# info = {k:v for k,v in info.items() if k in [column.name for column in self.columns] + ['kit_name', 'used_for']}
# logger.debug(f"kit info: {pformat(info)}")
# logger.debug(f"kit reagents: {pformat(reagents)}")
# info['reagent_types'] = reagents
# logger.debug(pformat(info))
# # send to kit constructor
# kit = PydKit(name=info['kit_name'])
# for reagent in info['reagent_types']:
# uses = {
# info['used_for']:
# {'sheet':reagent['sheet'],
# 'name':reagent['name'],
# 'lot':reagent['lot'],
# 'expiry':reagent['expiry']
# }}
# kit.reagent_types.append(PydReagentType(name=reagent['rtname'], eol_ext=reagent['eol'], uses=uses))
# logger.debug(f"Output pyd object: {kit.__dict__}")
# # result = construct_kit_from_yaml(ctx=self.ctx, kit_dict=info)
# sqlobj, result = kit.toSQL(self.ctx)
# sqlobj.save()
# msg = AlertPop(message=result['message'], status=result['status'])
# msg.exec()
# self.__init__(self.ctx)
# def parse_form(self) -> Tuple[dict, list]:
# logger.debug(f"Hello from {self.__class__} parser!")
# info = {}
# reagents = []
# widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore and not isinstance(widget.parent(), ReagentTypeForm)]
# for widget in widgets:
# # logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
# match widget:
# case ReagentTypeForm():
# reagents.append(widget.parse_form())
# case QLineEdit():
# info[widget.objectName()] = widget.text()
# case QComboBox():
# info[widget.objectName()] = widget.currentText()
# case QDateEdit():
# info[widget.objectName()] = widget.date().toPyDate()
# return info, reagents
# class ReagentTypeForm(QWidget):
# """
# custom widget to add information about a new reagenttype
# """
# def __init__(self) -> None:
# super().__init__()
# grid = QGridLayout()
# self.setLayout(grid)
# grid.addWidget(QLabel("Reagent Type Name"),0,0)
# # Widget to get reagent info
# self.reagent_getter = QComboBox()
# self.reagent_getter.setObjectName("rtname")
# # lookup all reagent type names from db
# # lookup = lookup_reagent_types(ctx=ctx)
# lookup = ReagentType.query()
# logger.debug(f"Looked up ReagentType names: {lookup}")
# self.reagent_getter.addItems([item.__str__() for item in lookup])
# self.reagent_getter.setEditable(True)
# grid.addWidget(self.reagent_getter,0,1)
# grid.addWidget(QLabel("Extension of Life (months):"),0,2)
# # widget to get extension of life
# self.eol = QSpinBox()
# self.eol.setObjectName('eol')
# self.eol.setMinimum(0)
# grid.addWidget(self.eol, 0,3)
# grid.addWidget(QLabel("Excel Location Sheet Name:"),1,0)
# self.location_sheet_name = QLineEdit()
# self.location_sheet_name.setObjectName("sheet")
# self.location_sheet_name.setText("e.g. 'Reagent Info'")
# grid.addWidget(self.location_sheet_name, 1,1)
# for iii, item in enumerate(["Name", "Lot", "Expiry"]):
# idx = iii + 2
# grid.addWidget(QLabel(f"{item} Row:"), idx, 0)
# row = QSpinBox()
# row.setFixedWidth(50)
# row.setObjectName(f'{item.lower()}_row')
# row.setMinimum(0)
# grid.addWidget(row, idx, 1)
# grid.addWidget(QLabel(f"{item} Column:"), idx, 2)
# col = QSpinBox()
# col.setFixedWidth(50)
# col.setObjectName(f'{item.lower()}_column')
# col.setMinimum(0)
# grid.addWidget(col, idx, 3)
# self.setFixedHeight(175)
# max_row = grid.rowCount()
# self.r_button = QPushButton("Remove")
# self.r_button.clicked.connect(self.remove)
# grid.addWidget(self.r_button,max_row,0,1,1)
# self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
# "qt_scrollarea_vcontainer", "submit_btn", "eol", "sheet", "rtname"
# ]
# def remove(self):
# self.setParent(None)
# self.destroy()
# def parse_form(self) -> dict:
# logger.debug(f"Hello from {self.__class__} parser!")
# info = {}
# info['eol'] = self.eol.value()
# info['sheet'] = self.location_sheet_name.text()
# info['rtname'] = self.reagent_getter.currentText()
# widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore]
# for widget in widgets:
# logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
# match widget:
# case QLineEdit():
# info[widget.objectName()] = widget.text()
# case QComboBox():
# info[widget.objectName()] = widget.currentText()
# case QDateEdit():
# info[widget.objectName()] = widget.date().toPyDate()
# case QSpinBox() | QDoubleSpinBox():
# if "_" in widget.objectName():
# key, sub_key = widget.objectName().split("_")
# if key not in info.keys():
# info[key] = {}
# logger.debug(f"Adding key {key}, {sub_key} and value {widget.value()} to {info}")
# info[key][sub_key] = widget.value()
# return info
# class ControlsDatePicker(QWidget):
# """
# custom widget to pick start and end dates for controls graphs
# """
# def __init__(self) -> None:
# super().__init__()
# self.start_date = QDateEdit(calendarPopup=True)
# # start date is two months prior to end date by default
# twomonthsago = QDate.currentDate().addDays(-60)
# self.start_date.setDate(twomonthsago)
# self.end_date = QDateEdit(calendarPopup=True)
# self.end_date.setDate(QDate.currentDate())
# self.layout = QHBoxLayout()
# self.layout.addWidget(QLabel("Start Date"))
# self.layout.addWidget(self.start_date)
# self.layout.addWidget(QLabel("End Date"))
# self.layout.addWidget(self.end_date)
# self.setLayout(self.layout)
# self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
# def sizeHint(self) -> QSize:
# return QSize(80,20)
class FirstStrandSalvage(QDialog):
def __init__(self, ctx:Settings, submitter_id:str, rsl_plate_num:str|None=None) -> None:
super().__init__()
if rsl_plate_num == None:
rsl_plate_num = ""
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)
self.submitter_id_input = QLineEdit()
self.submitter_id_input.setText(submitter_id)
self.rsl_plate_num = QLineEdit()
self.rsl_plate_num.setText(rsl_plate_num)
self.row_letter = QComboBox()
self.row_letter.addItems(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'])
self.row_letter.setEditable(False)
self.column_number = QComboBox()
self.column_number.addItems(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'])
self.column_number.setEditable(False)
self.layout = QFormLayout()
self.layout.addRow(self.tr("&Sample Number:"), self.submitter_id_input)
self.layout.addRow(self.tr("&Plate Number:"), self.rsl_plate_num)
self.layout.addRow(self.tr("&Source Row:"), self.row_letter)
self.layout.addRow(self.tr("&Source Column:"), self.column_number)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
def parse_form(self):
return dict(plate=self.rsl_plate_num.text(), submitter_id=self.submitter_id_input.text(), well=f"{self.row_letter.currentText()}{self.column_number.currentText()}")
class FirstStrandPlateList(QDialog):
def __init__(self, ctx:Settings) -> None:
super().__init__()
self.setWindowTitle("First Strand Plates")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
# ww = [item.rsl_plate_num for item in lookup_submissions(ctx=ctx, submission_type="Wastewater")]
ww = [item.rsl_plate_num for item in BasicSubmission.query(submission_type="Wastewater")]
self.plate1 = QComboBox()
self.plate2 = QComboBox()
self.plate3 = QComboBox()
self.layout = QFormLayout()
for ii, plate in enumerate([self.plate1, self.plate2, self.plate3]):
plate.addItems(ww)
self.layout.addRow(self.tr(f"&Plate {ii+1}:"), plate)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
def parse_form(self):
output = []
for plate in [self.plate1, self.plate2, self.plate3]:
output.append(plate.currentText())
return output
# class ReagentFormWidget(QWidget):
# def __init__(self, parent:QWidget, reagent:PydReagent, extraction_kit:str):
# super().__init__(parent)
# # self.setParent(parent)
# self.reagent = reagent
# self.extraction_kit = extraction_kit
# # self.ctx = reagent.ctx
# layout = QVBoxLayout()
# self.label = self.ReagentParsedLabel(reagent=reagent)
# layout.addWidget(self.label)
# self.lot = self.ReagentLot(reagent=reagent, extraction_kit=extraction_kit)
# layout.addWidget(self.lot)
# # Remove spacing between reagents
# layout.setContentsMargins(0,0,0,0)
# self.setLayout(layout)
# self.setObjectName(reagent.name)
# self.missing = reagent.missing
# # If changed set self.missing to True and update self.label
# self.lot.currentTextChanged.connect(self.updated)
# def parse_form(self) -> Tuple[PydReagent, dict]:
# lot = self.lot.currentText()
# # wanted_reagent = lookup_reagents(ctx=self.ctx, lot_number=lot, reagent_type=self.reagent.type)
# wanted_reagent = Reagent.query(lot_number=lot, reagent_type=self.reagent.type)
# # if reagent doesn't exist in database, off to add it (uses App.add_reagent)
# if wanted_reagent == None:
# dlg = QuestionAsker(title=f"Add {lot}?", message=f"Couldn't find reagent type {self.reagent.type}: {lot} in the database.\n\nWould you like to add it?")
# if dlg.exec():
# wanted_reagent = self.parent().parent().parent().parent().parent().parent().parent().parent().parent.add_reagent(reagent_lot=lot, reagent_type=self.reagent.type, expiry=self.reagent.expiry, name=self.reagent.name)
# return wanted_reagent, None
# else:
# # In this case we will have an empty reagent and the submission will fail kit integrity check
# logger.debug("Will not add reagent.")
# return None, Result(msg="Failed integrity check", status="Critical")
# else:
# # Since this now gets passed in directly from the parser -> pyd -> form and the parser gets the name
# # from the db, it should no longer be necessary to query the db with reagent/kit, but with rt name directly.
# # rt = lookup_reagent_types(ctx=self.ctx, name=self.reagent.type)
# # rt = lookup_reagent_types(ctx=self.ctx, kit_type=self.extraction_kit, reagent=wanted_reagent)
# rt = ReagentType.query(name=self.reagent.type)
# if rt == None:
# # rt = lookup_reagent_types(ctx=self.ctx, kit_type=self.extraction_kit, reagent=wanted_reagent)
# rt = ReagentType.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
# return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, type=rt.name, expiry=wanted_reagent.expiry, parsed=not self.missing), None
# def updated(self):
# self.missing = True
# self.label.updated(self.reagent.type)
# class ReagentParsedLabel(QLabel):
# def __init__(self, reagent:PydReagent):
# super().__init__()
# try:
# check = not reagent.missing
# except:
# check = False
# self.setObjectName(f"{reagent.type}_label")
# if check:
# self.setText(f"Parsed {reagent.type}")
# else:
# self.setText(f"MISSING {reagent.type}")
# def updated(self, reagent_type:str):
# self.setText(f"UPDATED {reagent_type}")
# class ReagentLot(QComboBox):
# def __init__(self, reagent, extraction_kit:str) -> None:
# super().__init__()
# # self.ctx = reagent.ctx
# self.setEditable(True)
# # if reagent.parsed:
# # pass
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
# # below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
# # lookup = lookup_reagents(ctx=self.ctx, reagent_type=reagent.type)
# lookup = Reagent.query(reagent_type=reagent.type)
# relevant_reagents = [item.__str__() for item in lookup]
# output_reg = []
# for rel_reagent in relevant_reagents:
# # extract strings from any sets.
# if isinstance(rel_reagent, set):
# for thing in rel_reagent:
# output_reg.append(thing)
# elif isinstance(rel_reagent, str):
# output_reg.append(rel_reagent)
# relevant_reagents = output_reg
# # if reagent in sheet is not found insert it into the front of relevant reagents so it shows
# logger.debug(f"Relevant reagents for {reagent.lot}: {relevant_reagents}")
# if str(reagent.lot) not in relevant_reagents:
# if check_not_nan(reagent.lot):
# relevant_reagents.insert(0, str(reagent.lot))
# else:
# # TODO: look up the last used reagent of this type in the database
# # looked_up_rt = lookup_reagenttype_kittype_association(ctx=self.ctx, reagent_type=reagent.type, kit_type=extraction_kit)
# looked_up_rt = KitTypeReagentTypeAssociation.query(reagent_type=reagent.type, kit_type=extraction_kit)
# try:
# # looked_up_reg = lookup_reagents(ctx=self.ctx, lot_number=looked_up_rt.last_used)
# looked_up_reg = Reagent.query(lot_number=looked_up_rt.last_used)
# except AttributeError:
# looked_up_reg = None
# logger.debug(f"Because there was no reagent listed for {reagent.lot}, we will insert the last lot used: {looked_up_reg}")
# if looked_up_reg != None:
# relevant_reagents.remove(str(looked_up_reg.lot))
# relevant_reagents.insert(0, str(looked_up_reg.lot))
# else:
# if len(relevant_reagents) > 1:
# logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. Moving to front of list.")
# idx = relevant_reagents.index(str(reagent.lot))
# logger.debug(f"The index we got for {reagent.lot} in {relevant_reagents} was {idx}")
# moved_reag = relevant_reagents.pop(idx)
# relevant_reagents.insert(0, moved_reag)
# else:
# logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. But no need to move due to short list.")
# logger.debug(f"New relevant reagents: {relevant_reagents}")
# self.setObjectName(f"lot_{reagent.type}")
# self.addItems(relevant_reagents)
# class SubmissionFormWidget(QWidget):
# def __init__(self, parent: QWidget, **kwargs) -> None:
# super().__init__(parent)
# # self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
# # "qt_scrollarea_vcontainer", "submit_btn"
# # ]
# self.ignore = ['filepath', 'samples', 'reagents', 'csv', 'ctx']
# layout = QVBoxLayout()
# for k, v in kwargs.items():
# if k not in self.ignore:
# add_widget = self.create_widget(key=k, value=v, submission_type=kwargs['submission_type'])
# if add_widget != None:
# layout.addWidget(add_widget)
# else:
# setattr(self, k, v)
# self.setLayout(layout)
# def create_widget(self, key:str, value:dict, submission_type:str|None=None):
# if key not in self.ignore:
# return self.InfoItem(self, key=key, value=value, submission_type=submission_type)
# return None
# def clear_form(self):
# for item in self.findChildren(QWidget):
# item.setParent(None)
# def find_widgets(self, object_name:str|None=None) -> List[QWidget]:
# query = self.findChildren(QWidget)
# if object_name != None:
# query = [widget for widget in query if widget.objectName()==object_name]
# return query
# def parse_form(self) -> PydSubmission:
# logger.debug(f"Hello from form parser!")
# info = {}
# reagents = []
# if hasattr(self, 'csv'):
# info['csv'] = self.csv
# for widget in self.findChildren(QWidget):
# # logger.debug(f"Parsed widget of type {type(widget)}")
# match widget:
# case ReagentFormWidget():
# reagent, _ = widget.parse_form()
# if reagent != None:
# reagents.append(reagent)
# case self.InfoItem():
# field, value = widget.parse_form()
# if field != None:
# info[field] = value
# logger.debug(f"Info: {pformat(info)}")
# logger.debug(f"Reagents: {pformat(reagents)}")
# # app = self.parent().parent().parent().parent().parent().parent().parent().parent
# submission = PydSubmission(filepath=self.filepath, reagents=reagents, samples=self.samples, **info)
# return submission
# class InfoItem(QWidget):
# def __init__(self, parent: QWidget, key:str, value:dict, submission_type:str|None=None) -> None:
# super().__init__(parent)
# layout = QVBoxLayout()
# self.label = self.ParsedQLabel(key=key, value=value)
# self.input: QWidget = self.set_widget(parent=self, key=key, value=value, submission_type=submission_type['value'])
# self.setObjectName(key)
# try:
# self.missing:bool = value['missing']
# except (TypeError, KeyError):
# self.missing:bool = True
# if self.input != None:
# layout.addWidget(self.label)
# layout.addWidget(self.input)
# layout.setContentsMargins(0,0,0,0)
# self.setLayout(layout)
# match self.input:
# case QComboBox():
# self.input.currentTextChanged.connect(self.update_missing)
# case QDateEdit():
# self.input.dateChanged.connect(self.update_missing)
# case QLineEdit():
# self.input.textChanged.connect(self.update_missing)
# def parse_form(self):
# match self.input:
# case QLineEdit():
# value = self.input.text()
# case QComboBox():
# value = self.input.currentText()
# case QDateEdit():
# value = self.input.date().toPyDate()
# case _:
# return None, None
# return self.input.objectName(), dict(value=value, missing=self.missing)
# def set_widget(self, parent: QWidget, key:str, value:dict, submission_type:str|None=None) -> QWidget:
# try:
# value = value['value']
# except (TypeError, KeyError):
# pass
# obj = parent.parent().parent()
# logger.debug(f"Creating widget for: {key}")
# match key:
# case 'submitting_lab':
# add_widget = QComboBox()
# # lookup organizations suitable for submitting_lab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
# labs = [item.__str__() for item in Organization.query()]
# # try to set closest match to top of list
# try:
# labs = difflib.get_close_matches(value, labs, len(labs), 0)
# except (TypeError, ValueError):
# pass
# # set combobox values to lookedup values
# add_widget.addItems(labs)
# case 'extraction_kit':
# # if extraction kit not available, all other values fail
# if not check_not_nan(value):
# msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!", status="warning")
# msg.exec()
# # create combobox to hold looked up kits
# add_widget = QComboBox()
# # lookup existing kits by 'submission_type' decided on by sheetparser
# logger.debug(f"Looking up kits used for {submission_type}")
# uses = [item.__str__() for item in KitType.query(used_for=submission_type)]
# obj.uses = uses
# logger.debug(f"Kits received for {submission_type}: {uses}")
# if check_not_nan(value):
# logger.debug(f"The extraction kit in parser was: {value}")
# uses.insert(0, uses.pop(uses.index(value)))
# obj.ext_kit = value
# else:
# logger.error(f"Couldn't find {obj.prsr.sub['extraction_kit']}")
# obj.ext_kit = uses[0]
# add_widget.addItems(uses)
# # Run reagent scraper whenever extraction kit is changed.
# # add_widget.currentTextChanged.connect(obj.scrape_reagents)
# case 'submitted_date':
# # uses base calendar
# add_widget = QDateEdit(calendarPopup=True)
# # sets submitted date based on date found in excel sheet
# try:
# add_widget.setDate(value)
# # if not found, use today
# except:
# add_widget.setDate(date.today())
# case 'submission_category':
# add_widget = QComboBox()
# cats = ['Diagnostic', "Surveillance", "Research"]
# # cats += [item.name for item in lookup_submission_type(ctx=obj.ctx)]
# cats += [item.name for item in SubmissionType.query()]
# try:
# cats.insert(0, cats.pop(cats.index(value)))
# except ValueError:
# cats.insert(0, cats.pop(cats.index(submission_type)))
# add_widget.addItems(cats)
# case _:
# # anything else gets added in as a line edit
# add_widget = QLineEdit()
# logger.debug(f"Setting widget text to {str(value).replace('_', ' ')}")
# add_widget.setText(str(value).replace("_", " "))
# if add_widget != None:
# add_widget.setObjectName(key)
# add_widget.setParent(parent)
# return add_widget
# def update_missing(self):
# self.missing = True
# self.label.updated(self.objectName())
# class ParsedQLabel(QLabel):
# def __init__(self, key:str, value:dict, title:bool=True, label_name:str|None=None):
# super().__init__()
# try:
# check = not value['missing']
# except:
# check = True
# if label_name != None:
# self.setObjectName(label_name)
# else:
# self.setObjectName(f"{key}_label")
# if title:
# output = key.replace('_', ' ').title()
# else:
# output = key.replace('_', ' ')
# if check:
# self.setText(f"Parsed {output}")
# else:
# self.setText(f"MISSING {output}")
# def updated(self, key:str, title:bool=True):
# if title:
# output = key.replace('_', ' ').title()
# else:
# output = key.replace('_', ' ')
# self.setText(f"UPDATED {output}")