Context menu for runs working.

This commit is contained in:
lwark
2025-05-22 10:00:25 -05:00
parent 75c665ea05
commit d850166e08
40 changed files with 2852 additions and 3329 deletions

View File

@@ -13,7 +13,7 @@ from PyQt6.QtGui import QAction
from pathlib import Path
from markdown import markdown
from pandas import ExcelWriter
from backend import Reagent, BasicSample, Organization, KitType, BasicRun
from backend import Reagent, Sample, ClientSubmission, KitType, Run
from tools import (
check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user,
under_development
@@ -22,7 +22,7 @@ from .date_type_picker import DateTypePicker
from .functions import select_save_file
from .pop_ups import HTMLPop
from .misc import Pagifier
from .submission_table import SubmissionsSheet, SubmissionsTree, ClientRunModel
from .submission_table import SubmissionsSheet, SubmissionsTree, ClientSubmissionRunModel
from .submission_widget import SubmissionFormContainer
from .controls_chart import ControlsViewer
from .summary import Summary
@@ -30,7 +30,7 @@ from .turnaround import TurnaroundTime
from .concentrations import Concentrations
from .omni_search import SearchBox
logger = logging.getLogger(f'submissions.{__name__}')
logger = logging.getLogger(f'procedure.{__name__}')
class App(QMainWindow):
@@ -57,7 +57,7 @@ class App(QMainWindow):
# NOTE: insert tabs into main app
self.table_widget = AddSubForm(self)
self.setCentralWidget(self.table_widget)
# NOTE: run initial setups
# NOTE: procedure initial setups
self._createActions()
self._createMenuBar()
self._createToolBar()
@@ -173,14 +173,14 @@ class App(QMainWindow):
def runSampleSearch(self):
"""
Create a search for samples.
Create a search for sample.
"""
dlg = SearchBox(self, object_type=BasicSample, extras=[])
dlg = SearchBox(self, object_type=Sample, 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 = SearchBox(parent=self, object_type=Reagent, extras=[dict(name='Role', field="reagentrole")])
dlg.exec()
def update_data(self):
@@ -239,7 +239,7 @@ class AddSubForm(QWidget):
self.tabs.addTab(self.tab3, "PCR Controls")
self.tabs.addTab(self.tab4, "Cost Report")
self.tabs.addTab(self.tab5, "Turnaround Times")
# NOTE: Create run adder form
# NOTE: Create procedure adder form
self.formwidget = SubmissionFormContainer(self)
self.formlayout = QVBoxLayout(self)
self.formwidget.setLayout(self.formlayout)
@@ -249,12 +249,12 @@ class AddSubForm(QWidget):
self.interior.setWidgetResizable(True)
self.interior.setFixedWidth(325)
self.interior.setWidget(self.formwidget)
# NOTE: Create sheet to hold existing submissions
# NOTE: Create sheet to hold existing procedure
self.sheetwidget = QWidget(self)
self.sheetlayout = QVBoxLayout(self)
self.sheetwidget.setLayout(self.sheetlayout)
# self.sub_wid = SubmissionsSheet(parent=parent)
self.sub_wid = SubmissionsTree(parent=parent, model=ClientRunModel(self))
self.sub_wid = SubmissionsTree(parent=parent, model=ClientSubmissionRunModel(self))
self.pager = Pagifier(page_max=self.sub_wid.total_count / page_size)
self.sheetlayout.addWidget(self.sub_wid)
self.sheetlayout.addWidget(self.pager)
@@ -264,11 +264,13 @@ class AddSubForm(QWidget):
self.tab1.layout.addWidget(self.interior)
self.tab1.layout.addWidget(self.sheetwidget)
self.tab2.layout = QVBoxLayout(self)
self.irida_viewer = ControlsViewer(self, archetype="Irida Control")
# self.irida_viewer = ControlsViewer(self, archetype="Irida Control")
self.irida_viewer = None
self.tab2.layout.addWidget(self.irida_viewer)
self.tab2.setLayout(self.tab2.layout)
self.tab3.layout = QVBoxLayout(self)
self.pcr_viewer = ControlsViewer(self, archetype="PCR Control")
# self.pcr_viewer = ControlsViewer(self, archetype="PCR Control")
self.pcr_viewer = None
self.tab3.layout.addWidget(self.pcr_viewer)
self.tab3.setLayout(self.tab3.layout)
summary_report = Summary(self)

View File

@@ -7,7 +7,7 @@ from PyQt6.QtWidgets import (
)
from PyQt6.QtCore import QSignalBlocker
from backend import ChartReportMaker
from backend.db import ControlType, IridaControl
from backend.db import ControlType
import logging
from tools import Report, report_result
from frontend.visualizations import CustomFigure
@@ -25,7 +25,7 @@ class ControlsViewer(InfoPane):
return
# NOTE: set tab2 layout
self.control_sub_typer = QComboBox()
# NOTE: fetch types of controls
# NOTE: fetch types of control
con_sub_types = [item for item in self.archetype.targets.keys()]
self.control_sub_typer.addItems(con_sub_types)
# NOTE: create custom widget to get types of analysis -- disabled by PCR control
@@ -52,7 +52,7 @@ class ControlsViewer(InfoPane):
@report_result
def update_data(self, *args, **kwargs):
"""
Get controls based on start/end dates
Get control based on start/end dates
"""
super().update_data()
# NOTE: mode_sub_type defaults to disabled
@@ -70,7 +70,7 @@ class ControlsViewer(InfoPane):
sub_types = []
# NOTE: added in allowed to have subtypes in case additions made in future.
if sub_types and self.mode.lower() in self.archetype.instance_class.subtyping_allowed:
# NOTE: block signal that will rerun controls getter and update mode_sub_typer
# NOTE: block signal that will rerun control getter and update mode_sub_typer
with QSignalBlocker(self.mode_sub_typer) as blocker:
self.mode_sub_typer.addItems(sub_types)
self.mode_sub_typer.setEnabled(True)
@@ -83,7 +83,7 @@ class ControlsViewer(InfoPane):
@report_result
def chart_maker_function(self, *args, **kwargs):
"""
Create html chart for controls reporting
Create html chart for control reporting
Args:
obj (QMainWindow): original app window
@@ -98,7 +98,7 @@ class ControlsViewer(InfoPane):
else:
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
# NOTE: query all control using the type/start and end dates from the gui
chart_settings = dict(
sub_type=self.con_sub_type,
start_date=self.start_date,

View File

@@ -6,7 +6,7 @@ from PyQt6.QtCore import Qt, QSignalBlocker
from PyQt6.QtWidgets import (
QDialog, QComboBox, QCheckBox, QLabel, QWidget, QVBoxLayout, QDialogButtonBox, QGridLayout
)
from backend.db.models import Equipment, BasicRun, Process
from backend.db.models import Equipment, Run, Process, Procedure
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
import logging
from typing import Generator
@@ -16,13 +16,13 @@ logger = logging.getLogger(f"submissions.{__name__}")
class EquipmentUsage(QDialog):
def __init__(self, parent, submission: BasicRun):
def __init__(self, parent, procedure: Procedure):
super().__init__(parent)
self.submission = submission
self.setWindowTitle(f"Equipment Checklist - {submission.rsl_plate_num}")
self.used_equipment = self.submission.used_equipment
self.kit = self.submission.extraction_kit
self.opt_equipment = submission.submission_type.get_equipment()
self.procedure = procedure
self.setWindowTitle(f"Equipment Checklist - {procedure.rsl_plate_num}")
self.used_equipment = self.procedure.equipment
self.kit = self.procedure.kittype
self.opt_equipment = procedure.proceduretype.get_equipment()
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.populate_form()
@@ -120,7 +120,7 @@ class RoleComboBox(QWidget):
def update_processes(self):
"""
Changes processes when equipment is changed
Changes process when equipment is changed
"""
equip = self.box.currentText()
equip2 = next((item for item in self.role.equipment if item.name == equip), self.role.equipment[0])
@@ -134,10 +134,10 @@ class RoleComboBox(QWidget):
"""
process = self.process.currentText().strip()
process = Process.query(name=process)
if process.tip_roles:
for iii, tip_role in enumerate(process.tip_roles):
if process.tiprole:
for iii, tip_role in enumerate(process.tiprole):
widget = QComboBox()
tip_choices = [item.name for item in tip_role.controls]
tip_choices = [item.name for item in tip_role.control]
widget.setEditable(False)
widget.addItems(tip_choices)
widget.setObjectName(f"tips_{tip_role.name}")

View File

@@ -12,7 +12,7 @@ import logging, numpy as np
from pprint import pformat
from typing import Tuple, List
from pathlib import Path
from backend.db.models import BasicRun
from backend.db.models import Run
logger = logging.getLogger(f"submissions.{__name__}")
@@ -20,7 +20,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
# Main window class
class GelBox(QDialog):
def __init__(self, parent, img_path: str | Path, submission: BasicRun):
def __init__(self, parent, img_path: str | Path, submission: Run):
super().__init__(parent)
# NOTE: setting title
self.setWindowTitle(f"Gel - {img_path}")
@@ -135,7 +135,7 @@ class ControlsForm(QWidget):
def parse_form(self) -> Tuple[List[dict], str]:
"""
Pulls the controls statuses from the form.
Pulls the control statuses from the form.
Returns:
List[dict]: output of values

View File

@@ -39,7 +39,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 control 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()

View File

@@ -19,7 +19,7 @@ env = jinja_template_loading()
class StartEndDatePicker(QWidget):
"""
custom widget to pick start and end dates for controls graphs
custom widget to pick start and end dates for control graphs
"""
def __init__(self, default_start: int) -> None:

View File

@@ -71,7 +71,7 @@ class AddEdit(QDialog):
# logger.debug(f"We have an elastic model.")
parsed['instance'] = self.instance
# 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.
# NOTE: Also, why am I not just using the toSQL method here. I could write one for contact.
model = model(**parsed)
return model, report

View File

@@ -144,7 +144,7 @@ class ManagerWindow(QDialog):
def update_data(self) -> None:
"""
Performs updating of widgets on first run and after options change.
Performs updating of widgets on first procedure and after options change.
Returns:
None

View File

@@ -100,7 +100,7 @@ class SearchBox(QDialog):
def update_data(self):
"""
Shows dataframe of relevant samples.
Shows dataframe of relevant sample.
"""
fields = self.parse_form()
sample_list_creator = self.object_type.fuzzy_search(**fields)

View File

@@ -1,4 +1,3 @@
import logging
from pathlib import Path
from typing import List
@@ -6,9 +5,12 @@ from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout
from backend.validators import PydSubmission
from backend.db.models import ClientSubmission
from backend.validators import PydSample, RSLNamer
from tools import get_application_from_parent, jinja_template_loading
env = jinja_template_loading()
logger = logging.getLogger(f"submissions.{__name__}")
@@ -16,9 +18,13 @@ logger = logging.getLogger(f"submissions.{__name__}")
class SampleChecker(QDialog):
def __init__(self, parent, title:str, pyd: PydSubmission):
def __init__(self, parent, title: str, samples: List[PydSample], clientsubmission: ClientSubmission|None=None):
super().__init__(parent)
self.pyd = pyd
if clientsubmission:
self.rsl_plate_num = RSLNamer.construct_new_plate_name(clientsubmission.to_dict())
else:
self.rsl_plate_num = clientsubmission
self.samples = samples
self.setWindowTitle(title)
self.app = get_application_from_parent(parent)
self.webview = QWebEngineView(parent=self)
@@ -36,9 +42,10 @@ class SampleChecker(QDialog):
css = f.read()
try:
samples = self.formatted_list
except AttributeError:
except AttributeError as e:
logger.error(f"Problem getting sample list: {e}")
samples = []
html = template.render(samples=samples, css=css)
html = template.render(samples=samples, css=css, rsl_plate_num=self.rsl_plate_num)
self.webview.setHtml(html)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
@@ -51,25 +58,37 @@ class SampleChecker(QDialog):
@pyqtSlot(str, str, str)
def text_changed(self, submission_rank: str, key: str, new_value: str):
logger.debug(f"Name: {submission_rank}, Key: {key}, Value: {new_value}")
match key:
case "row" | "column":
value = [new_value]
case _:
value = new_value
try:
item = next((sample for sample in self.pyd.samples if int(submission_rank) in sample.submission_rank))
item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank))
except StopIteration:
logger.error(f"Unable to find sample {submission_rank}")
return
item.__setattr__(key, value)
item.__setattr__(key, new_value)
@pyqtSlot(str, bool)
def enable_sample(self, submission_rank: str, enabled: bool):
logger.debug(f"Name: {submission_rank}, Enabled: {enabled}")
try:
item = next((sample for sample in self.samples if int(submission_rank) == sample.submission_rank))
except StopIteration:
logger.error(f"Unable to find sample {submission_rank}")
return
item.__setattr__("enabled", enabled)
@pyqtSlot(str)
def set_rsl_plate_num(self, rsl_plate_num: str):
logger.debug(f"RSL plate num: {rsl_plate_num}")
self.rsl_plate_num = rsl_plate_num
@property
def formatted_list(self) -> List[dict]:
output = []
for sample in self.pyd.sample_list:
if sample['submitter_id'] in [item['submitter_id'] for item in output]:
sample['color'] = "red"
for sample in self.samples:
logger.debug(sample)
s = sample.improved_dict(dictionaries=False)
if s['sample_id'] in [item['sample_id'] for item in output]:
s['color'] = "red"
else:
sample['color'] = "black"
output.append(sample)
s['color'] = "black"
output.append(s)
return output

View File

@@ -1,5 +1,5 @@
"""
Webview to show run and sample details.
Webview to show procedure and sample details.
"""
from PyQt6.QtWidgets import (QDialog, QPushButton, QVBoxLayout,
QDialogButtonBox, QTextEdit, QGridLayout)
@@ -7,7 +7,7 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtCore import Qt, pyqtSlot
from jinja2 import TemplateNotFound
from backend.db.models import BasicRun, BasicSample, Reagent, KitType, Equipment, Process, Tips
from backend.db.models import Run, Sample, Reagent, KitType, Equipment, Process, Tips
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
@@ -18,15 +18,15 @@ from pprint import pformat
from typing import List
logger = logging.getLogger(f"submissions.{__name__}")
logger = logging.getLogger(f"procedure.{__name__}")
class SubmissionDetails(QDialog):
"""
a window showing text details of run
a window showing text details of procedure
"""
def __init__(self, parent, sub: BasicRun | BasicSample | Reagent) -> None:
def __init__(self, parent, sub: Run | Sample | Reagent) -> None:
super().__init__(parent)
self.app = get_application_from_parent(parent)
@@ -51,10 +51,10 @@ class SubmissionDetails(QDialog):
self.channel = QWebChannel()
self.channel.registerObject('backend', self)
match sub:
case BasicRun():
case Run():
self.run_details(run=sub)
self.rsl_plate_num = sub.rsl_plate_num
case BasicSample():
case Sample():
self.sample_details(sample=sub)
case Reagent():
self.reagent_details(reagent=sub)
@@ -127,7 +127,7 @@ class SubmissionDetails(QDialog):
self.setWindowTitle(f"Process Details - {tips.name}")
@pyqtSlot(str)
def sample_details(self, sample: str | BasicSample):
def sample_details(self, sample: str | Sample):
"""
Changes details view to summary of Sample
@@ -136,19 +136,19 @@ class SubmissionDetails(QDialog):
"""
logger.debug(f"Sample details.")
if isinstance(sample, str):
sample = BasicSample.query(submitter_id=sample)
sample = Sample.query(sample_id=sample)
base_dict = sample.to_sub_dict(full_data=True)
exclude = ['submissions', 'excluded', 'colour', 'tooltip']
exclude = ['procedure', 'excluded', 'colour', 'tooltip']
base_dict['excluded'] = exclude
template = sample.details_template
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read()
html = template.render(sample=base_dict, css=css)
# with open(f"{sample.submitter_id}.html", 'w') as f:
# with open(f"{sample.sample_id}.html", 'w') as f:
# f.write(html)
self.webview.setHtml(html)
self.setWindowTitle(f"Sample Details - {sample.submitter_id}")
self.setWindowTitle(f"Sample Details - {sample.sample_id}")
@pyqtSlot(str, str)
def reagent_details(self, reagent: str | Reagent, kit: str | KitType):
@@ -156,7 +156,7 @@ class SubmissionDetails(QDialog):
Changes details view to summary of Reagent
Args:
kit (str | KitType): Name of kit.
kit (str | KitType): Name of kittype.
reagent (str | Reagent): Lot number of the reagent
"""
logger.debug(f"Reagent details.")
@@ -164,7 +164,7 @@ class SubmissionDetails(QDialog):
reagent = Reagent.query(lot=reagent)
if isinstance(kit, str):
self.kit = KitType.query(name=kit)
base_dict = reagent.to_sub_dict(extraction_kit=self.kit, full_data=True)
base_dict = reagent.to_sub_dict(kittype=self.kit, full_data=True)
env = jinja_template_loading()
temp_name = "reagent_details.html"
try:
@@ -203,7 +203,7 @@ class SubmissionDetails(QDialog):
logger.error(f"Reagent with lot {old_lot} not found.")
@pyqtSlot(str)
def run_details(self, run: str | BasicRun):
def run_details(self, run: str | Run):
"""
Sets details view to summary of Submission.
@@ -212,24 +212,24 @@ class SubmissionDetails(QDialog):
"""
logger.debug(f"Submission details.")
if isinstance(run, str):
run = BasicRun.query(rsl_plate_num=run)
run = Run.query(name=run)
self.rsl_plate_num = run.rsl_plate_num
self.base_dict = run.to_dict(full_data=True)
# NOTE: don't want id
self.base_dict['platemap'] = run.make_plate_map(sample_list=run.hitpicked)
self.base_dict['excluded'] = run.get_default_info("details_ignore")
self.base_dict, self.template = run.get_details_template(base_dict=self.base_dict)
self.template = run.details_template
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read()
# logger.debug(f"Base dictionary of run {self.rsl_plate_num}: {pformat(self.base_dict)}")
# logger.debug(f"Base dictionary of procedure {self.name}: {pformat(self.base_dict)}")
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
self.webview.setHtml(self.html)
@pyqtSlot(str)
def sign_off(self, run: str | BasicRun) -> None:
def sign_off(self, run: str | Run) -> None:
"""
Allows power user to signify a run is complete.
Allows power user to signify a procedure is complete.
Args:
run (str | BasicRun): Submission to be completed
@@ -239,7 +239,7 @@ class SubmissionDetails(QDialog):
"""
logger.info(f"Signing off on {run} - ({getuser()})")
if isinstance(run, str):
run = BasicRun.query(rsl_plate_num=run)
run = Run.query(name=run)
run.signed_by = getuser()
run.completed_date = datetime.now()
run.completed_date.replace(tzinfo=timezone)
@@ -248,7 +248,7 @@ class SubmissionDetails(QDialog):
def save_pdf(self):
"""
Renders run to html, then creates and saves .pdf file to user selected file.
Renders procedure to html, then creates and saves .pdf file to user selected file.
"""
fname = select_save_file(obj=self, default_name=self.export_plate, extension="pdf")
save_pdf(obj=self.webview, filename=fname)
@@ -256,11 +256,11 @@ class SubmissionDetails(QDialog):
class SubmissionComment(QDialog):
"""
a window for adding comment text to a run
a window for adding comment text to a procedure
"""
def __init__(self, parent, submission: BasicRun) -> None:
def __init__(self, parent, submission: Run) -> None:
logger.debug(parent)
super().__init__(parent)
self.app = get_application_from_parent(parent)
self.submission = submission
@@ -282,7 +282,7 @@ class SubmissionComment(QDialog):
def parse_form(self) -> List[dict]:
"""
Adds comment to run object.
Adds comment to procedure object.
"""
commenter = getuser()
comment = self.txt_editor.toPlainText()

View File

@@ -1,18 +1,20 @@
"""
Contains widgets specific to the run summary and run details.
Contains widgets specific to the procedure summary and procedure details.
"""
import logging
import sys
import sys, logging, re
from pprint import pformat
from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, QStyle, QStyleOptionViewItem, \
QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor
from backend.db.models import BasicRun, ClientSubmission
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor, QContextMenuEvent
from backend.db.models import Run, ClientSubmission
from tools import Report, Result, report_result
from .functions import select_open_file
logger = logging.getLogger(f"submissions.{__name__}")
logger = logging.getLogger(f"procedure.{__name__}")
class pandasModel(QAbstractTableModel):
@@ -63,7 +65,7 @@ class pandasModel(QAbstractTableModel):
class SubmissionsSheet(QTableView):
"""
presents run summary to user in tab1
presents procedure summary to user in tab1
"""
def __init__(self, parent) -> None:
@@ -78,16 +80,16 @@ class SubmissionsSheet(QTableView):
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.setSortingEnabled(True)
self.doubleClicked.connect(lambda x: BasicRun.query(id=x.sibling(x.row(), 0).data()).show_details(self))
# NOTE: Have to run native query here because mine just returns results?
self.total_count = BasicRun.__database_session__.query(BasicRun).count()
self.doubleClicked.connect(lambda x: Run.query(id=x.sibling(x.row(), 0).data()).show_details(self))
# NOTE: Have to procedure native query here because mine just returns results?
self.total_count = Run.__database_session__.query(Run).count()
def set_data(self, page: int = 1, page_size: int = 250) -> None:
"""
sets data in model
"""
# self.data = ClientSubmission.submissions_to_df(page=page, page_size=page_size)
self.data = BasicRun.submissions_to_df(page=page, page_size=page_size)
self.data = Run.submissions_to_df(page=page, page_size=page_size)
try:
self.data['Id'] = self.data['Id'].apply(str)
self.data['Id'] = self.data['Id'].str.zfill(4)
@@ -108,7 +110,7 @@ class SubmissionsSheet(QTableView):
id = self.selectionModel().currentIndex()
# NOTE: Convert to data in id column (i.e. column 0)
id = id.sibling(id.row(), 0).data()
submission = BasicRun.query(id=id)
submission = Run.query(id=id)
self.menu = QMenu(self)
self.con_actions = submission.custom_context_events()
for k in self.con_actions.keys():
@@ -140,7 +142,7 @@ class SubmissionsSheet(QTableView):
def link_extractions_function(self):
"""
Link extractions from runlogs to imported submissions
Link extractions from runlogs to imported procedure
Args:
obj (QMainWindow): original app window
@@ -166,9 +168,9 @@ class SubmissionsSheet(QTableView):
# NOTE: elution columns are item 6 in the comma split list to the end
for ii in range(6, len(run)):
new_run[f"column{str(ii - 5)}_vol"] = run[ii]
# NOTE: Lookup imported submissions
sub = BasicRun.query(rsl_plate_num=new_run['rsl_plate_num'])
# NOTE: If no such run exists, move onto the next run
# NOTE: Lookup imported procedure
sub = Run.query(name=new_run['name'])
# NOTE: If no such procedure exists, move onto the next procedure
if sub is None:
continue
try:
@@ -192,7 +194,7 @@ class SubmissionsSheet(QTableView):
def link_pcr_function(self):
"""
Link PCR data from run logs to an imported run
Link PCR data from procedure logs to an imported procedure
Args:
obj (QMainWindow): original app window
@@ -215,9 +217,9 @@ class SubmissionsSheet(QTableView):
experiment_name=run[4].strip(),
end_time=run[5].strip()
)
# NOTE: lookup imported run
sub = BasicRun.query(rsl_number=new_run['rsl_plate_num'])
# NOTE: if imported run doesn't exist move on to next run
# NOTE: lookup imported procedure
sub = Run.query(rsl_number=new_run['name'])
# NOTE: if imported procedure doesn't exist move on to next procedure
if sub is None:
continue
sub.set_attribute('pcr_info', new_run)
@@ -227,9 +229,10 @@ class SubmissionsSheet(QTableView):
return report
class RunDelegate(QStyledItemDelegate):
class ClientSubmissionDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(RunDelegate, self).__init__(parent)
super(ClientSubmissionDelegate, self).__init__(parent)
pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
icon1 = QWidget().style().standardIcon(pixmapi)
pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton
@@ -238,23 +241,29 @@ class RunDelegate(QStyledItemDelegate):
self._minus_icon = icon2
def initStyleOption(self, option, index):
super(RunDelegate, self).initStyleOption(option, index)
super(ClientSubmissionDelegate, self).initStyleOption(option, index)
if not index.parent().isValid():
is_open = bool(option.state & QStyle.StateFlag.State_Open)
option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration
option.icon = self._minus_icon if is_open else self._plus_icon
class RunDelegate(ClientSubmissionDelegate):
pass
class SubmissionsTree(QTreeView):
"""
https://stackoverflow.com/questions/54385437/how-can-i-make-a-table-that-can-collapse-its-rows-into-categories-in-qt
"""
def __init__(self, model, parent=None):
super(SubmissionsTree, self).__init__(parent)
self.total_count = ClientSubmission.__database_session__.query(ClientSubmission).count()
self.setIndentation(0)
self.setExpandsOnDoubleClick(False)
self.clicked.connect(self.on_clicked)
delegate = RunDelegate(self)
delegate = ClientSubmissionDelegate(self)
self.setItemDelegateForColumn(0, delegate)
self.model = model
self.setModel(self.model)
@@ -263,32 +272,69 @@ class SubmissionsTree(QTreeView):
# self.setStyleSheet("background-color: #0D1225;")
self.set_data()
self.doubleClicked.connect(self.show_details)
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
# self.customContextMenuRequested.connect(self.open_menu)
for ii in range(2):
self.resizeColumnToContents(ii)
@pyqtSlot(QModelIndex)
def on_clicked(self, index):
if not index.parent().isValid() and index.column() == 0:
self.setExpanded(index, not self.isExpanded(index))
def contextMenuEvent(self, event: QContextMenuEvent):
"""
Creates actions for right click menu events.
Args:
event (_type_): the item of interest
"""
indexes = self.selectedIndexes()
dicto = next((item.data(1) for item in indexes if item.data(1)))
query_obj = dicto['item_type'].query(name=dicto['query_str'], limit=1)
logger.debug(query_obj)
# NOTE: Convert to data in id column (i.e. column 0)
# id = id.sibling(id.row(), 0).data()
# logger.debug(id.model().query_group_object(id.row()))
# clientsubmission = id.model().query_group_object(id.row())
self.menu = QMenu(self)
self.con_actions = query_obj.custom_context_events
for key in self.con_actions.keys():
if key.lower() == "add procedure":
action = QMenu(self.menu)
action.setTitle("Add Procedure")
for procedure in query_obj.allowed_procedures:
proc_name = procedure.name
proc = QAction(proc_name, action)
proc.triggered.connect(lambda _, procedure_name=proc_name: self.con_actions['Add Procedure'](obj=self, proceduretype_name=procedure_name))
action.addAction(proc)
self.menu.addMenu(action)
else:
action = QAction(key, self)
action.triggered.connect(lambda _, action_name=key: self.con_actions[action_name](obj=self))
self.menu.addAction(action)
# # NOTE: add other required actions
self.menu.popup(QCursor.pos())
def set_data(self, page: int = 1, page_size: int = 250) -> None:
"""
sets data in model
"""
self.clear()
# self.data = ClientSubmission.submissions_to_df(page=page, page_size=page_size)
self.data = [item.to_dict(full_data=True) for item in ClientSubmission.query(chronologic=True, page=page, page_size=page_size)]
logger.debug(pformat(self.data))
self.data = [item.to_dict(full_data=True) for item in
ClientSubmission.query(chronologic=True, page=page, page_size=page_size)]
logger.debug(f"setting data:\n {pformat(self.data)}")
# sys.exit()
for submission in self.data:
group_str = f"{submission['submission_type']}-{submission['submitter_plate_number']}-{submission['submitted_date']}"
group_item = self.model.add_group(group_str)
for run in submission['runs']:
group_str = f"{submission['submissiontype']}-{submission['submitter_plate_id']}-{submission['submitted_date']}"
group_item = self.model.add_group(group_str, query_str=submission['submitter_plate_id'])
for run in submission['run']:
self.model.append_element_to_group(group_item=group_item, element=run)
def clear(self):
if self.model != None:
# self.model.clear() # works
@@ -302,8 +348,7 @@ class SubmissionsTree(QTreeView):
id = int(id.data())
except ValueError:
return
BasicRun.query(id=id).show_details(self)
Run.query(id=id).show_details(self)
def link_extractions(self):
pass
@@ -312,62 +357,64 @@ class SubmissionsTree(QTreeView):
pass
class ClientRunModel(QStandardItemModel):
class ClientSubmissionRunModel(QStandardItemModel):
def __init__(self, parent=None):
super(ClientRunModel, self).__init__(parent)
headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Technician", "Signed By"]
super(ClientSubmissionRunModel, self).__init__(parent)
headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"]
self.setColumnCount(len(headers))
self.setHorizontalHeaderLabels(headers)
for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i)
try:
logger.debug(it.text())
except AttributeError:
pass
# it.setForeground(QColor("#F2F2F2"))
def add_group(self, group_name):
def add_group(self, item_name, query_str: str):
item_root = QStandardItem()
item_root.setEditable(False)
item = QStandardItem(group_name)
item = QStandardItem(item_name)
item.setEditable(False)
ii = self.invisibleRootItem()
i = ii.rowCount()
for j, it in enumerate((item_root, item)):
# NOTE: Adding item to invisible root row i, column j (wherever j comes from)
ii.setChild(i, j, it)
ii.setEditable(False)
for j in range(self.columnCount()):
it = ii.child(i, j)
if it is None:
# NOTE: Set invisible root child to empty if it is None.
it = QStandardItem()
ii.setChild(i, j, it)
# it.setBackground(QColor("#002842"))
# it.setForeground(QColor("#F2F2F2"))
item_root.setData(dict(item_type=ClientSubmission, query_str=query_str), 1)
return item_root
def append_element_to_group(self, group_item, element:dict):
logger.debug(f"Element: {pformat(element)}")
def append_element_to_group(self, group_item, element: dict):
# logger.debug(f"Element: {pformat(element)}")
j = group_item.rowCount()
item_icon = QStandardItem()
item_icon.setEditable(False)
# item_icon.setBackground(QColor("#0D1225"))
# item_icon.setData(dict(item_type="Run", query_str=element['plate_number']), 1)
# group_item.setChild(j, 0, item_icon)
for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i)
try:
key = it.text().lower().replace(" ", "_")
except AttributeError:
continue
key = None
if not key:
continue
value = str(element[key])
item = QStandardItem(value)
item.setBackground(QColor("#CFE2F3"))
item.setEditable(False)
# item_icon.setChild(j, i, item)
item.setData(dict(item_type=Run, query_str=element['plate_number']),1)
group_item.setChild(j, i, item)
# group_item.setChild(j, 1, QStandardItem("B"))
def get_value(self, idx: int, column: int = 1):
return self.item(idx, column)
def query_group_object(self, idx: int):
row_obj = self.get_value(idx)
logger.debug(row_obj.query_str)
return self.sql_object.query(name=row_obj.query_str, limit=1)

View File

@@ -1,5 +1,5 @@
"""
Contains all run related frontend functions
Contains all procedure related frontend functions
"""
from PyQt6.QtWidgets import (
QWidget, QPushButton, QVBoxLayout,
@@ -10,11 +10,11 @@ from .functions import select_open_file, select_save_file
import logging
from pathlib import Path
from tools import Report, Result, check_not_nan, main_form_style, report_result, get_application_from_parent
from backend.excel import SheetParser, InfoParser
from backend.validators import PydSubmission, PydReagent
from backend.excel import ClientSubmissionParser, SampleParser
from backend.validators import PydSubmission, PydReagent, PydClientSubmission, PydSample
from backend.db import (
Organization, SubmissionType, Reagent,
ReagentRole, KitTypeReagentRoleAssociation, BasicRun
ClientLab, SubmissionType, Reagent,
ReagentRole, KitTypeReagentRoleAssociation, Run
)
from pprint import pformat
from .pop_ups import QuestionAsker, AlertPop
@@ -93,7 +93,7 @@ class SubmissionFormContainer(QWidget):
@report_result
def import_submission_function(self, fname: Path | None = None) -> Report:
"""
Import a new run to the app window
Import a new procedure to the app window
Args:
obj (QMainWindow): original app window
@@ -110,7 +110,7 @@ class SubmissionFormContainer(QWidget):
self.form.setParent(None)
except AttributeError:
pass
# NOTE: initialize samples
# NOTE: initialize sample
self.samples = []
self.missing_info = []
# NOTE: set file dialog
@@ -121,19 +121,28 @@ class SubmissionFormContainer(QWidget):
return report
# NOTE: create sheetparser using excel sheet and context from gui
try:
# self.prsr = SheetParser(filepath=fname)
self.parser = InfoParser(filepath=fname)
self.clientsubmissionparser = ClientSubmissionParser(filepath=fname)
except PermissionError:
logger.error(f"Couldn't get permission to access file: {fname}")
return
except AttributeError:
self.parser = InfoParser(filepath=fname)
self.pyd = self.parser.to_pydantic()
# logger.debug(f"Samples: {pformat(self.pyd.samples)}")
checker = SampleChecker(self, "Sample Checker", self.pyd)
self.clientsubmissionparser = ClientSubmissionParser(filepath=fname)
try:
# self.prsr = SheetParser(filepath=fname)
self.sampleparser = SampleParser(filepath=fname)
except PermissionError:
logger.error(f"Couldn't get permission to access file: {fname}")
return
except AttributeError:
self.sampleparser = SampleParser(filepath=fname)
self.pydclientsubmission = self.clientsubmissionparser.to_pydantic()
self.pydsamples = self.sampleparser.to_pydantic()
# logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}")
checker = SampleChecker(self, "Sample Checker", self.pydsamples)
if checker.exec():
# logger.debug(pformat(self.pyd.samples))
self.form = self.pyd.to_form(parent=self)
# logger.debug(pformat(self.pydclientsubmission.sample))
self.form = self.pydclientsubmission.to_form(parent=self)
self.form.samples = self.pydsamples
self.layout().addWidget(self.form)
else:
message = "Submission cancelled."
@@ -150,7 +159,7 @@ class SubmissionFormContainer(QWidget):
instance (Reagent | None): Blank reagent instance to be edited and then added.
Returns:
models.Reagent: the constructed reagent object to add to run
models.Reagent: the constructed reagent object to add to procedure
"""
report = Report()
if not instance:
@@ -167,23 +176,23 @@ class SubmissionFormContainer(QWidget):
class SubmissionFormWidget(QWidget):
update_reagent_fields = ['extraction_kit']
update_reagent_fields = ['kittype']
def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None:
def __init__(self, parent: QWidget, pyd: PydSubmission, disable: list | None = None) -> None:
super().__init__(parent)
if disable is None:
disable = []
self.app = get_application_from_parent(parent)
self.pyd = submission
self.pyd = pyd
self.missing_info = []
self.submission_type = SubmissionType.query(name=self.pyd.submission_type['value'])
basic_submission_class = self.submission_type.submission_class
logger.debug(f"Basic run class: {basic_submission_class}")
defaults = basic_submission_class.get_default_info("form_recover", "form_ignore", submission_type=self.pyd.submission_type['value'])
self.submissiontype = SubmissionType.query(name=self.pyd.submissiontype['value'])
# basic_submission_class = self.submission_type.submission_class
# logger.debug(f"Basic procedure class: {basic_submission_class}")
defaults = Run.get_default_info("form_recover", "form_ignore", submissiontype=self.pyd.submissiontype['value'])
self.recover = defaults['form_recover']
self.ignore = defaults['form_ignore']
self.layout = QVBoxLayout()
for k in list(self.pyd.model_fields.keys()) + list(self.pyd.model_extra.keys()):
for k in list(self.pyd.model_fields.keys()):# + list(self.pyd.model_extra.keys()):
logger.debug(f"Pydantic field: {k}")
if k in self.ignore:
logger.warning(f"{k} in form_ignore {self.ignore}, not creating widget")
@@ -201,8 +210,8 @@ class SubmissionFormWidget(QWidget):
except KeyError:
value = dict(value=None, missing=True)
logger.debug(f"Pydantic value: {value}")
add_widget = self.create_widget(key=k, value=value, submission_type=self.submission_type,
run_object=basic_submission_class, disable=check)
add_widget = self.create_widget(key=k, value=value, submission_type=self.submissiontype,
run_object=Run(), disable=check)
if add_widget is not None:
self.layout.addWidget(add_widget)
if k in self.__class__.update_reagent_fields:
@@ -212,7 +221,7 @@ class SubmissionFormWidget(QWidget):
self.layout.addWidget(self.disabler)
self.disabler.checkbox.checkStateChanged.connect(self.disable_reagents)
self.setStyleSheet(main_form_style)
# self.scrape_reagents(self.extraction_kit)
# self.scrape_reagents(self.kittype)
self.setLayout(self.layout)
def disable_reagents(self):
@@ -223,7 +232,7 @@ class SubmissionFormWidget(QWidget):
reagent.flip_check(self.disabler.checkbox.isChecked())
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType | None = None,
extraction_kit: str | None = None, run_object: BasicRun | None = None,
extraction_kit: str | None = None, run_object: Run | None = None,
disable: bool = False) -> "self.InfoItem":
"""
Make an InfoItem widget to hold a field
@@ -256,14 +265,14 @@ class SubmissionFormWidget(QWidget):
return None
@report_result
def scrape_reagents(self, *args, **kwargs): #extraction_kit:str, caller:str|None=None):
def scrape_reagents(self, *args, **kwargs): #kittype:str, caller:str|None=None):
"""
Extracted scrape reagents function that will run when
form 'extraction_kit' widget is updated.
Extracted scrape reagents function that will procedure when
form 'kittype' widget is updated.
Args:
obj (QMainWindow): updated main application
extraction_kit (str): name of extraction kit (in 'extraction_kit' widget)
extraction_kit (str): name of extraction kittype (in 'kittype' widget)
Returns:
Tuple[QMainWindow, dict]: Updated application and result
@@ -373,7 +382,7 @@ class SubmissionFormWidget(QWidget):
return report
case _:
pass
# NOTE: add reagents to run object
# NOTE: add reagents to procedure object
if base_submission is None:
return
for reagent in base_submission.reagents:
@@ -393,7 +402,7 @@ class SubmissionFormWidget(QWidget):
def export_csv_function(self, fname: Path | None = None):
"""
Save the run's csv file.
Save the procedure's csv file.
Args:
fname (Path | None, optional): Input filename. Defaults to None.
@@ -405,7 +414,7 @@ class SubmissionFormWidget(QWidget):
except PermissionError:
logger.warning(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
except AttributeError:
logger.error(f"No csv file found in the run at this point.")
logger.error(f"No csv file found in the procedure at this point.")
def parse_form(self) -> Report:
"""
@@ -442,11 +451,10 @@ class SubmissionFormWidget(QWidget):
report.add_result(report)
return report
class InfoItem(QWidget):
def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | SubmissionType | None = None,
run_object: BasicRun | None = None) -> None:
run_object: Run | None = None) -> None:
super().__init__(parent)
if isinstance(submission_type, str):
submission_type = SubmissionType.query(name=submission_type)
@@ -492,7 +500,7 @@ class SubmissionFormWidget(QWidget):
def set_widget(self, parent: QWidget, key: str, value: dict,
submission_type: str | SubmissionType | None = None,
sub_obj: BasicRun | None = None) -> QWidget:
sub_obj: Run | None = None) -> QWidget:
"""
Creates form widget
@@ -515,16 +523,16 @@ class SubmissionFormWidget(QWidget):
pass
obj = parent.parent().parent()
match key:
case 'submitting_lab':
case 'clientlab':
add_widget = MyQComboBox(scrollWidget=parent)
# NOTE: lookup organizations suitable for submitting_lab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
labs = [item.name for item in Organization.query()]
# NOTE: lookup organizations suitable for clientlab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
labs = [item.name for item in ClientLab.query()]
if isinstance(value, dict):
value = value['value']
if isinstance(value, Organization):
if isinstance(value, ClientLab):
value = value.name
try:
looked_up_lab = Organization.query(name=value, limit=1)
looked_up_lab = ClientLab.query(name=value, limit=1)
except AttributeError:
looked_up_lab = None
if looked_up_lab:
@@ -536,28 +544,28 @@ class SubmissionFormWidget(QWidget):
# NOTE: set combobox values to lookedup values
add_widget.addItems(labs)
add_widget.setToolTip("Select submitting lab.")
case 'extraction_kit':
# NOTE: if extraction kit not available, all other values fail
case 'kittype':
# NOTE: if extraction kittype not available, all other values fail
if not check_not_nan(value):
msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!",
msg = AlertPop(message="Make sure to check your extraction kittype in the excel sheet!",
status="warning")
msg.exec()
# NOTE: create combobox to hold looked up kits
add_widget = MyQComboBox(scrollWidget=parent)
# NOTE: lookup existing kits by 'submission_type' decided on by sheetparser
# NOTE: lookup existing kits by 'proceduretype' decided on by sheetparser
uses = [item.name for item in submission_type.kit_types]
obj.uses = uses
if check_not_nan(value):
try:
uses.insert(0, uses.pop(uses.index(value)))
except ValueError:
logger.warning(f"Couldn't find kit in list, skipping move to top of list.")
logger.warning(f"Couldn't find kittype in list, skipping move to top of list.")
obj.ext_kit = value
else:
logger.error(f"Couldn't find {obj.prsr.sub['extraction_kit']}")
logger.error(f"Couldn't find {obj.prsr.sub['kittype']}")
obj.ext_kit = uses[0]
add_widget.addItems(uses)
add_widget.setToolTip("Select extraction kit.")
add_widget.setToolTip("Select extraction kittype.")
parent.extraction_kit = add_widget.currentText()
case 'submission_category':
add_widget = MyQComboBox(scrollWidget=parent)
@@ -568,7 +576,7 @@ class SubmissionFormWidget(QWidget):
except ValueError:
categories.insert(0, categories.pop(categories.index(submission_type)))
add_widget.addItems(categories)
add_widget.setToolTip("Enter run category or select from list.")
add_widget.setToolTip("Enter procedure category or select from list.")
case _:
if key in sub_obj.timestamps:
add_widget = MyQDateEdit(calendarPopup=True, scrollWidget=parent)
@@ -692,10 +700,10 @@ class SubmissionFormWidget(QWidget):
wanted_reagent = self.parent.parent().add_reagent(instance=wanted_reagent)
return wanted_reagent, report
else:
# NOTE: In this case we will have an empty reagent and the run will fail kit integrity check
# NOTE: In this case we will have an empty reagent and the procedure will fail kittype 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 clientsubmissionparser -> pydclientsubmission -> form and the clientsubmissionparser gets the name from the db, it should no longer be necessary to query the db with reagent/kittype, but with rt name directly.
rt = ReagentRole.query(name=self.reagent.role)
if rt is None:
rt = ReagentRole.query(kittype=self.extraction_kit, reagent=wanted_reagent)
@@ -738,7 +746,7 @@ class SubmissionFormWidget(QWidget):
def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None:
super().__init__(scrollWidget=scrollWidget)
self.setEditable(True)
looked_up_rt = KitTypeReagentRoleAssociation.query(reagentrole=reagent.role,
looked_up_rt = KitTypeReagentRoleAssociation.query(reagentrole=reagent.equipmentrole,
kittype=extraction_kit)
relevant_reagents = [str(item.lot) for item in looked_up_rt.get_all_relevant_reagents()]
# NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows
@@ -754,7 +762,8 @@ class SubmissionFormWidget(QWidget):
looked_up_reg = None
if looked_up_reg:
try:
relevant_reagents.insert(0, relevant_reagents.pop(relevant_reagents.index(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}")
else:
@@ -764,9 +773,9 @@ class SubmissionFormWidget(QWidget):
relevant_reagents.insert(0, moved_reag)
else:
pass
self.setObjectName(f"lot_{reagent.role}")
self.setObjectName(f"lot_{reagent.equipmentrole}")
self.addItems(relevant_reagents)
self.setToolTip(f"Enter lot number for the reagent used for {reagent.role}")
self.setToolTip(f"Enter lot number for the reagent used for {reagent.equipmentrole}")
class DisableReagents(QWidget):
@@ -783,16 +792,22 @@ class SubmissionFormWidget(QWidget):
class ClientSubmissionFormWidget(SubmissionFormWidget):
def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None:
super().__init__(parent, submission=submission, disable=disable)
self.disabler.setHidden(True)
def __init__(self, parent: QWidget, clientsubmission: PydClientSubmission, samples: List = [],
disable: list | None = None) -> None:
super().__init__(parent, pyd=clientsubmission, disable=disable)
try:
self.disabler.setHidden(True)
except AttributeError:
pass
# save_btn = QPushButton("Save")
self.samples = samples
logger.debug(f"Samples: {self.samples}")
start_run_btn = QPushButton("Save")
# self.layout.addWidget(save_btn)
self.layout.addWidget(start_run_btn)
start_run_btn.clicked.connect(self.create_new_submission)
@report_result
def parse_form(self) -> Report:
"""
Transforms form info into PydSubmission
@@ -801,7 +816,7 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
Report: Report on status of parse.
"""
report = Report()
logger.info(f"Hello from client run form parser!")
logger.info(f"Hello from client procedure form parser!")
info = {}
reagents = []
for widget in self.findChildren(QWidget):
@@ -827,18 +842,20 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
report.add_result(report)
return report
@report_result
# @report_result
def to_pydantic(self, *args):
self.parse_form()
return self.pyd
@report_result
def create_new_submission(self, *args) -> Report:
self.parse_form()
sql = self.pyd.to_sql()
pyd = self.to_pydantic()
sql = pyd.to_sql()
for sample in self.samples:
if isinstance(sample, PydSample):
sample = sample.to_sql()
sql.add_sample(sample=sample)
logger.debug(sql.__dict__)
sql.save()
self.app.table_widget.sub_wid.set_data()
self.setParent(None)

View File

@@ -3,7 +3,7 @@ Pane to hold information e.g. cost summary.
"""
from .info_tab import InfoPane
from PyQt6.QtWidgets import QWidget, QLabel, QPushButton
from backend.db import Organization
from backend.db import ClientLab
from backend.excel import ReportMaker
from .misc import CheckableComboBox
import logging
@@ -24,7 +24,7 @@ class Summary(InfoPane):
self.org_select = CheckableComboBox()
self.org_select.setEditable(False)
self.org_select.addItem("Select", header=True)
for org in [org.name for org in Organization.query()]:
for org in [org.name for org in ClientLab.query()]:
self.org_select.addItem(org)
self.org_select.model().itemChanged.connect(self.update_data)
self.layout.addWidget(QLabel("Client"), 1, 0, 1, 1)