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