Post code-cleanup
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Constructs main application.
|
||||
"""
|
||||
import getpass
|
||||
from pprint import pformat
|
||||
from PyQt6.QtCore import qInstallMessageHandler
|
||||
from PyQt6.QtWidgets import (
|
||||
@@ -13,9 +14,10 @@ from pathlib import Path
|
||||
from markdown import markdown
|
||||
from __init__ import project_path
|
||||
from backend import SubmissionType, Reagent, BasicSample, Organization, KitType
|
||||
from tools import check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user
|
||||
from tools import (
|
||||
check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user, under_development
|
||||
)
|
||||
from .functions import select_save_file, select_open_file
|
||||
# from datetime import date
|
||||
from .pop_ups import HTMLPop, AlertPop
|
||||
from .misc import Pagifier
|
||||
import logging, webbrowser, sys, shutil
|
||||
@@ -84,7 +86,8 @@ class App(QMainWindow):
|
||||
maintenanceMenu.addAction(self.joinPCRAction)
|
||||
editMenu.addAction(self.editReagentAction)
|
||||
editMenu.addAction(self.manageOrgsAction)
|
||||
# editMenu.addAction(self.manageKitsAction)
|
||||
if getpass.getuser() == "lwark":
|
||||
editMenu.addAction(self.manageKitsAction)
|
||||
if not is_power_user():
|
||||
editMenu.setEnabled(False)
|
||||
|
||||
@@ -119,7 +122,7 @@ class App(QMainWindow):
|
||||
connect menu and tool bar item to functions
|
||||
"""
|
||||
self.importAction.triggered.connect(self.table_widget.formwidget.importSubmission)
|
||||
self.addReagentAction.triggered.connect(self.table_widget.formwidget.new_add_reagent)
|
||||
self.addReagentAction.triggered.connect(self.table_widget.formwidget.add_reagent)
|
||||
self.joinExtractionAction.triggered.connect(self.table_widget.sub_wid.link_extractions)
|
||||
self.joinPCRAction.triggered.connect(self.table_widget.sub_wid.link_pcr)
|
||||
self.helpAction.triggered.connect(self.showAbout)
|
||||
@@ -177,6 +180,11 @@ class App(QMainWindow):
|
||||
dlg = SearchBox(self, object_type=BasicSample, extras=[])
|
||||
dlg.exec()
|
||||
|
||||
@check_authorization
|
||||
def edit_reagent(self, *args, **kwargs):
|
||||
dlg = SearchBox(parent=self, object_type=Reagent, extras=[dict(name='Role', field="role")])
|
||||
dlg.exec()
|
||||
|
||||
def export_ST_yaml(self):
|
||||
"""
|
||||
Copies submission type yaml to file system for editing and remport
|
||||
@@ -191,13 +199,18 @@ class App(QMainWindow):
|
||||
fname = select_save_file(obj=self, default_name="Submission Type Template.yml", extension="yml")
|
||||
shutil.copyfile(yaml_path, fname)
|
||||
|
||||
@check_authorization
|
||||
def edit_reagent(self, *args, **kwargs):
|
||||
dlg = SearchBox(parent=self, object_type=Reagent, extras=[dict(name='Role', field="role")])
|
||||
dlg.exec()
|
||||
|
||||
@check_authorization
|
||||
def import_ST_yaml(self, *args, **kwargs):
|
||||
"""
|
||||
Imports a yml form into a submission type.
|
||||
|
||||
Args:
|
||||
*args ():
|
||||
**kwargs ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
fname = select_open_file(obj=self, file_extension="yml")
|
||||
if not fname:
|
||||
logger.info(f"Import cancelled.")
|
||||
@@ -220,9 +233,11 @@ class App(QMainWindow):
|
||||
dlg = ManagerWindow(parent=self, object_type=Organization, extras=[])
|
||||
if dlg.exec():
|
||||
new_org = dlg.parse_form()
|
||||
new_org.save()
|
||||
# logger.debug(new_org.__dict__)
|
||||
|
||||
def manage_kits(self):
|
||||
@under_development
|
||||
def manage_kits(self, *args, **kwargs):
|
||||
dlg = ManagerWindow(parent=self, object_type=KitType, extras=[])
|
||||
if dlg.exec():
|
||||
print(dlg.parse_form())
|
||||
|
||||
@@ -99,18 +99,24 @@ class ControlsViewer(InfoPane):
|
||||
self.mode_sub_type = self.mode_sub_typer.currentText()
|
||||
months = self.diff_month(self.start_date, self.end_date)
|
||||
# NOTE: query all controls using the type/start and end dates from the gui
|
||||
chart_settings = dict(sub_type=self.con_sub_type, start_date=self.start_date, end_date=self.end_date,
|
||||
mode=self.mode,
|
||||
sub_mode=self.mode_sub_type, parent=self, months=months)
|
||||
chart_settings = dict(
|
||||
sub_type=self.con_sub_type,
|
||||
start_date=self.start_date,
|
||||
end_date=self.end_date,
|
||||
mode=self.mode,
|
||||
sub_mode=self.mode_sub_type,
|
||||
parent=self,
|
||||
months=months
|
||||
)
|
||||
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):
|
||||
self.save_button.setEnabled(True)
|
||||
# NOTE: construct html for webview
|
||||
try:
|
||||
html = self.fig.to_html()
|
||||
except AttributeError:
|
||||
html = ""
|
||||
self.webview.setHtml(html)
|
||||
# try:
|
||||
# html = self.fig.html
|
||||
# except AttributeError:
|
||||
# html = ""
|
||||
self.webview.setHtml(self.fig.html)
|
||||
self.webview.update()
|
||||
return report
|
||||
|
||||
@@ -3,8 +3,9 @@ Creates forms that the user can enter equipment info into.
|
||||
'''
|
||||
from pprint import pformat
|
||||
from PyQt6.QtCore import Qt, QSignalBlocker
|
||||
from PyQt6.QtWidgets import (QDialog, QComboBox, QCheckBox,
|
||||
QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout)
|
||||
from PyQt6.QtWidgets import (
|
||||
QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout
|
||||
)
|
||||
from backend.db.models import Equipment, BasicSubmission, Process
|
||||
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
|
||||
import logging
|
||||
@@ -36,8 +37,8 @@ class EquipmentUsage(QDialog):
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
label = self.LabelRow(parent=self)
|
||||
self.layout.addWidget(label)
|
||||
for eq in self.opt_equipment:
|
||||
widg = eq.to_form(parent=self, used=self.used_equipment)
|
||||
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)
|
||||
@@ -64,6 +65,7 @@ class EquipmentUsage(QDialog):
|
||||
continue
|
||||
|
||||
class LabelRow(QWidget):
|
||||
"""Provides column headers"""
|
||||
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'''
|
||||
"""
|
||||
functions used by all windows in the application's frontend
|
||||
'''
|
||||
"""
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from PyQt6.QtCore import QMarginsF
|
||||
from PyQt6.QtGui import QPageLayout, QPageSize
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import QMainWindow, QFileDialog
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -60,3 +63,21 @@ def select_save_file(obj: QMainWindow, default_name: str, extension: str) -> Pat
|
||||
fname = Path(QFileDialog.getSaveFileName(obj, "Save File", home_dir, filter=f"{extension}(*.{extension})")[0])
|
||||
obj.last_dir = fname.parent
|
||||
return fname
|
||||
|
||||
|
||||
def save_pdf(obj: QWebEngineView, filename: Path):
|
||||
"""
|
||||
Handles printing to PDF
|
||||
|
||||
Args:
|
||||
obj (): Parent object
|
||||
filename (): Where to save pdf.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
page_layout = QPageLayout()
|
||||
page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
||||
page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
||||
page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
||||
obj.page().printToPdf(filename.absolute().__str__(), page_layout)
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
Gel box for artic quality control
|
||||
"""
|
||||
from operator import itemgetter
|
||||
from PyQt6.QtWidgets import (QWidget, QDialog, QGridLayout,
|
||||
QLabel, QLineEdit, QDialogButtonBox,
|
||||
QTextEdit, QComboBox
|
||||
)
|
||||
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 numpy as np
|
||||
import logging
|
||||
import logging, numpy as np
|
||||
from pprint import pformat
|
||||
from typing import Tuple, List
|
||||
from pathlib import Path
|
||||
@@ -103,7 +101,8 @@ class ControlsForm(QWidget):
|
||||
except TypeError:
|
||||
tt_text = None
|
||||
for iii, item in enumerate(
|
||||
["Negative Control Key", "Description", "Results - 65 C", "Results - 63 C", "Results - Spike"]):
|
||||
["Negative Control Key", "Description", "Results - 65 C", "Results - 63 C", "Results - Spike"]
|
||||
):
|
||||
label = QLabel(item)
|
||||
self.layout.addWidget(label, 0, iii, 1, 1)
|
||||
if iii > 1:
|
||||
|
||||
@@ -6,8 +6,8 @@ from PyQt6.QtCore import QSignalBlocker
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import QWidget, QGridLayout
|
||||
from tools import Report, report_result, Result
|
||||
from .misc import StartEndDatePicker, save_pdf
|
||||
from .functions import select_save_file
|
||||
from .misc import StartEndDatePicker
|
||||
from .functions import select_save_file, save_pdf
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -38,8 +38,7 @@ class InfoPane(QWidget):
|
||||
lastmonth = self.datepicker.end_date.date().addDays(-31)
|
||||
msg = f"Start date after end date is not allowed! Setting to {lastmonth.toString()}."
|
||||
logger.warning(msg)
|
||||
# NOTE: block signal that will rerun controls getter and set start date
|
||||
# Without triggering this function again
|
||||
# NOTE: block signal that will rerun controls getter and set start date without triggering this function again
|
||||
with QSignalBlocker(self.datepicker.start_date) as blocker:
|
||||
self.datepicker.start_date.setDate(lastmonth)
|
||||
self.update_data()
|
||||
|
||||
@@ -3,12 +3,10 @@ Contains miscellaneous widgets for frontend functions
|
||||
"""
|
||||
import math
|
||||
from datetime import date
|
||||
from PyQt6.QtGui import QPageLayout, QPageSize, QStandardItem, QIcon
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtGui import QStandardItem, QIcon
|
||||
from PyQt6.QtWidgets import (
|
||||
QLabel, QVBoxLayout,
|
||||
QLineEdit, QComboBox, QDialog,
|
||||
QDialogButtonBox, QDateEdit, QPushButton, QWidget, QHBoxLayout, QSizePolicy
|
||||
QLabel, QLineEdit, QComboBox, QDateEdit, QPushButton, QWidget,
|
||||
QHBoxLayout, QSizePolicy
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QDate, QSize, QMarginsF
|
||||
from tools import jinja_template_loading
|
||||
@@ -20,96 +18,98 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
||||
env = jinja_template_loading()
|
||||
|
||||
|
||||
class AddReagentForm(QDialog):
|
||||
"""
|
||||
dialog to add gather info about new reagent
|
||||
"""
|
||||
|
||||
def __init__(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
||||
reagent_name: str | None = None, kit: str | KitType | None = None) -> None:
|
||||
super().__init__()
|
||||
if reagent_name is None:
|
||||
reagent_name = reagent_role
|
||||
self.setWindowTitle("Add Reagent")
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
# NOTE: widget to get lot info
|
||||
self.name_input = QComboBox()
|
||||
self.name_input.setObjectName("name")
|
||||
self.name_input.setEditable(True)
|
||||
self.name_input.setCurrentText(reagent_name)
|
||||
self.lot_input = QLineEdit()
|
||||
self.lot_input.setObjectName("lot")
|
||||
self.lot_input.setText(reagent_lot)
|
||||
# NOTE: widget to get expiry info
|
||||
self.exp_input = QDateEdit(calendarPopup=True)
|
||||
self.exp_input.setObjectName('expiry')
|
||||
# NOTE: if expiry is not passed in from gui, use today
|
||||
if expiry is None:
|
||||
self.exp_input.setDate(QDate(1970, 1, 1))
|
||||
else:
|
||||
try:
|
||||
self.exp_input.setDate(expiry)
|
||||
except TypeError:
|
||||
self.exp_input.setDate(QDate(1970, 1, 1))
|
||||
# NOTE: widget to get reagent type info
|
||||
self.type_input = QComboBox()
|
||||
self.type_input.setObjectName('role')
|
||||
if kit:
|
||||
match kit:
|
||||
case str():
|
||||
kit = KitType.query(name=kit)
|
||||
case _:
|
||||
pass
|
||||
self.type_input.addItems([item.name for item in ReagentRole.query() if kit in item.kit_types])
|
||||
else:
|
||||
self.type_input.addItems([item.name for item in ReagentRole.query()])
|
||||
# NOTE: convert input to user-friendly string?
|
||||
try:
|
||||
reagent_role = reagent_role.replace("_", " ").title()
|
||||
except AttributeError:
|
||||
reagent_role = None
|
||||
# NOTE: set parsed reagent type to top of list
|
||||
index = self.type_input.findText(reagent_role, 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) -> dict:
|
||||
"""
|
||||
Converts information in form to dict.
|
||||
|
||||
Returns:
|
||||
dict: Output info
|
||||
"""
|
||||
return dict(name=self.name_input.currentText().strip(),
|
||||
lot=self.lot_input.text().strip(),
|
||||
expiry=self.exp_input.date().toPyDate(),
|
||||
role=self.type_input.currentText().strip())
|
||||
|
||||
def update_names(self):
|
||||
"""
|
||||
Updates reagent names form field with examples from reagent type
|
||||
"""
|
||||
self.name_input.clear()
|
||||
lookup = Reagent.query(role=self.type_input.currentText())
|
||||
self.name_input.addItems(list(set([item.name for item in lookup])))
|
||||
|
||||
|
||||
# class AddReagentForm(QDialog):
|
||||
# """
|
||||
# dialog to add gather info about new reagent (Defunct)
|
||||
# """
|
||||
#
|
||||
# def __init__(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
||||
# reagent_name: str | None = None, kit: str | KitType | None = None) -> None:
|
||||
# super().__init__()
|
||||
# if reagent_name is None:
|
||||
# reagent_name = reagent_role
|
||||
# self.setWindowTitle("Add Reagent")
|
||||
# QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
# self.buttonBox = QDialogButtonBox(QBtn)
|
||||
# self.buttonBox.accepted.connect(self.accept)
|
||||
# self.buttonBox.rejected.connect(self.reject)
|
||||
# # NOTE: widget to get lot info
|
||||
# self.name_input = QComboBox()
|
||||
# self.name_input.setObjectName("name")
|
||||
# self.name_input.setEditable(True)
|
||||
# self.name_input.setCurrentText(reagent_name)
|
||||
# self.lot_input = QLineEdit()
|
||||
# self.lot_input.setObjectName("lot")
|
||||
# self.lot_input.setText(reagent_lot)
|
||||
# # NOTE: widget to get expiry info
|
||||
# self.expiry_input = QDateEdit(calendarPopup=True)
|
||||
# self.expiry_input.setObjectName('expiry')
|
||||
# # NOTE: if expiry is not passed in from gui, use today
|
||||
# if expiry is None:
|
||||
# logger.warning(f"Did not receive expiry, setting to 1970, 1, 1")
|
||||
# self.expiry_input.setDate(QDate(1970, 1, 1))
|
||||
# else:
|
||||
# try:
|
||||
# self.expiry_input.setDate(expiry)
|
||||
# except TypeError:
|
||||
# self.expiry_input.setDate(QDate(1970, 1, 1))
|
||||
# # NOTE: widget to get reagent type info
|
||||
# self.role_input = QComboBox()
|
||||
# self.role_input.setObjectName('role')
|
||||
# if kit:
|
||||
# match kit:
|
||||
# case str():
|
||||
# kit = KitType.query(name=kit)
|
||||
# case _:
|
||||
# pass
|
||||
# self.role_input.addItems([item.name for item in ReagentRole.query() if kit in item.kit_types])
|
||||
# else:
|
||||
# self.role_input.addItems([item.name for item in ReagentRole.query()])
|
||||
# # NOTE: convert input to user-friendly string?
|
||||
# try:
|
||||
# reagent_role = reagent_role.replace("_", " ").title()
|
||||
# except AttributeError:
|
||||
# reagent_role = None
|
||||
# # NOTE: set parsed reagent type to top of list
|
||||
# index = self.role_input.findText(reagent_role, Qt.MatchFlag.MatchEndsWith)
|
||||
# if index >= 0:
|
||||
# self.role_input.setCurrentIndex(index)
|
||||
# self.layout = QVBoxLayout()
|
||||
# self.layout.addWidget(QLabel("Name:"))
|
||||
# self.layout.addWidget(self.name_input)
|
||||
# self.layout.addWidget(QLabel("Lot:"))
|
||||
# self.layout.addWidget(self.lot_input)
|
||||
# self.layout.addWidget(
|
||||
# QLabel("Expiry:\n(use exact date on reagent.\nEOL will be calculated from kit automatically)")
|
||||
# )
|
||||
# self.layout.addWidget(self.expiry_input)
|
||||
# self.layout.addWidget(QLabel("Type:"))
|
||||
# self.layout.addWidget(self.role_input)
|
||||
# self.layout.addWidget(self.buttonBox)
|
||||
# self.setLayout(self.layout)
|
||||
# self.role_input.currentTextChanged.connect(self.update_names)
|
||||
#
|
||||
# def parse_form(self) -> dict:
|
||||
# """
|
||||
# Converts information in form to dict.
|
||||
#
|
||||
# Returns:
|
||||
# dict: Output info
|
||||
# """
|
||||
# return dict(name=self.name_input.currentText().strip(),
|
||||
# lot=self.lot_input.text().strip(),
|
||||
# expiry=self.expiry_input.date().toPyDate(),
|
||||
# role=self.role_input.currentText().strip())
|
||||
#
|
||||
# def update_names(self):
|
||||
# """
|
||||
# Updates reagent names form field with examples from reagent type
|
||||
# """
|
||||
# self.name_input.clear()
|
||||
# lookup = Reagent.query(role=self.role_input.currentText())
|
||||
# self.name_input.addItems(list(set([item.name for item in lookup])))
|
||||
#
|
||||
#
|
||||
class StartEndDatePicker(QWidget):
|
||||
"""
|
||||
custom widget to pick start and end dates for controls graphs
|
||||
@@ -135,12 +135,12 @@ class StartEndDatePicker(QWidget):
|
||||
return QSize(80, 20)
|
||||
|
||||
|
||||
def save_pdf(obj: QWebEngineView, filename: Path):
|
||||
page_layout = QPageLayout()
|
||||
page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
||||
page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
||||
page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
||||
obj.page().printToPdf(filename.absolute().__str__(), page_layout)
|
||||
# def save_pdf(obj: QWebEngineView, filename: Path):
|
||||
# page_layout = QPageLayout()
|
||||
# page_layout.setPageSize(QPageSize(QPageSize.PageSizeId.A4))
|
||||
# page_layout.setOrientation(QPageLayout.Orientation.Portrait)
|
||||
# page_layout.setMargins(QMarginsF(25, 25, 25, 25))
|
||||
# obj.page().printToPdf(filename.absolute().__str__(), page_layout)
|
||||
|
||||
|
||||
# NOTE: subclass
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"""
|
||||
A widget to handle adding/updating any database object.
|
||||
"""
|
||||
from datetime import date
|
||||
from pprint import pformat
|
||||
from typing import Any, List, Tuple
|
||||
from typing import Any, Tuple
|
||||
from pydantic import BaseModel
|
||||
from PyQt6.QtWidgets import (
|
||||
QLabel, QDialog, QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit
|
||||
QLabel, QDialog, QWidget, QLineEdit, QGridLayout, QComboBox, QDialogButtonBox, QDateEdit, QSpinBox, QDoubleSpinBox
|
||||
)
|
||||
from sqlalchemy import String, TIMESTAMP
|
||||
from sqlalchemy import String, TIMESTAMP, INTEGER, FLOAT
|
||||
from sqlalchemy.orm import InstrumentedAttribute, ColumnProperty
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
|
||||
from tools import Report, Result, report_result
|
||||
from tools import Report, report_result
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -23,7 +24,6 @@ class AddEdit(QDialog):
|
||||
self.instance = instance
|
||||
self.object_type = instance.__class__
|
||||
self.layout = QGridLayout(self)
|
||||
# logger.debug(f"Manager: {manager}")
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
@@ -36,7 +36,6 @@ class AddEdit(QDialog):
|
||||
fields = {'name': fields.pop('name'), **fields}
|
||||
except KeyError:
|
||||
pass
|
||||
# logger.debug(pformat(fields, indent=4))
|
||||
height_counter = 0
|
||||
for key, field in fields.items():
|
||||
try:
|
||||
@@ -45,9 +44,6 @@ class AddEdit(QDialog):
|
||||
value = None
|
||||
try:
|
||||
logger.debug(f"{key} property: {type(field['class_attr'].property)}")
|
||||
# widget = EditProperty(self, key=key, column_type=field.property.expression.type,
|
||||
# value=getattr(self.instance, key))
|
||||
# logger.debug(f"Column type: {field}, Value: {value}")
|
||||
widget = EditProperty(self, key=key, column_type=field, value=value)
|
||||
except AttributeError as e:
|
||||
logger.error(f"Problem setting widget {key}: {e}")
|
||||
@@ -64,15 +60,11 @@ class AddEdit(QDialog):
|
||||
def parse_form(self) -> Tuple[BaseModel, Report]:
|
||||
report = Report()
|
||||
parsed = {result[0].strip(":"): result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)] if result[0]}
|
||||
logger.debug(parsed)
|
||||
# logger.debug(parsed)
|
||||
model = self.object_type.pydantic_model
|
||||
# NOTE: Hand-off to pydantic model for validation.
|
||||
# NOTE: Also, why am I not just using the toSQL method here. I could write one for contacts.
|
||||
model = model(**parsed)
|
||||
# output, result = model.to_sql()
|
||||
# report.add_result(result)
|
||||
# if len(report.results) < 1:
|
||||
# report.add_result(Result(msg="Added new regeant.", icon="Information", owner=__name__))
|
||||
return model, report
|
||||
|
||||
|
||||
@@ -84,7 +76,7 @@ class EditProperty(QWidget):
|
||||
self.label = QLabel(key.title().replace("_", " "))
|
||||
self.layout = QGridLayout()
|
||||
self.layout.addWidget(self.label, 0, 0, 1, 1)
|
||||
self.setObjectName(f"{key}:")
|
||||
self.setObjectName(key)
|
||||
match column_type['class_attr'].property:
|
||||
case ColumnProperty():
|
||||
self.column_property_set(column_type, value=value)
|
||||
@@ -97,15 +89,15 @@ class EditProperty(QWidget):
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def relationship_property_set(self, relationship_property, value=None):
|
||||
# print(relationship_property)
|
||||
self.property_class = relationship_property['class_attr'].property.entity.class_
|
||||
self.is_list = relationship_property['class_attr'].property.uselist
|
||||
choices = [item.name for item in self.property_class.query()]
|
||||
choices = [""] + [item.name for item in self.property_class.query()]
|
||||
try:
|
||||
instance_value = getattr(self.parent().instance, self.name)
|
||||
instance_value = getattr(self.parent().instance, self.objectName())
|
||||
except AttributeError:
|
||||
logger.error(f"Unable to get instance {self.parent().instance} attribute: {self.name}")
|
||||
logger.error(f"Unable to get instance {self.parent().instance} attribute: {self.objectName()}")
|
||||
instance_value = None
|
||||
# NOTE: get the value for the current instance and move it to the front.
|
||||
if isinstance(instance_value, list):
|
||||
instance_value = next((item.name for item in instance_value), None)
|
||||
if instance_value:
|
||||
@@ -120,6 +112,16 @@ class EditProperty(QWidget):
|
||||
value = ""
|
||||
self.widget = QLineEdit(self)
|
||||
self.widget.setText(value)
|
||||
case INTEGER():
|
||||
if not value:
|
||||
value = 1
|
||||
self.widget = QSpinBox()
|
||||
self.widget.setValue(value)
|
||||
case FLOAT():
|
||||
if not value:
|
||||
value = 1.0
|
||||
self.widget = QDoubleSpinBox()
|
||||
self.widget.setValue(value)
|
||||
case TIMESTAMP():
|
||||
self.widget = QDateEdit(self, calendarPopup=True)
|
||||
if not value:
|
||||
@@ -129,12 +131,13 @@ class EditProperty(QWidget):
|
||||
logger.error(f"{column_property} not a supported property.")
|
||||
self.widget = None
|
||||
try:
|
||||
tooltip_text = self.parent().object_type.add_edit_tooltips[self.name]
|
||||
tooltip_text = self.parent().object_type.add_edit_tooltips[self.objectName()]
|
||||
self.widget.setToolTip(tooltip_text)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def parse_form(self):
|
||||
# NOTE: Make sure there's a widget.
|
||||
try:
|
||||
check = self.widget
|
||||
except AttributeError:
|
||||
@@ -146,10 +149,12 @@ class EditProperty(QWidget):
|
||||
value = self.widget.date().toPyDate()
|
||||
case QComboBox():
|
||||
value = self.widget.currentText()
|
||||
case QSpinBox() | QDoubleSpinBox():
|
||||
value = self.widget.value()
|
||||
# if self.is_list:
|
||||
# value = [self.property_class.query(name=prelim)]
|
||||
# else:
|
||||
# value = self.property_class.query(name=prelim)
|
||||
case _:
|
||||
value = None
|
||||
return self.name, value
|
||||
return self.objectName(), value
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from operator import itemgetter
|
||||
"""
|
||||
Provides a screen for managing all attributes of a database object.
|
||||
"""
|
||||
from typing import Any, List
|
||||
from PyQt6.QtCore import QSortFilterProxyModel, Qt
|
||||
from PyQt6.QtGui import QAction, QCursor
|
||||
@@ -49,21 +51,22 @@ class ManagerWindow(QDialog):
|
||||
self.layout.addWidget(self.sub_class, 0, 0)
|
||||
else:
|
||||
self.sub_class = None
|
||||
# self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0)
|
||||
self.options = QComboBox(self)
|
||||
self.options.setObjectName("options")
|
||||
self.update_options()
|
||||
self.setLayout(self.layout)
|
||||
self.setWindowTitle(f"Manage {self.object_type.__name__}")
|
||||
|
||||
def update_options(self):
|
||||
def update_options(self) -> None:
|
||||
"""
|
||||
Changes form inputs based on sample type
|
||||
"""
|
||||
|
||||
if self.sub_class:
|
||||
self.object_type = getattr(db, self.sub_class.currentText())
|
||||
options = [item.name for item in self.object_type.query()]
|
||||
logger.debug(f"self.instance: {self.instance}")
|
||||
if self.instance:
|
||||
options.insert(0, options.pop(options.index(self.instance.name)))
|
||||
self.options.clear()
|
||||
self.options.addItems(options)
|
||||
self.options.setEditable(False)
|
||||
@@ -75,23 +78,34 @@ class ManagerWindow(QDialog):
|
||||
self.add_button.clicked.connect(self.add_new)
|
||||
self.update_data()
|
||||
|
||||
def update_data(self):
|
||||
def update_data(self) -> None:
|
||||
"""
|
||||
Performs updating of widgets on first run and after options change.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
# NOTE: Remove all old widgets.
|
||||
deletes = [item for item in self.findChildren(EditProperty)] + \
|
||||
[item for item in self.findChildren(EditRelationship)] + \
|
||||
[item for item in self.findChildren(QDialogButtonBox)]
|
||||
for item in deletes:
|
||||
item.setParent(None)
|
||||
# NOTE: Find the instance this manager will update
|
||||
self.instance = self.object_type.query(name=self.options.currentText())
|
||||
fields = {k: v for k, v in self.object_type.__dict__.items() if
|
||||
isinstance(v, InstrumentedAttribute) and k != "id"}
|
||||
for key, field in fields.items():
|
||||
# logger.debug(f"Key: {key}, Value: {field}")
|
||||
match field.property:
|
||||
# NOTE: ColumnProperties will be directly edited.
|
||||
case ColumnProperty():
|
||||
# NOTE: field.property.expression.type gives db column type eg. STRING or TIMESTAMP
|
||||
widget = EditProperty(self, key=key, column_type=field.property.expression.type,
|
||||
value=getattr(self.instance, key))
|
||||
# NOTE: RelationshipDeclareds will be given a list of existing related objects.
|
||||
case _RelationshipDeclared():
|
||||
if key != "submissions":
|
||||
# NOTE: field.comparator.entity.class_ gives the relationship class
|
||||
widget = EditRelationship(self, key=key, entity=field.comparator.entity.class_,
|
||||
value=getattr(self.instance, key))
|
||||
else:
|
||||
@@ -100,11 +114,18 @@ class ManagerWindow(QDialog):
|
||||
continue
|
||||
if widget:
|
||||
self.layout.addWidget(widget, self.layout.rowCount(), 0, 1, 2)
|
||||
# NOTE: Add OK|Cancel to bottom of dialog.
|
||||
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
|
||||
|
||||
def parse_form(self):
|
||||
def parse_form(self) -> Any:
|
||||
"""
|
||||
Returns the instance associated with this window.
|
||||
|
||||
Returns:
|
||||
Any: The instance with updated fields.
|
||||
"""
|
||||
# TODO: Need Relationship property here too?
|
||||
results = [item.parse_form() for item in self.findChildren(EditProperty)]
|
||||
# logger.debug(results)
|
||||
for result in results:
|
||||
# logger.debug(result)
|
||||
self.instance.__setattr__(result[0], result[1])
|
||||
@@ -113,9 +134,10 @@ class ManagerWindow(QDialog):
|
||||
def add_new(self):
|
||||
dlg = AddEdit(parent=self, instance=self.object_type(), manager=self.object_type.__name__.lower())
|
||||
if dlg.exec():
|
||||
new_instance = dlg.parse_form()
|
||||
# logger.debug(new_instance.__dict__)
|
||||
new_pyd = dlg.parse_form()
|
||||
new_instance = new_pyd.to_sql()
|
||||
new_instance.save()
|
||||
self.instance = new_instance
|
||||
self.update_options()
|
||||
|
||||
|
||||
@@ -222,10 +244,13 @@ class EditRelationship(QWidget):
|
||||
self.data['id'] = self.data['id'].apply(str)
|
||||
self.data['id'] = self.data['id'].str.zfill(4)
|
||||
except KeyError as e:
|
||||
logger.error(f"Could not alter id to string due to {e}")
|
||||
logger.error(f"Could not alter id to string due to KeyError: {e}")
|
||||
proxy_model = QSortFilterProxyModel()
|
||||
proxy_model.setSourceModel(pandasModel(self.data))
|
||||
self.table.setModel(proxy_model)
|
||||
self.table.resizeColumnsToContents()
|
||||
self.table.resizeRowsToContents()
|
||||
self.table.setSortingEnabled(True)
|
||||
self.table.doubleClicked.connect(self.parse_row)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
Search box that performs fuzzy search for various object types
|
||||
"""
|
||||
from pprint import pformat
|
||||
from typing import Tuple, Any, List
|
||||
from typing import Tuple, Any, List, Generator
|
||||
from pandas import DataFrame
|
||||
from PyQt6.QtCore import QSortFilterProxyModel
|
||||
from PyQt6.QtCore import QSortFilterProxyModel, QModelIndex
|
||||
from PyQt6.QtWidgets import (
|
||||
QLabel, QVBoxLayout, QDialog,
|
||||
QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QDialogButtonBox
|
||||
@@ -74,7 +74,6 @@ class SearchBox(QDialog):
|
||||
search_fields = []
|
||||
for iii, searchable in enumerate(search_fields):
|
||||
widget = FieldSearch(parent=self, label=searchable['label'], field_name=searchable['field'])
|
||||
# widget = FieldSearch(parent=self, label=k, field_name=v)
|
||||
widget.setObjectName(searchable['field'])
|
||||
self.layout.addWidget(widget, 1 + iii, 0)
|
||||
widget.search_widget.textChanged.connect(self.update_data)
|
||||
@@ -100,11 +99,17 @@ class SearchBox(QDialog):
|
||||
# NOTE: Setting results moved to here from __init__ 202411118
|
||||
self.results.setData(df=data)
|
||||
|
||||
def return_selected_rows(self):
|
||||
rows = sorted(set(index.row() for index in
|
||||
self.results.selectedIndexes()))
|
||||
def return_selected_rows(self) -> Generator[dict, None, None]:
|
||||
"""
|
||||
Yields data from selected rows
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of column name: data
|
||||
"""
|
||||
rows = sorted(set(index.row() for index in self.results.selectedIndexes()))
|
||||
for index in rows:
|
||||
output = {column:self.results.model().data(self.results.model().index(index, ii)) for ii, column in enumerate(self.results.data.columns)}
|
||||
output = {column: self.results.model().data(self.results.model().index(index, ii)) for ii, column in
|
||||
enumerate(self.results.data.columns)}
|
||||
yield output
|
||||
|
||||
|
||||
@@ -130,7 +135,13 @@ class FieldSearch(QWidget):
|
||||
"""
|
||||
self.parent().update_data()
|
||||
|
||||
def parse_form(self) -> Tuple:
|
||||
def parse_form(self) -> Tuple[str, str]:
|
||||
"""
|
||||
Gets object name and widget value.
|
||||
|
||||
Returns:
|
||||
Tuple(str, str): Key, value to be used in constructing a dictionary.
|
||||
"""
|
||||
field_value = self.search_widget.text()
|
||||
if field_value == "":
|
||||
field_value = None
|
||||
@@ -147,6 +158,7 @@ class SearchResults(QTableView):
|
||||
self.context = kwargs
|
||||
self.parent = parent
|
||||
self.object_type = object_type
|
||||
|
||||
try:
|
||||
self.extras = extras + self.object_type.searchables
|
||||
except AttributeError:
|
||||
@@ -160,7 +172,8 @@ class SearchResults(QTableView):
|
||||
|
||||
self.data = df
|
||||
try:
|
||||
self.columns_of_interest = [dict(name=item['field'], column=self.data.columns.get_loc(item['field'])) for item in self.extras]
|
||||
self.columns_of_interest = [dict(name=item['field'], column=self.data.columns.get_loc(item['field'])) for
|
||||
item in self.extras]
|
||||
except KeyError:
|
||||
self.columns_of_interest = []
|
||||
try:
|
||||
@@ -171,9 +184,21 @@ class SearchResults(QTableView):
|
||||
proxy_model = QSortFilterProxyModel()
|
||||
proxy_model.setSourceModel(pandasModel(self.data))
|
||||
self.setModel(proxy_model)
|
||||
self.resizeColumnsToContents()
|
||||
self.resizeRowsToContents()
|
||||
self.setSortingEnabled(True)
|
||||
self.doubleClicked.connect(self.parse_row)
|
||||
|
||||
def parse_row(self, x):
|
||||
def parse_row(self, x: QModelIndex) -> None:
|
||||
"""
|
||||
Runs the self.object_type edit from search method for row X.
|
||||
|
||||
Args:
|
||||
x (QModelIndex): Row to be parsed.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
context = {item['name']: x.sibling(x.row(), item['column']).data() for item in self.columns_of_interest}
|
||||
try:
|
||||
object = self.object_type.query(**context)
|
||||
|
||||
@@ -8,9 +8,8 @@ from PyQt6.QtWebChannel import QWebChannel
|
||||
from PyQt6.QtCore import Qt, pyqtSlot
|
||||
from jinja2 import TemplateNotFound
|
||||
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
||||
from tools import is_power_user, jinja_template_loading, timezone
|
||||
from .functions import select_save_file
|
||||
from .misc import save_pdf
|
||||
from tools import is_power_user, jinja_template_loading, timezone, get_application_from_parent
|
||||
from .functions import select_save_file, save_pdf
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from getpass import getuser
|
||||
@@ -30,13 +29,11 @@ class SubmissionDetails(QDialog):
|
||||
def __init__(self, parent, sub: BasicSubmission | BasicSample | Reagent) -> None:
|
||||
|
||||
super().__init__(parent)
|
||||
try:
|
||||
self.app = parent.parent().parent().parent().parent().parent().parent()
|
||||
except AttributeError:
|
||||
self.app = None
|
||||
self.app = get_application_from_parent(parent)
|
||||
self.webview = QWebEngineView(parent=self)
|
||||
self.webview.setMinimumSize(900, 500)
|
||||
self.webview.setMaximumWidth(900)
|
||||
# NOTE: Decide if exporting should be allowed.
|
||||
self.webview.loadFinished.connect(self.activate_export)
|
||||
self.layout = QGridLayout()
|
||||
# NOTE: button to export a pdf version
|
||||
@@ -61,9 +58,16 @@ class SubmissionDetails(QDialog):
|
||||
self.sample_details(sample=sub)
|
||||
case Reagent():
|
||||
self.reagent_details(reagent=sub)
|
||||
# NOTE: Used to maintain javascript functions.
|
||||
self.webview.page().setWebChannel(self.channel)
|
||||
|
||||
def activate_export(self):
|
||||
def activate_export(self) -> None:
|
||||
"""
|
||||
Determines if export pdf should be active.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
title = self.webview.title()
|
||||
self.setWindowTitle(title)
|
||||
if "Submission" in title:
|
||||
@@ -103,6 +107,13 @@ class SubmissionDetails(QDialog):
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def reagent_details(self, reagent: str | Reagent, kit: str | KitType):
|
||||
"""
|
||||
Changes details view to summary of Reagent
|
||||
|
||||
Args:
|
||||
kit (str | KitType): Name of kit.
|
||||
reagent (str | Reagent): Lot number of the reagent
|
||||
"""
|
||||
if isinstance(reagent, str):
|
||||
reagent = Reagent.query(lot=reagent)
|
||||
if isinstance(kit, str):
|
||||
@@ -124,6 +135,17 @@ class SubmissionDetails(QDialog):
|
||||
|
||||
@pyqtSlot(str, str, str)
|
||||
def update_reagent(self, old_lot: str, new_lot: str, expiry: str):
|
||||
"""
|
||||
Designed to allow editing reagent in details view (depreciated)
|
||||
|
||||
Args:
|
||||
old_lot ():
|
||||
new_lot ():
|
||||
expiry ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
expiry = datetime.strptime(expiry, "%Y-%m-%d")
|
||||
reagent = Reagent.query(lot=old_lot)
|
||||
if reagent:
|
||||
@@ -157,7 +179,16 @@ class SubmissionDetails(QDialog):
|
||||
self.webview.setHtml(self.html)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def sign_off(self, submission: str | BasicSubmission):
|
||||
def sign_off(self, submission: str | BasicSubmission) -> None:
|
||||
"""
|
||||
Allows power user to signify a submission is complete.
|
||||
|
||||
Args:
|
||||
submission (str | BasicSubmission): Submission to be completed
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
logger.info(f"Signing off on {submission} - ({getuser()})")
|
||||
if isinstance(submission, str):
|
||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
||||
@@ -183,10 +214,7 @@ class SubmissionComment(QDialog):
|
||||
def __init__(self, parent, submission: BasicSubmission) -> None:
|
||||
|
||||
super().__init__(parent)
|
||||
try:
|
||||
self.app = parent.parent().parent().parent().parent().parent().parent
|
||||
except AttributeError:
|
||||
pass
|
||||
self.app = get_application_from_parent(parent)
|
||||
self.submission = submission
|
||||
self.setWindowTitle(f"{self.submission.rsl_plate_num} Submission Comment")
|
||||
# NOTE: create text field
|
||||
|
||||
@@ -65,12 +65,6 @@ class SubmissionsSheet(QTableView):
|
||||
"""
|
||||
|
||||
def __init__(self, parent) -> None:
|
||||
"""
|
||||
initialize
|
||||
|
||||
Args:
|
||||
ctx (dict): settings passed from gui
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.app = self.parent()
|
||||
self.report = Report()
|
||||
@@ -107,7 +101,9 @@ class SubmissionsSheet(QTableView):
|
||||
Args:
|
||||
event (_type_): the item of interest
|
||||
"""
|
||||
# NOTE: Get current row index
|
||||
id = self.selectionModel().currentIndex()
|
||||
# NOTE: Convert to data in id column (i.e. column 0)
|
||||
id = id.sibling(id.row(), 0).data()
|
||||
submission = BasicSubmission.query(id=id)
|
||||
self.menu = QMenu(self)
|
||||
|
||||
@@ -9,16 +9,15 @@ 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
|
||||
from tools import Report, Result, check_not_nan, main_form_style, report_result, get_application_from_parent
|
||||
from backend.excel.parser import SheetParser
|
||||
from backend.validators import PydSubmission, PydReagent
|
||||
from backend.db import (
|
||||
KitType, Organization, SubmissionType, Reagent,
|
||||
Organization, SubmissionType, Reagent,
|
||||
ReagentRole, KitTypeReagentRoleAssociation, BasicSubmission
|
||||
)
|
||||
from pprint import pformat
|
||||
from .pop_ups import QuestionAsker, AlertPop
|
||||
from .misc import AddReagentForm
|
||||
from .omni_add_edit import AddEdit
|
||||
from typing import List, Tuple
|
||||
from datetime import date
|
||||
@@ -67,7 +66,6 @@ class SubmissionFormContainer(QWidget):
|
||||
def __init__(self, parent: QWidget) -> None:
|
||||
super().__init__(parent)
|
||||
self.app = self.parent().parent()
|
||||
self.report = Report()
|
||||
self.setStyleSheet('background-color: light grey;')
|
||||
self.setAcceptDrops(True)
|
||||
# NOTE: if import_drag is emitted, importSubmission will fire
|
||||
@@ -97,12 +95,12 @@ class SubmissionFormContainer(QWidget):
|
||||
"""
|
||||
self.app.raise_()
|
||||
self.app.activateWindow()
|
||||
self.report = Report()
|
||||
report = Report()
|
||||
self.import_submission_function(fname)
|
||||
return self.report
|
||||
return report
|
||||
|
||||
@report_result
|
||||
def import_submission_function(self, fname: Path | None = None):
|
||||
def import_submission_function(self, fname: Path | None = None) -> Report:
|
||||
"""
|
||||
Import a new submission to the app window
|
||||
|
||||
@@ -110,10 +108,11 @@ class SubmissionFormContainer(QWidget):
|
||||
obj (QMainWindow): original app window
|
||||
|
||||
Returns:
|
||||
Tuple[QMainWindow, dict|None]: Collection of new main app window and result dict
|
||||
Report: Object to give results of import.
|
||||
"""
|
||||
logger.info(f"\n\nStarting Import...\n\n")
|
||||
report = Report()
|
||||
# NOTE: Clear any previous forms.
|
||||
try:
|
||||
self.form.setParent(None)
|
||||
except AttributeError:
|
||||
@@ -141,7 +140,16 @@ class SubmissionFormContainer(QWidget):
|
||||
return report
|
||||
|
||||
@report_result
|
||||
def new_add_reagent(self, instance: Reagent | None = None):
|
||||
def add_reagent(self, instance: Reagent | None = None):
|
||||
"""
|
||||
Action to create new reagent in DB.
|
||||
|
||||
Args:
|
||||
instance (Reagent | None): Blank reagent instance to be edited and then added.
|
||||
|
||||
Returns:
|
||||
models.Reagent: the constructed reagent object to add to submission
|
||||
"""
|
||||
report = Report()
|
||||
if not instance:
|
||||
instance = Reagent()
|
||||
@@ -149,48 +157,12 @@ class SubmissionFormContainer(QWidget):
|
||||
if dlg.exec():
|
||||
reagent = dlg.parse_form()
|
||||
reagent.missing = False
|
||||
# logger.debug(f"Reagent: {reagent}, result: {result}")
|
||||
# report.add_result(result)
|
||||
# NOTE: send reagent to db
|
||||
sqlobj = reagent.to_sql()
|
||||
sqlobj.save()
|
||||
logger.debug(f"Reagent added!")
|
||||
report.add_result(Result(owner=__name__, code=0, msg="New reagent created.", status="Information"))
|
||||
# report.add_result(result)
|
||||
return reagent, report
|
||||
|
||||
@report_result
|
||||
def add_reagent(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
||||
name: str | None = None, kit: str | KitType | None = None) -> Tuple[PydReagent, Report]:
|
||||
"""
|
||||
Action to create new reagent in DB.
|
||||
|
||||
Args:
|
||||
reagent_lot (str | None, optional): Parsed reagent from import form. Defaults to None.
|
||||
reagent_role (str | None, optional): Parsed reagent type from import form. Defaults to None.
|
||||
expiry (date | None, optional): Parsed reagent expiry data. Defaults to None.
|
||||
name (str | None, optional): Parsed reagent name. Defaults to None.
|
||||
|
||||
Returns:
|
||||
models.Reagent: the constructed reagent object to add to submission
|
||||
"""
|
||||
report = Report()
|
||||
if isinstance(reagent_lot, bool):
|
||||
reagent_lot = ""
|
||||
# NOTE: create form
|
||||
dlg = AddReagentForm(reagent_lot=reagent_lot, reagent_role=reagent_role, expiry=expiry, reagent_name=name,
|
||||
kit=kit)
|
||||
if dlg.exec():
|
||||
# NOTE: extract form info
|
||||
info = dlg.parse_form()
|
||||
# NOTE: create reagent object
|
||||
reagent = PydReagent(ctx=self.app.ctx, **info, missing=False)
|
||||
# NOTE: send reagent to db
|
||||
sqlobj = reagent.to_sql()
|
||||
sqlobj.save()
|
||||
# report.add_result(result)
|
||||
return reagent
|
||||
|
||||
|
||||
class SubmissionFormWidget(QWidget):
|
||||
update_reagent_fields = ['extraction_kit']
|
||||
@@ -199,12 +171,12 @@ class SubmissionFormWidget(QWidget):
|
||||
super().__init__(parent)
|
||||
if disable is None:
|
||||
disable = []
|
||||
self.app = parent.app
|
||||
self.app = get_application_from_parent(parent)
|
||||
self.pyd = submission
|
||||
self.missing_info = []
|
||||
self.submission_type = SubmissionType.query(name=self.pyd.submission_type['value'])
|
||||
st = self.submission_type.submission_class
|
||||
defaults = st.get_default_info("form_recover", "form_ignore", submission_type=self.pyd.submission_type['value'])
|
||||
basic_submission_class = self.submission_type.submission_class
|
||||
defaults = basic_submission_class.get_default_info("form_recover", "form_ignore", submission_type=self.pyd.submission_type['value'])
|
||||
self.recover = defaults['form_recover']
|
||||
self.ignore = defaults['form_ignore']
|
||||
self.layout = QVBoxLayout()
|
||||
@@ -225,7 +197,7 @@ class SubmissionFormWidget(QWidget):
|
||||
except KeyError:
|
||||
value = dict(value=None, missing=True)
|
||||
add_widget = self.create_widget(key=k, value=value, submission_type=self.submission_type,
|
||||
sub_obj=st, disable=check)
|
||||
sub_obj=basic_submission_class, disable=check)
|
||||
if add_widget is not None:
|
||||
self.layout.addWidget(add_widget)
|
||||
if k in self.__class__.update_reagent_fields:
|
||||
@@ -302,10 +274,9 @@ 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}")
|
||||
# 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)
|
||||
self.layout.addWidget(add_widget)
|
||||
report.add_result(integrity_report)
|
||||
@@ -340,9 +311,12 @@ class SubmissionFormWidget(QWidget):
|
||||
Returns:
|
||||
List[QWidget]: Widgets matching filter
|
||||
"""
|
||||
query = self.findChildren(QWidget)
|
||||
if object_name is not None:
|
||||
query = [widget for widget in query if widget.objectName() == object_name]
|
||||
if object_name:
|
||||
query = self.findChildren(QWidget, name=object_name)
|
||||
else:
|
||||
query = self.findChildren(QWidget)
|
||||
# if object_name is not None:
|
||||
# query = [widget for widget in query if widget.objectName() == object_name]
|
||||
return query
|
||||
|
||||
@report_result
|
||||
@@ -581,13 +555,13 @@ class SubmissionFormWidget(QWidget):
|
||||
parent.extraction_kit = add_widget.currentText()
|
||||
case 'submission_category':
|
||||
add_widget = MyQComboBox(scrollWidget=parent)
|
||||
cats = ['Diagnostic', "Surveillance", "Research"]
|
||||
cats += [item.name for item in SubmissionType.query()]
|
||||
categories = ['Diagnostic', "Surveillance", "Research"]
|
||||
categories += [item.name for item in SubmissionType.query()]
|
||||
try:
|
||||
cats.insert(0, cats.pop(cats.index(value)))
|
||||
categories.insert(0, categories.pop(categories.index(value)))
|
||||
except ValueError:
|
||||
cats.insert(0, cats.pop(cats.index(submission_type)))
|
||||
add_widget.addItems(cats)
|
||||
categories.insert(0, categories.pop(categories.index(submission_type)))
|
||||
add_widget.addItems(categories)
|
||||
add_widget.setToolTip("Enter submission category or select from list.")
|
||||
case _:
|
||||
if key in sub_obj.timestamps:
|
||||
@@ -655,7 +629,8 @@ class SubmissionFormWidget(QWidget):
|
||||
|
||||
def __init__(self, parent: QWidget, reagent: PydReagent, extraction_kit: str):
|
||||
super().__init__(parent)
|
||||
self.app = self.parent().parent().parent().parent().parent().parent().parent().parent()
|
||||
self.parent = parent
|
||||
self.app = get_application_from_parent(parent)
|
||||
self.reagent = reagent
|
||||
self.extraction_kit = extraction_kit
|
||||
layout = QGridLayout()
|
||||
@@ -684,10 +659,11 @@ class SubmissionFormWidget(QWidget):
|
||||
def disable(self):
|
||||
self.lot.setEnabled(self.check.isChecked())
|
||||
self.label.setEnabled(self.check.isChecked())
|
||||
if not any([item.lot.isEnabled() for item in self.parent().findChildren(self.__class__)]):
|
||||
self.parent().disabler.checkbox.setChecked(False)
|
||||
else:
|
||||
self.parent().disabler.checkbox.setChecked(True)
|
||||
with QSignalBlocker(self.parent.disabler.checkbox) as blocker:
|
||||
if any([item.lot.isEnabled() for item in self.parent.findChildren(self.__class__)]):
|
||||
self.parent.disabler.checkbox.setChecked(True)
|
||||
else:
|
||||
self.parent.disabler.checkbox.setChecked(False)
|
||||
|
||||
@report_result
|
||||
def parse_form(self) -> Tuple[PydReagent | None, Report]:
|
||||
@@ -703,31 +679,23 @@ class SubmissionFormWidget(QWidget):
|
||||
lot = self.lot.currentText()
|
||||
wanted_reagent, new = Reagent.query_or_create(lot=lot, role=self.reagent.role, expiry=self.reagent.expiry)
|
||||
# NOTE: if reagent doesn't exist in database, offer to add it (uses App.add_reagent)
|
||||
logger.debug(f"Wanted reagent: {wanted_reagent}, New: {new}")
|
||||
# if wanted_reagent is None:
|
||||
if new:
|
||||
dlg = QuestionAsker(title=f"Add {lot}?",
|
||||
message=f"Couldn't find reagent type {self.reagent.role}: {lot} in the database.\n\nWould you like to add it?")
|
||||
|
||||
if dlg.exec():
|
||||
wanted_reagent = self.parent().parent().new_add_reagent(instance=wanted_reagent)
|
||||
logger.debug(f"Reagent added!")
|
||||
report.add_result(Result(owner=__name__, code=0, msg="New reagent created.", status="Information"))
|
||||
return wanted_reagent, report
|
||||
else:
|
||||
# NOTE: In this case we will have an empty reagent and the submission will fail kit integrity check
|
||||
|
||||
return None, report
|
||||
else:
|
||||
# NOTE: 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.
|
||||
# NOTE: 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 = ReagentRole.query(name=self.reagent.role)
|
||||
logger.debug(f"Reagent role: {rt}")
|
||||
if rt is None:
|
||||
rt = ReagentRole.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
||||
final = PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||
expiry=wanted_reagent.expiry.date(), missing=False)
|
||||
logger.debug(f"Final Reagent: {final}")
|
||||
return final, report
|
||||
|
||||
def updated(self):
|
||||
@@ -781,10 +749,11 @@ class SubmissionFormWidget(QWidget):
|
||||
looked_up_reg = None
|
||||
if looked_up_reg:
|
||||
try:
|
||||
relevant_reagents.remove(str(looked_up_reg.lot))
|
||||
# relevant_reagents.remove(str(looked_up_reg.lot))
|
||||
relevant_reagents.insert(0, relevant_reagents.pop(relevant_reagents.index(looked_up_reg.lot)))
|
||||
except ValueError as e:
|
||||
logger.error(f"Error reordering relevant reagents: {e}")
|
||||
relevant_reagents.insert(0, str(looked_up_reg.lot))
|
||||
# relevant_reagents.insert(0, str(looked_up_reg.lot))
|
||||
else:
|
||||
if len(relevant_reagents) > 1:
|
||||
idx = relevant_reagents.index(str(reagent.lot))
|
||||
|
||||
@@ -32,7 +32,13 @@ class Summary(InfoPane):
|
||||
self.update_data()
|
||||
|
||||
|
||||
def update_data(self):
|
||||
def update_data(self) -> None:
|
||||
"""
|
||||
Sets data in the info pane
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
super().update_data()
|
||||
orgs = [self.org_select.itemText(i) for i in range(self.org_select.count()) if self.org_select.itemChecked(i)]
|
||||
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
|
||||
|
||||
@@ -31,7 +31,13 @@ class TurnaroundTime(InfoPane):
|
||||
self.submission_typer.currentTextChanged.connect(self.update_data)
|
||||
self.update_data()
|
||||
|
||||
def update_data(self):
|
||||
def update_data(self) -> None:
|
||||
"""
|
||||
Sets data in the info pane
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
super().update_data()
|
||||
months = self.diff_month(self.start_date, self.end_date)
|
||||
chart_settings = dict(start_date=self.start_date, end_date=self.end_date)
|
||||
@@ -47,4 +53,4 @@ class TurnaroundTime(InfoPane):
|
||||
else:
|
||||
threshold = None
|
||||
self.fig = TurnaroundChart(df=self.report_obj.df, settings=chart_settings, modes=[], threshold=threshold, months=months)
|
||||
self.webview.setHtml(self.fig.to_html())
|
||||
self.webview.setHtml(self.fig.html)
|
||||
|
||||
Reference in New Issue
Block a user