Pre-frontend refactor.
This commit is contained in:
@@ -6,23 +6,27 @@ from PyQt6.QtWidgets import (
|
||||
QMainWindow, QToolBar,
|
||||
QTabWidget, QWidget, QVBoxLayout,
|
||||
QComboBox, QHBoxLayout,
|
||||
QScrollArea, QLineEdit, QDateEdit
|
||||
QScrollArea
|
||||
)
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from pathlib import Path
|
||||
from backend.db.functions import (
|
||||
lookup_control_types, lookup_modes
|
||||
)
|
||||
from backend.db.models import ControlType, Control
|
||||
from backend.validators import PydSubmission, PydReagent
|
||||
from tools import check_if_app, Settings
|
||||
from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker, ReagentFormWidget
|
||||
from .functions import (
|
||||
import_submission_function, kit_reload_function, kit_integrity_completion_function,
|
||||
submit_new_sample_function, generate_report_function, add_kit_function, add_org_function,
|
||||
controls_getter_function, chart_maker_function, link_controls_function, link_extractions_function,
|
||||
link_pcr_function, autofill_excel, scrape_reagents, export_csv_function, import_pcr_results_function
|
||||
)
|
||||
from tools import check_if_app, Settings, Report
|
||||
from frontend.custom_widgets import SubmissionsSheet, AlertPop, AddReagentForm, KitAdder, ControlsDatePicker
|
||||
import logging
|
||||
from datetime import date
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
logger = logging.getLogger(f'submissions.{__name__}')
|
||||
logger.info("Hello, I am a logger")
|
||||
@@ -34,6 +38,7 @@ class App(QMainWindow):
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
self.last_dir = ctx.directory_path
|
||||
self.report = Report()
|
||||
# indicate version and connected database in title bar
|
||||
try:
|
||||
self.title = f"Submissions App (v{ctx.package.__version__}) - {ctx.database_path}"
|
||||
@@ -58,7 +63,6 @@ class App(QMainWindow):
|
||||
self.show()
|
||||
self.statusBar().showMessage('Ready', 5000)
|
||||
|
||||
|
||||
def _createMenuBar(self):
|
||||
"""
|
||||
adds items to menu bar
|
||||
@@ -67,7 +71,7 @@ class App(QMainWindow):
|
||||
menuBar = self.menuBar()
|
||||
fileMenu = menuBar.addMenu("&File")
|
||||
# Creating menus using a title
|
||||
methodsMenu = menuBar.addMenu("&Methods")
|
||||
# methodsMenu = menuBar.addMenu("&Methods")
|
||||
reportMenu = menuBar.addMenu("&Reports")
|
||||
maintenanceMenu = menuBar.addMenu("&Monthly")
|
||||
helpMenu = menuBar.addMenu("&Help")
|
||||
@@ -75,7 +79,7 @@ class App(QMainWindow):
|
||||
helpMenu.addAction(self.docsAction)
|
||||
fileMenu.addAction(self.importAction)
|
||||
fileMenu.addAction(self.importPCRAction)
|
||||
methodsMenu.addAction(self.constructFS)
|
||||
# methodsMenu.addAction(self.constructFS)
|
||||
reportMenu.addAction(self.generateReportAction)
|
||||
maintenanceMenu.addAction(self.joinExtractionAction)
|
||||
maintenanceMenu.addAction(self.joinPCRAction)
|
||||
@@ -106,8 +110,7 @@ class App(QMainWindow):
|
||||
self.joinPCRAction = QAction("Link PCR Logs")
|
||||
self.helpAction = QAction("&About", self)
|
||||
self.docsAction = QAction("&Docs", self)
|
||||
self.constructFS = QAction("Make First Strand", self)
|
||||
|
||||
# self.constructFS = QAction("Make First Strand", self)
|
||||
|
||||
def _connectActions(self):
|
||||
"""
|
||||
@@ -128,7 +131,7 @@ class App(QMainWindow):
|
||||
self.joinPCRAction.triggered.connect(self.linkPCR)
|
||||
self.helpAction.triggered.connect(self.showAbout)
|
||||
self.docsAction.triggered.connect(self.openDocs)
|
||||
self.constructFS.triggered.connect(self.construct_first_strand)
|
||||
# self.constructFS.triggered.connect(self.construct_first_strand)
|
||||
self.table_widget.formwidget.import_drag.connect(self.importSubmission)
|
||||
|
||||
def showAbout(self):
|
||||
@@ -150,7 +153,7 @@ class App(QMainWindow):
|
||||
logger.debug(f"Attempting to open {url}")
|
||||
webbrowser.get('windows-default').open(f"file://{url.__str__()}")
|
||||
|
||||
def result_reporter(self, result:dict|None=None):
|
||||
def result_reporter(self):
|
||||
# def result_reporter(self, result:TypedDict[]|None=None):
|
||||
"""
|
||||
Report any anomolous results - if any - to the user
|
||||
@@ -158,31 +161,41 @@ class App(QMainWindow):
|
||||
Args:
|
||||
result (dict | None, optional): The result from a function. Defaults to None.
|
||||
"""
|
||||
logger.info(f"We got the result: {result}")
|
||||
if result != None:
|
||||
msg = AlertPop(message=result['message'], status=result['status'])
|
||||
msg.exec()
|
||||
# logger.info(f"We got the result: {result}")
|
||||
# if result != None:
|
||||
# msg = AlertPop(message=result['message'], status=result['status'])
|
||||
# msg.exec()
|
||||
logger.debug(f"Running results reporter for: {self.report.results}")
|
||||
if len(self.report.results) > 0:
|
||||
logger.debug(f"We've got some results!")
|
||||
for result in self.report.results:
|
||||
logger.debug(f"Showing result: {result}")
|
||||
if result != None:
|
||||
alert = result.report()
|
||||
if alert.exec():
|
||||
pass
|
||||
self.report = Report()
|
||||
else:
|
||||
self.statusBar().showMessage("Action completed sucessfully.", 5000)
|
||||
|
||||
|
||||
def importSubmission(self, fname:Path|None=None):
|
||||
"""
|
||||
import submission from excel sheet into form
|
||||
"""
|
||||
from .main_window_functions import import_submission_function
|
||||
# from .main_window_functions import import_submission_function
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
self, result = import_submission_function(self, fname)
|
||||
logger.debug(f"Import result: {result}")
|
||||
self.result_reporter(result)
|
||||
self = import_submission_function(self, fname)
|
||||
logger.debug(f"Result from result reporter: {self.report.results}")
|
||||
self.result_reporter()
|
||||
|
||||
def kit_reload(self):
|
||||
"""
|
||||
Removes all reagents from form before running kit integrity completion.
|
||||
"""
|
||||
from .main_window_functions import kit_reload_function
|
||||
self, result = kit_reload_function(self)
|
||||
self.result_reporter(result)
|
||||
# from .main_window_functions import kit_reload_function
|
||||
self = kit_reload_function(self)
|
||||
self.result_reporter()
|
||||
|
||||
def kit_integrity_completion(self):
|
||||
"""
|
||||
@@ -190,15 +203,15 @@ class App(QMainWindow):
|
||||
NOTE: this will not change self.reagents which should be fine
|
||||
since it's only used when looking up
|
||||
"""
|
||||
from .main_window_functions import kit_integrity_completion_function
|
||||
self, result = kit_integrity_completion_function(self)
|
||||
self.result_reporter(result)
|
||||
# from .main_window_functions import kit_integrity_completion_function
|
||||
self = kit_integrity_completion_function(self)
|
||||
self.result_reporter()
|
||||
|
||||
def submit_new_sample(self):
|
||||
"""
|
||||
Attempt to add sample to database when 'submit' button clicked
|
||||
"""
|
||||
from .main_window_functions import submit_new_sample_function
|
||||
# from .main_window_functions import submit_new_sample_function
|
||||
self, result = submit_new_sample_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -237,7 +250,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
Action to create a summary of sheet data per client
|
||||
"""
|
||||
from .main_window_functions import generate_report_function
|
||||
# from .main_window_functions import generate_report_function
|
||||
self, result = generate_report_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -245,7 +258,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
Constructs new kit from yaml and adds to DB.
|
||||
"""
|
||||
from .main_window_functions import add_kit_function
|
||||
# from .main_window_functions import add_kit_function
|
||||
self, result = add_kit_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -253,7 +266,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
Constructs new kit from yaml and adds to DB.
|
||||
"""
|
||||
from .main_window_functions import add_org_function
|
||||
# from .main_window_functions import add_org_function
|
||||
self, result = add_org_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -261,24 +274,24 @@ class App(QMainWindow):
|
||||
"""
|
||||
Lookup controls from database and send to chartmaker
|
||||
"""
|
||||
from .main_window_functions import controls_getter_function
|
||||
self, result = controls_getter_function(self)
|
||||
self.result_reporter(result)
|
||||
# from .main_window_functions import controls_getter_function
|
||||
self = controls_getter_function(self)
|
||||
self.result_reporter()
|
||||
|
||||
def _chart_maker(self):
|
||||
"""
|
||||
Creates plotly charts for webview
|
||||
"""
|
||||
from .main_window_functions import chart_maker_function
|
||||
self, result = chart_maker_function(self)
|
||||
self.result_reporter(result)
|
||||
# from .main_window_functions import chart_maker_function
|
||||
self = chart_maker_function(self)
|
||||
self.result_reporter()
|
||||
|
||||
def linkControls(self):
|
||||
"""
|
||||
Adds controls pulled from irida to relevant submissions
|
||||
NOTE: Depreciated due to improvements in controls scraper.
|
||||
"""
|
||||
from .main_window_functions import link_controls_function
|
||||
# from .main_window_functions import link_controls_function
|
||||
self, result = link_controls_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -286,7 +299,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
Links extraction logs from .csv files to relevant submissions.
|
||||
"""
|
||||
from .main_window_functions import link_extractions_function
|
||||
# from .main_window_functions import link_extractions_function
|
||||
self, result = link_extractions_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -294,7 +307,7 @@ class App(QMainWindow):
|
||||
"""
|
||||
Links PCR logs from .csv files to relevant submissions.
|
||||
"""
|
||||
from .main_window_functions import link_pcr_function
|
||||
# from .main_window_functions import link_pcr_function
|
||||
self, result = link_pcr_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
@@ -302,25 +315,29 @@ class App(QMainWindow):
|
||||
"""
|
||||
Imports results exported from Design and Analysis .eds files
|
||||
"""
|
||||
from .main_window_functions import import_pcr_results_function
|
||||
# from .main_window_functions import import_pcr_results_function
|
||||
self, result = import_pcr_results_function(self)
|
||||
self.result_reporter(result)
|
||||
|
||||
def construct_first_strand(self):
|
||||
"""
|
||||
Converts first strand excel sheet to Biomek CSV
|
||||
"""
|
||||
from .main_window_functions import construct_first_strand_function
|
||||
self, result = construct_first_strand_function(self)
|
||||
self.result_reporter(result)
|
||||
# def construct_first_strand(self):
|
||||
# """
|
||||
# Converts first strand excel sheet to Biomek CSV
|
||||
# """
|
||||
# from .main_window_functions import construct_first_strand_function
|
||||
# self, result = construct_first_strand_function(self)
|
||||
# self.result_reporter(result)
|
||||
|
||||
def scrape_reagents(self, *args, **kwargs):
|
||||
from .main_window_functions import scrape_reagents
|
||||
# from .main_window_functions import scrape_reagents
|
||||
logger.debug(f"Args: {args}")
|
||||
logger.debug(F"kwargs: {kwargs}")
|
||||
self, result = scrape_reagents(self, args[0])
|
||||
self = scrape_reagents(self, args[0])
|
||||
self.kit_integrity_completion()
|
||||
self.result_reporter(result)
|
||||
self.result_reporter()
|
||||
|
||||
def export_csv(self, fname:Path|None=None):
|
||||
from .main_window_functions import export_csv_function
|
||||
export_csv_function(self, fname)
|
||||
|
||||
class AddSubForm(QWidget):
|
||||
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
'''
|
||||
functions used by all windows in the application's frontend
|
||||
'''
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow, QWidget, QFileDialog,
|
||||
QLineEdit, QComboBox, QDateEdit, QSpinBox,
|
||||
QDoubleSpinBox
|
||||
)
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to read from
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
file_extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).resolve().__str__()
|
||||
home_dir = obj.last_dir.resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().resolve().__str__()
|
||||
fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
obj.last_file = fname
|
||||
return fname
|
||||
|
||||
def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to write to
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
default_name (str): default base file name
|
||||
extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__()
|
||||
home_dir = obj.last_dir.joinpath(default_name).resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().joinpath(default_name).resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0])
|
||||
# fname = Path(QFileDialog.getSaveFileName(obj, "Save File", filter = f"{extension}(*.{extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
|
||||
def extract_form_info(object) -> dict:
|
||||
"""
|
||||
retrieves object names and values from form
|
||||
DEPRECIATED. Replaced by individual form parser methods.
|
||||
|
||||
Args:
|
||||
object (_type_): the form widget
|
||||
|
||||
Returns:
|
||||
dict: dictionary of objectName:text items
|
||||
"""
|
||||
|
||||
from frontend.custom_widgets import ReagentTypeForm
|
||||
dicto = {}
|
||||
reagents = []
|
||||
logger.debug(f"Object type: {type(object)}")
|
||||
# grab all widgets in form
|
||||
try:
|
||||
all_children = object.layout.parentWidget().findChildren(QWidget)
|
||||
except AttributeError:
|
||||
all_children = object.layout().parentWidget().findChildren(QWidget)
|
||||
for item in all_children:
|
||||
logger.debug(f"Looking at: {item.objectName()}: {type(item)}")
|
||||
match item:
|
||||
case QLineEdit():
|
||||
dicto[item.objectName()] = item.text()
|
||||
case QComboBox():
|
||||
dicto[item.objectName()] = item.currentText()
|
||||
case QDateEdit():
|
||||
dicto[item.objectName()] = item.date().toPyDate()
|
||||
case QSpinBox() | QDoubleSpinBox():
|
||||
dicto[item.objectName()] = item.value()
|
||||
case ReagentTypeForm():
|
||||
reagent = extract_form_info(item)
|
||||
logger.debug(f"Reagent found: {reagent}")
|
||||
if isinstance(reagent, tuple):
|
||||
reagent = reagent[0]
|
||||
# reagents[reagent["name"].strip()] = {'eol':int(reagent['eol'])}
|
||||
reagents.append({k:v for k,v in reagent.items() if k not in ['', 'qt_spinbox_lineedit']})
|
||||
# value for ad hoc check above
|
||||
if isinstance(dicto, tuple):
|
||||
logger.warning(f"Got tuple for dicto for some reason.")
|
||||
dicto = dicto[0]
|
||||
if isinstance(reagents, tuple):
|
||||
logger.warning(f"Got tuple for reagents for some reason.")
|
||||
reagents = reagents[0]
|
||||
if reagents != {}:
|
||||
return dicto, reagents
|
||||
return dicto
|
||||
@@ -13,8 +13,6 @@ from PyQt6.QtWidgets import (
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QDate, QSize, pyqtSignal
|
||||
from tools import check_not_nan, jinja_template_loading, Settings
|
||||
from backend.db.functions import (lookup_reagent_types, lookup_reagents, lookup_submission_type, lookup_reagenttype_kittype_association, \
|
||||
lookup_submissions, lookup_organizations, lookup_kit_types)
|
||||
from backend.db.models import *
|
||||
from sqlalchemy import FLOAT, INTEGER
|
||||
import logging
|
||||
@@ -200,7 +198,6 @@ class KitAdder(QWidget):
|
||||
"qt_scrollarea_vcontainer", "submit_btn"
|
||||
]
|
||||
|
||||
|
||||
def add_RT(self) -> None:
|
||||
"""
|
||||
insert new reagent type row
|
||||
@@ -439,7 +436,7 @@ class ReagentFormWidget(QWidget):
|
||||
# self.setParent(parent)
|
||||
self.reagent = reagent
|
||||
self.extraction_kit = extraction_kit
|
||||
self.ctx = reagent.ctx
|
||||
# self.ctx = reagent.ctx
|
||||
layout = QVBoxLayout()
|
||||
self.label = self.ReagentParsedLabel(reagent=reagent)
|
||||
layout.addWidget(self.label)
|
||||
@@ -476,7 +473,7 @@ class ReagentFormWidget(QWidget):
|
||||
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(ctx=self.ctx, name=wanted_reagent.name, lot=wanted_reagent.lot, type=rt.name, expiry=wanted_reagent.expiry, parsed=not self.missing), None
|
||||
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
|
||||
@@ -504,7 +501,7 @@ class ReagentFormWidget(QWidget):
|
||||
|
||||
def __init__(self, reagent, extraction_kit:str) -> None:
|
||||
super().__init__()
|
||||
self.ctx = reagent.ctx
|
||||
# self.ctx = reagent.ctx
|
||||
self.setEditable(True)
|
||||
# if reagent.parsed:
|
||||
# pass
|
||||
@@ -569,6 +566,7 @@ class SubmissionFormWidget(QWidget):
|
||||
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):
|
||||
|
||||
@@ -7,7 +7,6 @@ from PyQt6.QtWidgets import (
|
||||
)
|
||||
from tools import jinja_template_loading
|
||||
import logging
|
||||
from backend.db.functions import lookup_kit_types, lookup_submission_type
|
||||
from backend.db.models import KitType, SubmissionType
|
||||
from typing import Literal
|
||||
|
||||
@@ -38,19 +37,19 @@ class AlertPop(QMessageBox):
|
||||
"""
|
||||
Dialog to show an alert.
|
||||
"""
|
||||
def __init__(self, message:str, status:Literal['information', 'question', 'warning', 'critical']) -> QMessageBox:
|
||||
def __init__(self, message:str, status:Literal['Information', 'Question', 'Warning', 'Critical'], owner:str|None=None) -> QMessageBox:
|
||||
super().__init__()
|
||||
# select icon by string
|
||||
icon = getattr(QMessageBox.Icon, status.title())
|
||||
icon = getattr(QMessageBox.Icon, status)
|
||||
self.setIcon(icon)
|
||||
self.setInformativeText(message)
|
||||
self.setWindowTitle(status.title())
|
||||
self.setWindowTitle(f"{owner} - {status.title()}")
|
||||
|
||||
class KitSelector(QDialog):
|
||||
"""
|
||||
dialog to ask yes/no questions
|
||||
"""
|
||||
def __init__(self, ctx:dict, title:str, message:str) -> QDialog:
|
||||
def __init__(self, title:str, message:str) -> QDialog:
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
@@ -78,7 +77,7 @@ class SubmissionTypeSelector(QDialog):
|
||||
"""
|
||||
dialog to ask yes/no questions
|
||||
"""
|
||||
def __init__(self, ctx:dict, title:str, message:str) -> QDialog:
|
||||
def __init__(self, title:str, message:str) -> QDialog:
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
|
||||
105
src/submissions/frontend/functions/__init__.py
Normal file
105
src/submissions/frontend/functions/__init__.py
Normal file
@@ -0,0 +1,105 @@
|
||||
'''
|
||||
functions used by all windows in the application's frontend
|
||||
'''
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from PyQt6.QtWidgets import QMainWindow, QFileDialog
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to read from
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
file_extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).resolve().__str__()
|
||||
home_dir = obj.last_dir.resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().resolve().__str__()
|
||||
fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
obj.last_file = fname
|
||||
return fname
|
||||
|
||||
def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to write to
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
default_name (str): default base file name
|
||||
extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__()
|
||||
home_dir = obj.last_dir.joinpath(default_name).resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().joinpath(default_name).resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0])
|
||||
# fname = Path(QFileDialog.getSaveFileName(obj, "Save File", filter = f"{extension}(*.{extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
|
||||
# def extract_form_info(object) -> dict:
|
||||
# """
|
||||
# retrieves object names and values from form
|
||||
# DEPRECIATED. Replaced by individual form parser methods.
|
||||
|
||||
# Args:
|
||||
# object (_type_): the form widget
|
||||
|
||||
# Returns:
|
||||
# dict: dictionary of objectName:text items
|
||||
# """
|
||||
|
||||
# from frontend.custom_widgets import ReagentTypeForm
|
||||
# dicto = {}
|
||||
# reagents = []
|
||||
# logger.debug(f"Object type: {type(object)}")
|
||||
# # grab all widgets in form
|
||||
# try:
|
||||
# all_children = object.layout.parentWidget().findChildren(QWidget)
|
||||
# except AttributeError:
|
||||
# all_children = object.layout().parentWidget().findChildren(QWidget)
|
||||
# for item in all_children:
|
||||
# logger.debug(f"Looking at: {item.objectName()}: {type(item)}")
|
||||
# match item:
|
||||
# case QLineEdit():
|
||||
# dicto[item.objectName()] = item.text()
|
||||
# case QComboBox():
|
||||
# dicto[item.objectName()] = item.currentText()
|
||||
# case QDateEdit():
|
||||
# dicto[item.objectName()] = item.date().toPyDate()
|
||||
# case QSpinBox() | QDoubleSpinBox():
|
||||
# dicto[item.objectName()] = item.value()
|
||||
# case ReagentTypeForm():
|
||||
# reagent = extract_form_info(item)
|
||||
# logger.debug(f"Reagent found: {reagent}")
|
||||
# if isinstance(reagent, tuple):
|
||||
# reagent = reagent[0]
|
||||
# # reagents[reagent["name"].strip()] = {'eol':int(reagent['eol'])}
|
||||
# reagents.append({k:v for k,v in reagent.items() if k not in ['', 'qt_spinbox_lineedit']})
|
||||
# # value for ad hoc check above
|
||||
# if isinstance(dicto, tuple):
|
||||
# logger.warning(f"Got tuple for dicto for some reason.")
|
||||
# dicto = dicto[0]
|
||||
# if isinstance(reagents, tuple):
|
||||
# logger.warning(f"Got tuple for reagents for some reason.")
|
||||
# reagents = reagents[0]
|
||||
# if reagents != {}:
|
||||
# return dicto, reagents
|
||||
# return dicto
|
||||
|
||||
|
||||
from .main_window_functions import *
|
||||
from .submission_functions import *
|
||||
102
src/submissions/frontend/functions/all_window_functions.py
Normal file
102
src/submissions/frontend/functions/all_window_functions.py
Normal file
@@ -0,0 +1,102 @@
|
||||
'''
|
||||
functions used by all windows in the application's frontend
|
||||
NOTE: Depreciated. Moved to functions.__init__
|
||||
'''
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from PyQt6.QtWidgets import QMainWindow, QFileDialog
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
def select_open_file(obj:QMainWindow, file_extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to read from
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
file_extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).resolve().__str__()
|
||||
home_dir = obj.last_dir.resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().resolve().__str__()
|
||||
fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', home_dir, filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
# fname = Path(QFileDialog.getOpenFileName(obj, 'Open file', filter = f"{file_extension}(*.{file_extension})")[0])
|
||||
obj.last_file = fname
|
||||
return fname
|
||||
|
||||
def select_save_file(obj:QMainWindow, default_name:str, extension:str) -> Path:
|
||||
"""
|
||||
File dialog to select a file to write to
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Original main app window to be parent
|
||||
default_name (str): default base file name
|
||||
extension (str): file extension
|
||||
|
||||
Returns:
|
||||
Path: Path of file to be opened
|
||||
"""
|
||||
try:
|
||||
# home_dir = Path(obj.ctx.directory_path).joinpath(default_name).resolve().__str__()
|
||||
home_dir = obj.last_dir.joinpath(default_name).resolve().__str__()
|
||||
except FileNotFoundError:
|
||||
home_dir = Path.home().joinpath(default_name).resolve().__str__()
|
||||
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter = f"{extension}(*.{extension})")[0])
|
||||
# fname = Path(QFileDialog.getSaveFileName(obj, "Save File", filter = f"{extension}(*.{extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
|
||||
# def extract_form_info(object) -> dict:
|
||||
# """
|
||||
# retrieves object names and values from form
|
||||
# DEPRECIATED. Replaced by individual form parser methods.
|
||||
|
||||
# Args:
|
||||
# object (_type_): the form widget
|
||||
|
||||
# Returns:
|
||||
# dict: dictionary of objectName:text items
|
||||
# """
|
||||
|
||||
# from frontend.custom_widgets import ReagentTypeForm
|
||||
# dicto = {}
|
||||
# reagents = []
|
||||
# logger.debug(f"Object type: {type(object)}")
|
||||
# # grab all widgets in form
|
||||
# try:
|
||||
# all_children = object.layout.parentWidget().findChildren(QWidget)
|
||||
# except AttributeError:
|
||||
# all_children = object.layout().parentWidget().findChildren(QWidget)
|
||||
# for item in all_children:
|
||||
# logger.debug(f"Looking at: {item.objectName()}: {type(item)}")
|
||||
# match item:
|
||||
# case QLineEdit():
|
||||
# dicto[item.objectName()] = item.text()
|
||||
# case QComboBox():
|
||||
# dicto[item.objectName()] = item.currentText()
|
||||
# case QDateEdit():
|
||||
# dicto[item.objectName()] = item.date().toPyDate()
|
||||
# case QSpinBox() | QDoubleSpinBox():
|
||||
# dicto[item.objectName()] = item.value()
|
||||
# case ReagentTypeForm():
|
||||
# reagent = extract_form_info(item)
|
||||
# logger.debug(f"Reagent found: {reagent}")
|
||||
# if isinstance(reagent, tuple):
|
||||
# reagent = reagent[0]
|
||||
# # reagents[reagent["name"].strip()] = {'eol':int(reagent['eol'])}
|
||||
# reagents.append({k:v for k,v in reagent.items() if k not in ['', 'qt_spinbox_lineedit']})
|
||||
# # value for ad hoc check above
|
||||
# if isinstance(dicto, tuple):
|
||||
# logger.warning(f"Got tuple for dicto for some reason.")
|
||||
# dicto = dicto[0]
|
||||
# if isinstance(reagents, tuple):
|
||||
# logger.warning(f"Got tuple for reagents for some reason.")
|
||||
# reagents = reagents[0]
|
||||
# if reagents != {}:
|
||||
# return dicto, reagents
|
||||
# return dicto
|
||||
@@ -3,11 +3,8 @@ contains operations used by multiple widgets.
|
||||
'''
|
||||
from datetime import date
|
||||
import difflib
|
||||
from getpass import getuser
|
||||
import inspect
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
from pprint import pformat
|
||||
import yaml
|
||||
import json
|
||||
from typing import Tuple, List
|
||||
@@ -17,28 +14,22 @@ from xhtml2pdf import pisa
|
||||
import pandas as pd
|
||||
from backend.db.models import *
|
||||
import logging
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow, QLabel, QWidget, QPushButton,
|
||||
QLineEdit, QComboBox, QDateEdit
|
||||
)
|
||||
from .all_window_functions import select_open_file, select_save_file
|
||||
from PyQt6.QtWidgets import QMainWindow, QPushButton
|
||||
# from .all_window_functions import select_open_file, select_save_file
|
||||
from . import select_open_file, select_save_file
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
from backend.db.models import BasicSubmission
|
||||
from backend.db.functions import (
|
||||
lookup_reagents, get_control_subtypes,
|
||||
update_subsampassoc_with_pcr, check_kit_integrity, update_last_used, lookup_organizations, lookup_kit_types,
|
||||
lookup_submissions, lookup_controls, lookup_samples, lookup_submission_sample_association, store_object, lookup_submission_type,
|
||||
#construct_submission_info, construct_kit_from_yaml, construct_org_from_yaml
|
||||
get_control_subtypes, update_subsampassoc_with_pcr, check_kit_integrity, update_last_used
|
||||
)
|
||||
from backend.excel.parser import SheetParser, PCRParser, SampleParser
|
||||
from backend.excel.parser import SheetParser, PCRParser
|
||||
from backend.excel.reports import make_report_html, make_report_xlsx, convert_data_list_to_df
|
||||
from backend.validators import PydSubmission, PydSample, PydReagent
|
||||
from tools import check_not_nan, convert_well_to_row_column
|
||||
from .custom_widgets.pop_ups import AlertPop, QuestionAsker
|
||||
from .custom_widgets import ReportDatePicker
|
||||
from .visualizations.control_charts import create_charts, construct_html
|
||||
from backend.validators import PydSubmission, PydKit
|
||||
from tools import Report, Result
|
||||
from frontend.custom_widgets.pop_ups import AlertPop, QuestionAsker
|
||||
from frontend.custom_widgets import ReportDatePicker
|
||||
from frontend.visualizations.control_charts import create_charts, construct_html
|
||||
from pathlib import Path
|
||||
from frontend.custom_widgets.misc import FirstStrandSalvage, FirstStrandPlateList, ReagentFormWidget
|
||||
from frontend.custom_widgets.misc import ReagentFormWidget
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -53,7 +44,7 @@ def import_submission_function(obj:QMainWindow, fname:Path|None=None) -> Tuple[Q
|
||||
Tuple[QMainWindow, dict|None]: Collection of new main app window and result dict
|
||||
"""
|
||||
logger.debug(f"\n\nStarting Import...\n\n")
|
||||
result = None
|
||||
report = Report()
|
||||
# logger.debug(obj.ctx)
|
||||
# initialize samples
|
||||
try:
|
||||
@@ -67,126 +58,38 @@ def import_submission_function(obj:QMainWindow, fname:Path|None=None) -> Tuple[Q
|
||||
fname = select_open_file(obj, file_extension="xlsx")
|
||||
logger.debug(f"Attempting to parse file: {fname}")
|
||||
if not fname.exists():
|
||||
result = dict(message=f"File {fname.__str__()} not found.", status="critical")
|
||||
return obj, result
|
||||
# result = dict(message=f"File {fname.__str__()} not found.", status="critical")
|
||||
report.add_result(Result(msg=f"File {fname.__str__()} not found.", status="critical"))
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
# create sheetparser using excel sheet and context from gui
|
||||
try:
|
||||
obj.prsr = SheetParser(ctx=obj.ctx, filepath=fname)
|
||||
except PermissionError:
|
||||
logger.error(f"Couldn't get permission to access file: {fname}")
|
||||
return obj, result
|
||||
return obj
|
||||
try:
|
||||
logger.debug(f"Submission dictionary:\n{pprint.pformat(obj.prsr.sub)}")
|
||||
logger.debug(f"Submission dictionary:\n{pformat(obj.prsr.sub)}")
|
||||
obj.pyd = obj.prsr.to_pydantic()
|
||||
logger.debug(f"Pydantic result: \n\n{pprint.pformat(obj.pyd)}\n\n")
|
||||
logger.debug(f"Pydantic result: \n\n{pformat(obj.pyd)}\n\n")
|
||||
except Exception as e:
|
||||
return obj, dict(message= f"Problem creating pydantic model:\n\n{e}", status="critical")
|
||||
# destroy any widgets from previous imports
|
||||
# obj.table_widget.formwidget.set_parent(None)
|
||||
# obj.current_submission_type = pyd.submission_type['value']
|
||||
# obj.current_file = pyd.filepath
|
||||
# Get list of fields from pydantic model.
|
||||
# fields = list(pyd.model_fields.keys()) + list(pyd.model_extra.keys())
|
||||
# fields.remove('filepath')
|
||||
# logger.debug(f"pydantic fields: {fields}")
|
||||
# for field in fields:
|
||||
# value = getattr(pyd, field)
|
||||
# logger.debug(f"Checking: {field}: {value}")
|
||||
# # Get from pydantic model whether field was completed in the form
|
||||
# if isinstance(value, dict) and field != 'ctx':
|
||||
# logger.debug(f"The field {field} is a dictionary: {value}")
|
||||
# if not value['parsed']:
|
||||
# obj.missing_info.append(field)
|
||||
# label = ParsedQLabel(value, field)
|
||||
# match field:
|
||||
# case 'submitting_lab':
|
||||
# logger.debug(f"{field}: {value['value']}")
|
||||
# # create combobox to hold looked up submitting labs
|
||||
# add_widget = QComboBox()
|
||||
# labs = [item.__str__() for item in lookup_organizations(ctx=obj.ctx)]
|
||||
# # try to set closest match to top of list
|
||||
# try:
|
||||
# labs = difflib.get_close_matches(value['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['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 {pyd.submission_type['value']}")
|
||||
# uses = [item.__str__() for item in lookup_kit_types(ctx=obj.ctx, used_for=pyd.submission_type['value'])]
|
||||
# logger.debug(f"Kits received for {pyd.submission_type['value']}: {uses}")
|
||||
# if check_not_nan(value['value']):
|
||||
# logger.debug(f"The extraction kit in parser was: {value['value']}")
|
||||
# uses.insert(0, uses.pop(uses.index(value['value'])))
|
||||
# obj.ext_kit = value['value']
|
||||
# else:
|
||||
# logger.error(f"Couldn't find {obj.prsr.sub['extraction_kit']}")
|
||||
# obj.ext_kit = uses[0]
|
||||
# # 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['value'])
|
||||
# # if not found, use today
|
||||
# except:
|
||||
# add_widget.setDate(date.today())
|
||||
# case 'samples':
|
||||
# # hold samples in 'obj' until form submitted
|
||||
# logger.debug(f"{field}:\n\t{value}")
|
||||
# obj.samples = value
|
||||
# continue
|
||||
# case 'submission_category':
|
||||
# add_widget = QComboBox()
|
||||
# cats = ['Diagnostic', "Surveillance", "Research"]
|
||||
# cats += [item.name for item in lookup_submission_type(ctx=obj.ctx)]
|
||||
# try:
|
||||
# cats.insert(0, cats.pop(cats.index(value['value'])))
|
||||
# except ValueError:
|
||||
# cats.insert(0, cats.pop(cats.index(pyd.submission_type['value'])))
|
||||
# add_widget.addItems(cats)
|
||||
# case "ctx" | 'reagents' | 'csv' | 'filepath':
|
||||
# continue
|
||||
# case _:
|
||||
# # anything else gets added in as a line edit
|
||||
# add_widget = QLineEdit()
|
||||
# logger.debug(f"Setting widget text to {str(value['value']).replace('_', ' ')}")
|
||||
# add_widget.setText(str(value['value']).replace("_", " "))
|
||||
# try:
|
||||
# add_widget.setObjectName(field)
|
||||
# logger.debug(f"Widget name set to: {add_widget.objectName()}")
|
||||
# obj.table_widget.formlayout.addWidget(label)
|
||||
# obj.table_widget.formlayout.addWidget(add_widget)
|
||||
# except AttributeError as e:
|
||||
# logger.error(e)
|
||||
report.add_result(Result(msg=f"Problem creating pydantic model:\n\n{e}", status="critical"))
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
obj.form = obj.pyd.toForm(parent=obj)
|
||||
obj.table_widget.formlayout.addWidget(obj.form)
|
||||
# kit_widget = obj.table_widget.formlayout.parentWidget().findChild(QComboBox, 'extraction_kit')
|
||||
kit_widget = obj.form.find_widgets(object_name="extraction_kit")[0].input
|
||||
logger.debug(f"Kitwidget {kit_widget}")
|
||||
# block
|
||||
# with QSignalBlocker(kit_widget) as blocker:
|
||||
# kit_widget.addItems(obj.uses)
|
||||
obj.scrape_reagents(kit_widget.currentText())
|
||||
kit_widget.currentTextChanged.connect(obj.scrape_reagents)
|
||||
# compare obj.reagents with expected reagents in kit
|
||||
if obj.prsr.sample_result != None:
|
||||
msg = AlertPop(message=obj.prsr.sample_result, status="WARNING")
|
||||
msg.exec()
|
||||
# logger.debug(f"Pydantic extra fields: {obj.pyd.model_extra}")
|
||||
# if "csv" in pyd.model_extra:
|
||||
# obj.csv = pyd.model_extra['csv']
|
||||
logger.debug(f"All attributes of obj:\n{pprint.pformat(obj.__dict__)}")
|
||||
return obj, result
|
||||
obj.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {obj.report.results}")
|
||||
logger.debug(f"All attributes of obj:\n{pformat(obj.__dict__)}")
|
||||
return obj
|
||||
|
||||
def kit_reload_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -198,21 +101,16 @@ def kit_reload_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
result = None
|
||||
report = Report()
|
||||
# for item in obj.table_widget.formlayout.parentWidget().findChildren(QWidget):
|
||||
logger.debug(f"Attempting to clear {obj.form.find_widgets()}")
|
||||
|
||||
for item in obj.form.find_widgets():
|
||||
if isinstance(item, ReagentFormWidget):
|
||||
item.setParent(None)
|
||||
# if item.text().startswith("Lot"):
|
||||
# item.setParent(None)
|
||||
# else:
|
||||
# logger.debug(f"Type of {item.objectName()} is {type(item)}")
|
||||
# if item.objectName().startswith("lot_"):
|
||||
# item.setParent(None)
|
||||
kit_integrity_completion_function(obj)
|
||||
return obj, result
|
||||
obj = kit_integrity_completion_function(obj)
|
||||
obj.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {obj.report.results}")
|
||||
return obj
|
||||
|
||||
def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -224,9 +122,8 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
result = None
|
||||
report = Report()
|
||||
missing_reagents = []
|
||||
# kit_reload_function(obj=obj)
|
||||
logger.debug(inspect.currentframe().f_back.f_code.co_name)
|
||||
# find the widget that contains kit info
|
||||
kit_widget = obj.form.find_widgets(object_name="extraction_kit")[0].input
|
||||
@@ -235,13 +132,8 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
||||
obj.ext_kit = kit_widget.currentText()
|
||||
# for reagent in obj.pyd.reagents:
|
||||
for reagent in obj.form.reagents:
|
||||
# obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':True}, item.type, title=False, label_name=f"lot_{item.type}"))
|
||||
# reagent = dict(type=item.type, lot=item.lot, expiry=item.expiry, name=item.name)
|
||||
# add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit)
|
||||
# obj.table_widget.formlayout.addWidget(add_widget)
|
||||
add_widget = ReagentFormWidget(parent=obj.table_widget.formwidget, reagent=reagent, extraction_kit=obj.ext_kit)
|
||||
add_widget.setParent(obj.form)
|
||||
# obj.table_widget.formlayout.addWidget(add_widget)
|
||||
obj.form.layout().addWidget(add_widget)
|
||||
if reagent.missing:
|
||||
missing_reagents.append(reagent)
|
||||
@@ -249,22 +141,23 @@ def kit_integrity_completion_function(obj:QMainWindow) -> Tuple[QMainWindow, dic
|
||||
# TODO: put check_kit_integrity here instead of what's here?
|
||||
# see if there are any missing reagents
|
||||
if len(missing_reagents) > 0:
|
||||
result = dict(message=f"The submission you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.type.upper() for item in missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!", status="Warning")
|
||||
# for item in obj.missing_reagents:
|
||||
# # Add label that has parsed as False to show "MISSING" label.
|
||||
# obj.table_widget.formlayout.addWidget(ParsedQLabel({'parsed':False}, item.type, title=False, label_name=f"missing_{item.type}"))
|
||||
# # Set default parameters for the empty reagent.
|
||||
# reagent = dict(type=item.type, lot=None, expiry=date.today(), name=None)
|
||||
# # create and add widget
|
||||
# # add_widget = ImportReagent(ctx=obj.ctx, reagent=PydReagent(**reagent), extraction_kit=obj.ext_kit)
|
||||
# add_widget = ImportReagent(ctx=obj.ctx, reagent=reagent, extraction_kit=obj.ext_kit)
|
||||
# obj.table_widget.formlayout.addWidget(add_widget)
|
||||
# Add submit button to the form.
|
||||
result = Result(msg=f"""The submission you are importing is missing some reagents expected by the kit.\n\n
|
||||
It looks like you are missing: {[item.type.upper() for item in missing_reagents]}\n\n
|
||||
Alternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.
|
||||
\n\nPlease make sure you check the lots carefully!""".replace(" ", ""), status="Warning")
|
||||
report.add_result(result)
|
||||
if hasattr(obj.pyd, "csv"):
|
||||
export_csv_btn = QPushButton("Export CSV")
|
||||
export_csv_btn.setObjectName("export_csv_btn")
|
||||
obj.form.layout().addWidget(export_csv_btn)
|
||||
export_csv_btn.clicked.connect(obj.export_csv)
|
||||
submit_btn = QPushButton("Submit")
|
||||
submit_btn.setObjectName("submit_btn")
|
||||
obj.form.layout().addWidget(submit_btn)
|
||||
submit_btn.clicked.connect(obj.submit_new_sample)
|
||||
return obj, result
|
||||
obj.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {obj.report.results}")
|
||||
return obj
|
||||
|
||||
def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -277,13 +170,15 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
logger.debug(f"\n\nBeginning Submission\n\n")
|
||||
result = None
|
||||
report = Report()
|
||||
obj.pyd: PydSubmission = obj.form.parse_form()
|
||||
logger.debug(f"Submission: {pprint.pformat(obj.pyd)}")
|
||||
logger.debug(f"Submission: {pformat(obj.pyd)}")
|
||||
logger.debug("Checking kit integrity...")
|
||||
kit_integrity = check_kit_integrity(sub=obj.pyd)
|
||||
if kit_integrity != None:
|
||||
return obj, dict(message=kit_integrity['message'], status="critical")
|
||||
result = check_kit_integrity(sub=obj.pyd)
|
||||
report.add_result(result)
|
||||
if len(result.results) > 0:
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
base_submission, result = obj.pyd.toSQL()
|
||||
# check output message for issues
|
||||
match result['code']:
|
||||
@@ -308,25 +203,28 @@ def submit_new_sample_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
# add reagents to submission object
|
||||
for reagent in base_submission.reagents:
|
||||
update_last_used(reagent=reagent, kit=base_submission.extraction_kit)
|
||||
logger.debug(f"Here is the final submission: {pprint.pformat(base_submission.__dict__)}")
|
||||
logger.debug(f"Parsed reagents: {pprint.pformat(base_submission.reagents)}")
|
||||
logger.debug(f"Here is the final submission: {pformat(base_submission.__dict__)}")
|
||||
logger.debug(f"Parsed reagents: {pformat(base_submission.reagents)}")
|
||||
logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.")
|
||||
base_submission.save()
|
||||
# update summary sheet
|
||||
obj.table_widget.sub_wid.setData()
|
||||
# reset form
|
||||
obj.form.setParent(None)
|
||||
logger.debug(f"All attributes of obj: {pprint.pformat(obj.__dict__)}")
|
||||
logger.debug(f"All attributes of obj: {pformat(obj.__dict__)}")
|
||||
wkb = obj.pyd.autofill_excel()
|
||||
if wkb != None:
|
||||
fname = select_save_file(obj=obj, default_name=obj.pyd.construct_filename(), extension="xlsx")
|
||||
wkb.save(filename=fname.__str__())
|
||||
try:
|
||||
wkb.save(filename=fname.__str__())
|
||||
except PermissionError:
|
||||
logger.error("Hit a permission error when saving workbook. Cancelled?")
|
||||
if hasattr(obj.pyd, 'csv'):
|
||||
dlg = QuestionAsker("Export CSV?", "Would you like to export the csv file?")
|
||||
if dlg.exec():
|
||||
fname = select_save_file(obj, f"{obj.pyd.rsl_plate_num['value']}.csv", extension="csv")
|
||||
fname = select_save_file(obj, f"{obj.pyd.construct_filename()}.csv", extension="csv")
|
||||
try:
|
||||
obj.csv.to_csv(fname.__str__(), index=False)
|
||||
obj.pyd.csv.to_csv(fname.__str__(), index=False)
|
||||
except PermissionError:
|
||||
logger.debug(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
||||
return obj, result
|
||||
@@ -344,11 +242,9 @@ def generate_report_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
# ask for date ranges
|
||||
dlg = ReportDatePicker()
|
||||
if dlg.exec():
|
||||
# info = extract_form_info(dlg)
|
||||
info = dlg.parse_form()
|
||||
logger.debug(f"Report info: {info}")
|
||||
# find submissions based on date range
|
||||
# subs = lookup_submissions(ctx=obj.ctx, start_date=info['start_date'], end_date=info['end_date'])
|
||||
subs = BasicSubmission.query(start_date=info['start_date'], end_date=info['end_date'])
|
||||
# convert each object to dict
|
||||
records = [item.report_dict() for item in subs]
|
||||
@@ -357,7 +253,6 @@ def generate_report_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
html = make_report_html(df=summary_df, start_date=info['start_date'], end_date=info['end_date'])
|
||||
# get save location of report
|
||||
fname = select_save_file(obj=obj, default_name=f"Submissions_Report_{info['start_date']}-{info['end_date']}.pdf", extension="pdf")
|
||||
# logger.debug(f"report output name: {fname}")
|
||||
with open(fname, "w+b") as f:
|
||||
pisa.CreatePDF(html, dest=f)
|
||||
writer = pd.ExcelWriter(fname.with_suffix(".xlsx"), engine='openpyxl')
|
||||
@@ -378,7 +273,7 @@ def generate_report_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
if cell.row > 1:
|
||||
cell.style = 'Currency'
|
||||
writer.close()
|
||||
return obj, result
|
||||
return obj, None
|
||||
|
||||
def add_kit_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -405,7 +300,7 @@ def add_kit_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
except PermissionError:
|
||||
return
|
||||
# send to kit creator function
|
||||
result = construct_kit_from_yaml(ctx=obj.ctx, exp=exp)
|
||||
result = PydKit(**exp)
|
||||
return obj, result
|
||||
|
||||
def add_org_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
@@ -446,7 +341,7 @@ def controls_getter_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
result = None
|
||||
report = Report()
|
||||
# subtype defaults to disabled
|
||||
try:
|
||||
obj.table_widget.sub_typer.disconnect()
|
||||
@@ -461,7 +356,8 @@ def controls_getter_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
with QSignalBlocker(obj.table_widget.datepicker.start_date) as blocker:
|
||||
obj.table_widget.datepicker.start_date.setDate(threemonthsago)
|
||||
obj._controls_getter()
|
||||
return obj, result
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
# convert to python useable date objects
|
||||
obj.start_date = obj.table_widget.datepicker.start_date.date().toPyDate()
|
||||
obj.end_date = obj.table_widget.datepicker.end_date.date().toPyDate()
|
||||
@@ -481,7 +377,8 @@ def controls_getter_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
obj.table_widget.sub_typer.clear()
|
||||
obj.table_widget.sub_typer.setEnabled(False)
|
||||
obj._chart_maker()
|
||||
return obj, result
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
|
||||
def chart_maker_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -493,7 +390,7 @@ def chart_maker_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Collection of new main app window and result dict
|
||||
"""
|
||||
result = None
|
||||
report = Report()
|
||||
logger.debug(f"Control getter context: \n\tControl type: {obj.con_type}\n\tMode: {obj.mode}\n\tStart Date: {obj.start_date}\n\tEnd Date: {obj.end_date}")
|
||||
# set the subtype for kraken
|
||||
if obj.table_widget.sub_typer.currentText() == "":
|
||||
@@ -517,7 +414,7 @@ def chart_maker_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
if data == []:
|
||||
return obj, dict(status="Critical", message="No data found for controls in given date range.")
|
||||
# send to dataframe creator
|
||||
df = convert_data_list_to_df(ctx=obj.ctx, input=data, subtype=obj.subtype)
|
||||
df = convert_data_list_to_df(input=data, subtype=obj.subtype)
|
||||
if obj.subtype == None:
|
||||
title = obj.mode
|
||||
else:
|
||||
@@ -531,7 +428,8 @@ def chart_maker_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
obj.table_widget.webengineview.setHtml(html)
|
||||
obj.table_widget.webengineview.update()
|
||||
logger.debug("Figure updated... I hope.")
|
||||
return obj, result
|
||||
obj.report.add_result(report)
|
||||
return obj
|
||||
|
||||
def link_controls_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -737,7 +635,7 @@ def import_pcr_results_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
result = None
|
||||
fname = select_open_file(obj, file_extension="xlsx")
|
||||
parser = PCRParser(ctx=obj.ctx, filepath=fname)
|
||||
parser = PCRParser(filepath=fname)
|
||||
logger.debug(f"Attempting lookup for {parser.plate_num}")
|
||||
# sub = lookup_submission_by_rsl_num(ctx=obj.ctx, rsl_num=parser.plate_num)
|
||||
sub = BasicSubmission.query(rsl_number=parser.plate_num)
|
||||
@@ -884,107 +782,108 @@ def autofill_excel(obj:QMainWindow, xl_map:dict, reagents:List[dict], missing_re
|
||||
fname = select_save_file(obj=obj, default_name=info['rsl_plate_num'], extension="xlsx")
|
||||
workbook.save(filename=fname.__str__())
|
||||
|
||||
def construct_first_strand_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
Generates a csv file from client submitted xlsx file.
|
||||
# def construct_first_strand_function(obj:QMainWindow) -> Tuple[QMainWindow, dict]:
|
||||
# """
|
||||
# Generates a csv file from client submitted xlsx file.
|
||||
# NOTE: Depreciated, now folded into import Artic.
|
||||
|
||||
Args:
|
||||
obj (QMainWindow): Main application
|
||||
# Args:
|
||||
# obj (QMainWindow): Main application
|
||||
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Updated main application and result
|
||||
"""
|
||||
def get_plates(input_sample_number:str, plates:list) -> Tuple[int, str]:
|
||||
logger.debug(f"Looking up {input_sample_number} in {plates}")
|
||||
# samp = lookup_samples(ctx=obj.ctx, ww_processing_num=input_sample_number)
|
||||
samp = BasicSample.query(ww_processing_num=input_sample_number)
|
||||
if samp == None:
|
||||
# samp = lookup_samples(ctx=obj.ctx, submitter_id=input_sample_number)
|
||||
samp = BasicSample.query(submitter_id=input_sample_number)
|
||||
if samp == None:
|
||||
return None, None
|
||||
logger.debug(f"Got sample: {samp}")
|
||||
# new_plates = [(iii+1, lookup_submission_sample_association(ctx=obj.ctx, sample=samp, submission=plate)) for iii, plate in enumerate(plates)]
|
||||
new_plates = [(iii+1, SubmissionSampleAssociation.query(sample=samp, submission=plate)) for iii, plate in enumerate(plates)]
|
||||
logger.debug(f"Associations: {pprint.pformat(new_plates)}")
|
||||
try:
|
||||
plate_num, plate = next(assoc for assoc in new_plates if assoc[1])
|
||||
except StopIteration:
|
||||
plate_num, plate = None, None
|
||||
logger.debug(f"Plate number {plate_num} is {plate}")
|
||||
return plate_num, plate
|
||||
fname = select_open_file(obj=obj, file_extension="xlsx")
|
||||
xl = pd.ExcelFile(fname)
|
||||
sprsr = SampleParser(ctx=obj.ctx, xl=xl, submission_type="First Strand")
|
||||
_, samples = sprsr.parse_samples(generate=False)
|
||||
logger.debug(f"Samples: {pformat(samples)}")
|
||||
logger.debug("Called first strand sample parser")
|
||||
plates = sprsr.grab_plates()
|
||||
# Fix no plates found in form.
|
||||
if plates == []:
|
||||
dlg = FirstStrandPlateList(ctx=obj.ctx)
|
||||
if dlg.exec():
|
||||
plates = dlg.parse_form()
|
||||
plates = list(set(plates))
|
||||
logger.debug(f"Plates: {pformat(plates)}")
|
||||
output_samples = []
|
||||
logger.debug(f"Samples: {pformat(samples)}")
|
||||
old_plate_number = 1
|
||||
old_plate = ''
|
||||
for item in samples:
|
||||
try:
|
||||
item['well'] = re.search(r"\s\((.*)\)$", item['submitter_id']).groups()[0]
|
||||
except AttributeError:
|
||||
pass
|
||||
item['submitter_id'] = re.sub(r"\s\(.*\)$", "", str(item['submitter_id'])).strip()
|
||||
new_dict = {}
|
||||
new_dict['sample'] = item['submitter_id']
|
||||
plate_num, plate = get_plates(input_sample_number=new_dict['sample'], plates=plates)
|
||||
if plate_num == None:
|
||||
plate_num = str(old_plate_number) + "*"
|
||||
else:
|
||||
old_plate_number = plate_num
|
||||
logger.debug(f"Got plate number: {plate_num}, plate: {plate}")
|
||||
if item['submitter_id'] == "NTC1":
|
||||
new_dict['destination_row'] = 8
|
||||
new_dict['destination_column'] = 2
|
||||
new_dict['plate_number'] = 'control'
|
||||
new_dict['plate'] = None
|
||||
output_samples.append(new_dict)
|
||||
continue
|
||||
elif item['submitter_id'] == "NTC2":
|
||||
new_dict['destination_row'] = 8
|
||||
new_dict['destination_column'] = 5
|
||||
new_dict['plate_number'] = 'control'
|
||||
new_dict['plate'] = None
|
||||
output_samples.append(new_dict)
|
||||
continue
|
||||
else:
|
||||
new_dict['destination_row'] = item['row']
|
||||
new_dict['destination_column'] = item['column']
|
||||
new_dict['plate_number'] = plate_num
|
||||
# Fix plate association not found
|
||||
if plate == None:
|
||||
dlg = FirstStrandSalvage(ctx=obj.ctx, submitter_id=item['submitter_id'], rsl_plate_num=old_plate)
|
||||
if dlg.exec():
|
||||
item.update(dlg.parse_form())
|
||||
try:
|
||||
new_dict['source_row'], new_dict['source_column'] = convert_well_to_row_column(item['well'])
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
new_dict['plate'] = plate.submission.rsl_plate_num
|
||||
new_dict['source_row'] = plate.row
|
||||
new_dict['source_column'] = plate.column
|
||||
old_plate = plate.submission.rsl_plate_num
|
||||
output_samples.append(new_dict)
|
||||
df = pd.DataFrame.from_records(output_samples)
|
||||
df.sort_values(by=['destination_column', 'destination_row'], ascending=True, inplace=True)
|
||||
columnsTitles = ['sample', 'destination_column', 'destination_row', 'plate_number', 'plate', "source_column", 'source_row']
|
||||
df = df.reindex(columns=columnsTitles)
|
||||
ofname = select_save_file(obj=obj, default_name=f"First Strand {date.today()}", extension="csv")
|
||||
df.to_csv(ofname, index=False)
|
||||
return obj, None
|
||||
# Returns:
|
||||
# Tuple[QMainWindow, dict]: Updated main application and result
|
||||
# """
|
||||
# def get_plates(input_sample_number:str, plates:list) -> Tuple[int, str]:
|
||||
# logger.debug(f"Looking up {input_sample_number} in {plates}")
|
||||
# # samp = lookup_samples(ctx=obj.ctx, ww_processing_num=input_sample_number)
|
||||
# samp = BasicSample.query(ww_processing_num=input_sample_number)
|
||||
# if samp == None:
|
||||
# # samp = lookup_samples(ctx=obj.ctx, submitter_id=input_sample_number)
|
||||
# samp = BasicSample.query(submitter_id=input_sample_number)
|
||||
# if samp == None:
|
||||
# return None, None
|
||||
# logger.debug(f"Got sample: {samp}")
|
||||
# # new_plates = [(iii+1, lookup_submission_sample_association(ctx=obj.ctx, sample=samp, submission=plate)) for iii, plate in enumerate(plates)]
|
||||
# new_plates = [(iii+1, SubmissionSampleAssociation.query(sample=samp, submission=plate)) for iii, plate in enumerate(plates)]
|
||||
# logger.debug(f"Associations: {pformat(new_plates)}")
|
||||
# try:
|
||||
# plate_num, plate = next(assoc for assoc in new_plates if assoc[1])
|
||||
# except StopIteration:
|
||||
# plate_num, plate = None, None
|
||||
# logger.debug(f"Plate number {plate_num} is {plate}")
|
||||
# return plate_num, plate
|
||||
# fname = select_open_file(obj=obj, file_extension="xlsx")
|
||||
# xl = pd.ExcelFile(fname)
|
||||
# sprsr = SampleParser(xl=xl, submission_type="First Strand")
|
||||
# _, samples = sprsr.parse_samples(generate=False)
|
||||
# logger.debug(f"Samples: {pformat(samples)}")
|
||||
# logger.debug("Called first strand sample parser")
|
||||
# plates = sprsr.grab_plates()
|
||||
# # Fix no plates found in form.
|
||||
# if plates == []:
|
||||
# dlg = FirstStrandPlateList(ctx=obj.ctx)
|
||||
# if dlg.exec():
|
||||
# plates = dlg.parse_form()
|
||||
# plates = list(set(plates))
|
||||
# logger.debug(f"Plates: {pformat(plates)}")
|
||||
# output_samples = []
|
||||
# logger.debug(f"Samples: {pformat(samples)}")
|
||||
# old_plate_number = 1
|
||||
# old_plate = ''
|
||||
# for item in samples:
|
||||
# try:
|
||||
# item['well'] = re.search(r"\s\((.*)\)$", item['submitter_id']).groups()[0]
|
||||
# except AttributeError:
|
||||
# pass
|
||||
# item['submitter_id'] = re.sub(r"\s\(.*\)$", "", str(item['submitter_id'])).strip()
|
||||
# new_dict = {}
|
||||
# new_dict['sample'] = item['submitter_id']
|
||||
# plate_num, plate = get_plates(input_sample_number=new_dict['sample'], plates=plates)
|
||||
# if plate_num == None:
|
||||
# plate_num = str(old_plate_number) + "*"
|
||||
# else:
|
||||
# old_plate_number = plate_num
|
||||
# logger.debug(f"Got plate number: {plate_num}, plate: {plate}")
|
||||
# if item['submitter_id'] == "NTC1":
|
||||
# new_dict['destination_row'] = 8
|
||||
# new_dict['destination_column'] = 2
|
||||
# new_dict['plate_number'] = 'control'
|
||||
# new_dict['plate'] = None
|
||||
# output_samples.append(new_dict)
|
||||
# continue
|
||||
# elif item['submitter_id'] == "NTC2":
|
||||
# new_dict['destination_row'] = 8
|
||||
# new_dict['destination_column'] = 5
|
||||
# new_dict['plate_number'] = 'control'
|
||||
# new_dict['plate'] = None
|
||||
# output_samples.append(new_dict)
|
||||
# continue
|
||||
# else:
|
||||
# new_dict['destination_row'] = item['row']
|
||||
# new_dict['destination_column'] = item['column']
|
||||
# new_dict['plate_number'] = plate_num
|
||||
# # Fix plate association not found
|
||||
# if plate == None:
|
||||
# dlg = FirstStrandSalvage(ctx=obj.ctx, submitter_id=item['submitter_id'], rsl_plate_num=old_plate)
|
||||
# if dlg.exec():
|
||||
# item.update(dlg.parse_form())
|
||||
# try:
|
||||
# new_dict['source_row'], new_dict['source_column'] = convert_well_to_row_column(item['well'])
|
||||
# except KeyError:
|
||||
# pass
|
||||
# else:
|
||||
# new_dict['plate'] = plate.submission.rsl_plate_num
|
||||
# new_dict['source_row'] = plate.row
|
||||
# new_dict['source_column'] = plate.column
|
||||
# old_plate = plate.submission.rsl_plate_num
|
||||
# output_samples.append(new_dict)
|
||||
# df = pd.DataFrame.from_records(output_samples)
|
||||
# df.sort_values(by=['destination_column', 'destination_row'], ascending=True, inplace=True)
|
||||
# columnsTitles = ['sample', 'destination_column', 'destination_row', 'plate_number', 'plate', "source_column", 'source_row']
|
||||
# df = df.reindex(columns=columnsTitles)
|
||||
# ofname = select_save_file(obj=obj, default_name=f"First Strand {date.today()}", extension="csv")
|
||||
# df.to_csv(ofname, index=False)
|
||||
# return obj, None
|
||||
|
||||
def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, dict]:
|
||||
"""
|
||||
@@ -998,6 +897,7 @@ def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, d
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict]: Updated application and result
|
||||
"""
|
||||
report = Report()
|
||||
logger.debug(f"Extraction kit: {extraction_kit}")
|
||||
# obj.reagents = []
|
||||
# obj.missing_reagents = []
|
||||
@@ -1022,5 +922,14 @@ def scrape_reagents(obj:QMainWindow, extraction_kit:str) -> Tuple[QMainWindow, d
|
||||
obj.form.reagents = obj.prsr.sub['reagents']
|
||||
# logger.debug(f"Imported reagents: {obj.reagents}")
|
||||
# logger.debug(f"Missing reagents: {obj.missing_reagents}")
|
||||
return obj, None
|
||||
obj.report.add_result(report)
|
||||
logger.debug(f"Outgoing report: {obj.report.results}")
|
||||
return obj
|
||||
|
||||
def export_csv_function(obj:QMainWindow, fname:Path|None=None):
|
||||
if isinstance(fname, bool) or fname == None:
|
||||
fname = select_save_file(obj=obj, default_name=obj.pyd.construct_filename(), extension="csv")
|
||||
try:
|
||||
obj.pyd.csv.to_csv(fname.__str__(), index=False)
|
||||
except PermissionError:
|
||||
logger.debug(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
||||
Reference in New Issue
Block a user