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