Commit pre-refactor for code cleanup.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# __init__.py
|
# __init__.py
|
||||||
|
|
||||||
# Version of the realpython-reader package
|
# Version of the realpython-reader package
|
||||||
__version__ = "1.2.2"
|
__version__ = "1.2.3"
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ def lookup_all_submissions_by_type(ctx:dict, type:str|None=None) -> list[models.
|
|||||||
if type == None:
|
if type == None:
|
||||||
subs = ctx['database_session'].query(models.BasicSubmission).all()
|
subs = ctx['database_session'].query(models.BasicSubmission).all()
|
||||||
else:
|
else:
|
||||||
subs = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.submission_type==type).all()
|
subs = ctx['database_session'].query(models.BasicSubmission).filter(models.BasicSubmission.submission_type==type.lower().replace(" ", "_")).all()
|
||||||
return subs
|
return subs
|
||||||
|
|
||||||
def lookup_all_orgs(ctx:dict) -> list[models.Organization]:
|
def lookup_all_orgs(ctx:dict) -> list[models.Organization]:
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from ..models import *
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
def check_kit_integrity(sub:BasicSubmission):
|
||||||
|
ext_kit_rtypes = [reagenttype.name for reagenttype in sub.extraction_kit.reagent_types]
|
||||||
|
logger.debug(f"Kit reagents: {ext_kit_rtypes}")
|
||||||
|
reagenttypes = [reagent.type.name for reagent in sub.reagents]
|
||||||
|
logger.debug(f"Submission reagents: {reagenttypes}")
|
||||||
|
check = set(ext_kit_rtypes) == set(reagenttypes)
|
||||||
|
logger.debug(f"Checking if reagents match kit contents: {check}")
|
||||||
|
common = list(set(ext_kit_rtypes).intersection(reagenttypes))
|
||||||
|
logger.debug(f"common reagents types: {common}")
|
||||||
|
if check:
|
||||||
|
result = None
|
||||||
|
else:
|
||||||
|
result = {'message' : f"Couldn't verify reagents match listed kit components.\n\nIt looks like you are missing: {[x.upper for x in ext_kit_rtypes if x not in common]}\n\nAlternatively, you may have set the wrong extraction kit."}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class Reagent(Base):
|
|||||||
Returns:
|
Returns:
|
||||||
str: string representing this object's lot number
|
str: string representing this object's lot number
|
||||||
"""
|
"""
|
||||||
return self.lot
|
return str(self.lot)
|
||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import re
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import uuid
|
import uuid
|
||||||
|
from frontend.functions import check_not_nan
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ class SheetParser(object):
|
|||||||
"""
|
"""
|
||||||
Dummy function to handle unknown excel structures
|
Dummy function to handle unknown excel structures
|
||||||
"""
|
"""
|
||||||
|
logger.error(f"Unknown excel workbook structure. Cannot parse.")
|
||||||
self.sub = None
|
self.sub = None
|
||||||
|
|
||||||
|
|
||||||
@@ -96,11 +98,11 @@ class SheetParser(object):
|
|||||||
if ii == 11:
|
if ii == 11:
|
||||||
continue
|
continue
|
||||||
logger.debug(f"Running reagent parse for {row[1]} with type {type(row[1])} and value: {row[2]} with type {type(row[2])}")
|
logger.debug(f"Running reagent parse for {row[1]} with type {type(row[1])} and value: {row[2]} with type {type(row[2])}")
|
||||||
try:
|
# try:
|
||||||
check = not np.isnan(row[1])
|
# check = not np.isnan(row[1])
|
||||||
except TypeError:
|
# except TypeError:
|
||||||
check = True
|
# check = True
|
||||||
if not isinstance(row[2], float) and check:
|
if not isinstance(row[2], float) and check_not_nan(row[1]):
|
||||||
# must be prefixed with 'lot_' to be recognized by gui
|
# must be prefixed with 'lot_' to be recognized by gui
|
||||||
try:
|
try:
|
||||||
reagent_type = row[1].replace(' ', '_').lower().strip()
|
reagent_type = row[1].replace(' ', '_').lower().strip()
|
||||||
@@ -114,7 +116,18 @@ class SheetParser(object):
|
|||||||
logger.debug(f"Couldn't upperize {row[2]}, must be a number")
|
logger.debug(f"Couldn't upperize {row[2]}, must be a number")
|
||||||
output_var = row[2]
|
output_var = row[2]
|
||||||
logger.debug(f"Output variable is {output_var}")
|
logger.debug(f"Output variable is {output_var}")
|
||||||
self.sub[f"lot_{reagent_type}"] = output_var
|
# self.sub[f"lot_{reagent_type}"] = output_var
|
||||||
|
# update 2023-02-10 to above allowing generation of expiry date in adding reagent to db.
|
||||||
|
logger.debug(f"Expiry date for imported reagent: {row[3]}")
|
||||||
|
try:
|
||||||
|
check = not np.isnan(row[3])
|
||||||
|
except TypeError:
|
||||||
|
check = True
|
||||||
|
if check:
|
||||||
|
expiry = row[3].date()
|
||||||
|
else:
|
||||||
|
expiry = date.today()
|
||||||
|
self.sub[f"lot_{reagent_type}"] = {'lot':output_var, 'exp':expiry}
|
||||||
|
|
||||||
submission_info = self._parse_generic("Sample List")
|
submission_info = self._parse_generic("Sample List")
|
||||||
# iloc is [row][column] and the first row is set as header row so -2
|
# iloc is [row][column] and the first row is set as header row so -2
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ def make_report_xlsx(records:list[dict]) -> DataFrame:
|
|||||||
# put submissions with the same lab together
|
# put submissions with the same lab together
|
||||||
df = df.sort_values("Submitting Lab")
|
df = df.sort_values("Submitting Lab")
|
||||||
# aggregate cost and sample count columns
|
# aggregate cost and sample count columns
|
||||||
df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Cost': ['sum', 'count'], 'Sample Count':['sum']})
|
df2 = df.groupby(["Submitting Lab", "Extraction Kit"]).agg({'Extraction Kit':'count', 'Cost': 'sum', 'Sample Count':'sum'})
|
||||||
|
df2 = df2.rename(columns={"Extraction Kit": 'Kit Count'})
|
||||||
|
logger.debug(f"Output daftaframe for xlsx: {df2.columns}")
|
||||||
# apply formating to cost column
|
# apply formating to cost column
|
||||||
# df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
|
# df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')] = df2.iloc[:, (df2.columns.get_level_values(1)=='sum') & (df2.columns.get_level_values(0)=='Cost')].applymap('${:,.2f}'.format)
|
||||||
return df2
|
return df2
|
||||||
@@ -54,11 +56,14 @@ def make_report_html(df:DataFrame, start_date:date, end_date:date) -> str:
|
|||||||
"""
|
"""
|
||||||
old_lab = ""
|
old_lab = ""
|
||||||
output = []
|
output = []
|
||||||
|
logger.debug(f"Report DataFrame: {df}")
|
||||||
for ii, row in enumerate(df.iterrows()):
|
for ii, row in enumerate(df.iterrows()):
|
||||||
row = [item for item in row]
|
row = [item for item in row]
|
||||||
|
logger.debug(f"Row: {row}")
|
||||||
|
|
||||||
lab = row[0][0]
|
lab = row[0][0]
|
||||||
logger.debug(f"Old lab: {old_lab}, Current lab: {lab}")
|
logger.debug(f"Old lab: {old_lab}, Current lab: {lab}")
|
||||||
kit = dict(name=row[0][1], cost=row[1][('Cost', 'sum')], plate_count=int(row[1][('Cost', 'count')]), sample_count=int(row[1][('Sample Count', 'sum')]))
|
kit = dict(name=row[0][1], cost=row[1]['Cost'], plate_count=int(row[1]['Kit Count']), sample_count=int(row[1]['Sample Count']))
|
||||||
if lab == old_lab:
|
if lab == old_lab:
|
||||||
output[ii-1]['kits'].append(kit)
|
output[ii-1]['kits'].append(kit)
|
||||||
output[ii-1]['total_cost'] += kit['cost']
|
output[ii-1]['total_cost'] += kit['cost']
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ from PyQt6.QtWidgets import (
|
|||||||
from PyQt6.QtGui import QAction
|
from PyQt6.QtGui import QAction
|
||||||
from PyQt6.QtCore import QSignalBlocker
|
from PyQt6.QtCore import QSignalBlocker
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
|
||||||
# import pandas as pd
|
# import pandas as pd
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import plotly
|
import plotly
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -21,7 +19,7 @@ from xhtml2pdf import pisa
|
|||||||
# import plotly.express as px
|
# import plotly.express as px
|
||||||
import yaml
|
import yaml
|
||||||
import pprint
|
import pprint
|
||||||
|
import numpy as np
|
||||||
from backend.excel.parser import SheetParser
|
from backend.excel.parser import SheetParser
|
||||||
from backend.excel.reports import convert_control_by_mode, convert_data_list_to_df
|
from backend.excel.reports import convert_control_by_mode, convert_data_list_to_df
|
||||||
from backend.db import (construct_submission_info, lookup_reagent,
|
from backend.db import (construct_submission_info, lookup_reagent,
|
||||||
@@ -30,9 +28,13 @@ from backend.db import (construct_submission_info, lookup_reagent,
|
|||||||
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
|
get_all_Control_Types_names, create_kit_from_yaml, get_all_available_modes, get_all_controls_by_type,
|
||||||
get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num
|
get_control_subtypes, lookup_all_submissions_by_type, get_all_controls, lookup_submission_by_rsl_num
|
||||||
)
|
)
|
||||||
|
from .functions import check_kit_integrity, check_not_nan
|
||||||
from backend.excel.reports import make_report_xlsx, make_report_html
|
from backend.excel.reports import make_report_xlsx, make_report_html
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from frontend.custom_widgets import AddReagentQuestion, AddReagentForm, SubmissionsSheet, ReportDatePicker, KitAdder, ControlsDatePicker, OverwriteSubQuestion
|
from frontend.custom_widgets.sub_details import SubmissionsSheet
|
||||||
|
from frontend.custom_widgets.pop_ups import AddReagentQuestion, OverwriteSubQuestion, AlertPop
|
||||||
|
from frontend.custom_widgets import AddReagentForm, ReportDatePicker, KitAdder, ControlsDatePicker
|
||||||
import logging
|
import logging
|
||||||
import difflib
|
import difflib
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
@@ -130,6 +132,7 @@ class App(QMainWindow):
|
|||||||
logger.debug(self.ctx)
|
logger.debug(self.ctx)
|
||||||
# initialize samples
|
# initialize samples
|
||||||
self.samples = []
|
self.samples = []
|
||||||
|
self.reagents = {}
|
||||||
# set file dialog
|
# set file dialog
|
||||||
home_dir = str(Path(self.ctx["directory_path"]))
|
home_dir = str(Path(self.ctx["directory_path"]))
|
||||||
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir)[0])
|
fname = Path(QFileDialog.getOpenFileName(self, 'Open file', home_dir)[0])
|
||||||
@@ -180,14 +183,9 @@ class App(QMainWindow):
|
|||||||
# create label
|
# create label
|
||||||
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
self.table_widget.formlayout.addWidget(QLabel(item.replace("_", " ").title()))
|
||||||
# if extraction kit not available, all other values fail
|
# if extraction kit not available, all other values fail
|
||||||
if prsr.sub[item] == 'nan':
|
if np.isnan(prsr.sub[item]):
|
||||||
msg = QMessageBox()
|
msg = AlertPop(message="Make sure to check your extraction kit!", status="warning")
|
||||||
# msg.setIcon(QMessageBox.critical)
|
|
||||||
msg.setText("Error")
|
|
||||||
msg.setInformativeText('You need to enter a value for extraction kit.')
|
|
||||||
msg.setWindowTitle("Error")
|
|
||||||
msg.exec()
|
msg.exec()
|
||||||
break
|
|
||||||
# create combobox to hold looked up kits
|
# create combobox to hold looked up kits
|
||||||
add_widget = QComboBox()
|
add_widget = QComboBox()
|
||||||
# lookup existing kits by 'submission_type' decided on by sheetparser
|
# lookup existing kits by 'submission_type' decided on by sheetparser
|
||||||
@@ -216,13 +214,13 @@ class App(QMainWindow):
|
|||||||
query_var = item.replace("lot_", "")
|
query_var = item.replace("lot_", "")
|
||||||
logger.debug(f"Query for: {query_var}")
|
logger.debug(f"Query for: {query_var}")
|
||||||
if isinstance(prsr.sub[item], numpy.float64):
|
if isinstance(prsr.sub[item], numpy.float64):
|
||||||
logger.debug(f"{prsr.sub[item]} is a numpy float!")
|
logger.debug(f"{prsr.sub[item]['lot']} is a numpy float!")
|
||||||
try:
|
try:
|
||||||
prsr.sub[item] = int(prsr.sub[item])
|
prsr.sub[item] = int(prsr.sub[item]['lot'])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
# query for reagents using type name from sheet and kit from sheet
|
# query for reagents using type name from sheet and kit from sheet
|
||||||
logger.debug(f"Attempting lookup of reagents by type: {query_var} and kit: {prsr.sub['extraction_kit']}")
|
logger.debug(f"Attempting lookup of reagents by type: {query_var}")
|
||||||
# below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
|
# below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
|
||||||
relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])]
|
relevant_reagents = [item.__str__() for item in lookup_regent_by_type_name(ctx=self.ctx, type_name=query_var)]#, kit_name=prsr.sub['extraction_kit'])]
|
||||||
output_reg = []
|
output_reg = []
|
||||||
@@ -235,15 +233,12 @@ class App(QMainWindow):
|
|||||||
relevant_reagents = output_reg
|
relevant_reagents = output_reg
|
||||||
logger.debug(f"Relevant reagents: {relevant_reagents}")
|
logger.debug(f"Relevant reagents: {relevant_reagents}")
|
||||||
# if reagent in sheet is not found insert it into items
|
# if reagent in sheet is not found insert it into items
|
||||||
if prsr.sub[item] not in relevant_reagents and prsr.sub[item] != 'nan':
|
if str(prsr.sub[item]['lot']) not in relevant_reagents and prsr.sub[item]['lot'] != 'nan':
|
||||||
try:
|
if check_not_nan(prsr.sub[item]['lot']):
|
||||||
check = not numpy.isnan(prsr.sub[item])
|
relevant_reagents.insert(0, str(prsr.sub[item]['lot']))
|
||||||
except TypeError:
|
logger.debug(f"New relevant reagents: {relevant_reagents}")
|
||||||
check = True
|
|
||||||
if check:
|
|
||||||
relevant_reagents.insert(0, str(prsr.sub[item]))
|
|
||||||
logger.debug(f"Relevant reagents: {relevant_reagents}")
|
|
||||||
add_widget.addItems(relevant_reagents)
|
add_widget.addItems(relevant_reagents)
|
||||||
|
self.reagents[item] = prsr.sub[item]
|
||||||
# TODO: make samples not appear in frame.
|
# TODO: make samples not appear in frame.
|
||||||
case 'samples':
|
case 'samples':
|
||||||
# hold samples in 'self' until form submitted
|
# hold samples in 'self' until form submitted
|
||||||
@@ -259,6 +254,7 @@ class App(QMainWindow):
|
|||||||
submit_btn = QPushButton("Submit")
|
submit_btn = QPushButton("Submit")
|
||||||
self.table_widget.formlayout.addWidget(submit_btn)
|
self.table_widget.formlayout.addWidget(submit_btn)
|
||||||
submit_btn.clicked.connect(self.submit_new_sample)
|
submit_btn.clicked.connect(self.submit_new_sample)
|
||||||
|
logger.debug(f"Imported reagents: {self.reagents}")
|
||||||
|
|
||||||
def submit_new_sample(self):
|
def submit_new_sample(self):
|
||||||
"""
|
"""
|
||||||
@@ -278,7 +274,9 @@ class App(QMainWindow):
|
|||||||
if wanted_reagent == None:
|
if wanted_reagent == None:
|
||||||
dlg = AddReagentQuestion(reagent_type=reagent, reagent_lot=reagents[reagent])
|
dlg = AddReagentQuestion(reagent_type=reagent, reagent_lot=reagents[reagent])
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
wanted_reagent = self.add_reagent(reagent_lot=reagents[reagent], reagent_type=reagent.replace("lot_", ""))
|
logger.debug(f"checking reagent: {reagent} in self.reagents. Result: {self.reagents[reagent]}")
|
||||||
|
expiry_date = self.reagents[reagent]['exp']
|
||||||
|
wanted_reagent = self.add_reagent(reagent_lot=reagents[reagent], reagent_type=reagent.replace("lot_", ""), expiry=expiry_date)
|
||||||
else:
|
else:
|
||||||
logger.debug("Will not add reagent.")
|
logger.debug("Will not add reagent.")
|
||||||
if wanted_reagent != None:
|
if wanted_reagent != None:
|
||||||
@@ -301,15 +299,17 @@ class App(QMainWindow):
|
|||||||
for reagent in parsed_reagents:
|
for reagent in parsed_reagents:
|
||||||
base_submission.reagents.append(reagent)
|
base_submission.reagents.append(reagent)
|
||||||
# base_submission.reagents_id = reagent.id
|
# base_submission.reagents_id = reagent.id
|
||||||
|
logger.debug("Checking kit integrity...")
|
||||||
|
kit_integrity = check_kit_integrity(base_submission)
|
||||||
|
if kit_integrity != None:
|
||||||
|
msg = AlertPop(message=kit_integrity['message'], status="critical")
|
||||||
|
msg.exec()
|
||||||
|
return
|
||||||
logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.")
|
logger.debug(f"Sending submission: {base_submission.rsl_plate_num} to database.")
|
||||||
result = store_submission(ctx=self.ctx, base_submission=base_submission)
|
result = store_submission(ctx=self.ctx, base_submission=base_submission)
|
||||||
# check result of storing for issues
|
# # check result of storing for issues
|
||||||
if result != None:
|
if result != None:
|
||||||
msg = QMessageBox()
|
msg = AlertPop(result['message'])
|
||||||
# msg.setIcon(QMessageBox.critical)
|
|
||||||
msg.setText("Error")
|
|
||||||
msg.setInformativeText(result['message'])
|
|
||||||
msg.setWindowTitle("Error")
|
|
||||||
msg.exec()
|
msg.exec()
|
||||||
# update summary sheet
|
# update summary sheet
|
||||||
self.table_widget.sub_wid.setData()
|
self.table_widget.sub_wid.setData()
|
||||||
@@ -318,7 +318,7 @@ class App(QMainWindow):
|
|||||||
item.setParent(None)
|
item.setParent(None)
|
||||||
|
|
||||||
|
|
||||||
def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None):
|
def add_reagent(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None):
|
||||||
"""
|
"""
|
||||||
Action to create new reagent in DB.
|
Action to create new reagent in DB.
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ class App(QMainWindow):
|
|||||||
if isinstance(reagent_lot, bool):
|
if isinstance(reagent_lot, bool):
|
||||||
reagent_lot = ""
|
reagent_lot = ""
|
||||||
# create form
|
# create form
|
||||||
dlg = AddReagentForm(ctx=self.ctx, reagent_lot=reagent_lot, reagent_type=reagent_type)
|
dlg = AddReagentForm(ctx=self.ctx, reagent_lot=reagent_lot, reagent_type=reagent_type, expiry=expiry)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
# extract form info
|
# extract form info
|
||||||
labels, values = self.extract_form_info(dlg)
|
labels, values = self.extract_form_info(dlg)
|
||||||
@@ -341,7 +341,7 @@ class App(QMainWindow):
|
|||||||
# create reagent object
|
# create reagent object
|
||||||
reagent = construct_reagent(ctx=self.ctx, info_dict=info)
|
reagent = construct_reagent(ctx=self.ctx, info_dict=info)
|
||||||
# send reagent to db
|
# send reagent to db
|
||||||
store_reagent(ctx=self.ctx, reagent=reagent)
|
# store_reagent(ctx=self.ctx, reagent=reagent)
|
||||||
return reagent
|
return reagent
|
||||||
|
|
||||||
|
|
||||||
@@ -417,19 +417,16 @@ class App(QMainWindow):
|
|||||||
# set_column(idx, idx, max_len) # set column width
|
# set_column(idx, idx, max_len) # set column width
|
||||||
# colu = worksheet.column_dimensions["C"]
|
# colu = worksheet.column_dimensions["C"]
|
||||||
# style = NamedStyle(name="custom_currency", number_format='Currency')
|
# style = NamedStyle(name="custom_currency", number_format='Currency')
|
||||||
for cell in worksheet['C']:
|
for cell in worksheet['D']:
|
||||||
# try:
|
# try:
|
||||||
# check = int(cell.row)
|
# check = int(cell.row)
|
||||||
# except TypeError:
|
# except TypeError:
|
||||||
# continue
|
# continue
|
||||||
if cell.row > 3:
|
if cell.row > 1:
|
||||||
cell.style = 'Currency'
|
cell.style = 'Currency'
|
||||||
writer.close()
|
writer.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_kit(self):
|
def add_kit(self):
|
||||||
"""
|
"""
|
||||||
Constructs new kit from yaml and adds to DB.
|
Constructs new kit from yaml and adds to DB.
|
||||||
@@ -477,7 +474,7 @@ class App(QMainWindow):
|
|||||||
# correct start date being more recent than end date and rerun
|
# correct start date being more recent than end date and rerun
|
||||||
if self.table_widget.datepicker.start_date.date() > self.table_widget.datepicker.end_date.date():
|
if self.table_widget.datepicker.start_date.date() > self.table_widget.datepicker.end_date.date():
|
||||||
logger.warning("Start date after end date is not allowed!")
|
logger.warning("Start date after end date is not allowed!")
|
||||||
threemonthsago = self.table_widget.datepicker.end_date.date().addDays(-90)
|
threemonthsago = self.table_widget.datepicker.end_date.date().addDays(-60)
|
||||||
# block signal that will rerun controls getter and set start date
|
# block signal that will rerun controls getter and set start date
|
||||||
with QSignalBlocker(self.table_widget.datepicker.start_date) as blocker:
|
with QSignalBlocker(self.table_widget.datepicker.start_date) as blocker:
|
||||||
self.table_widget.datepicker.start_date.setDate(threemonthsago)
|
self.table_widget.datepicker.start_date.setDate(threemonthsago)
|
||||||
@@ -572,12 +569,14 @@ class App(QMainWindow):
|
|||||||
if " " in sample:
|
if " " in sample:
|
||||||
logger.warning(f"There is not supposed to be a space in the sample name!!!")
|
logger.warning(f"There is not supposed to be a space in the sample name!!!")
|
||||||
sample = sample.replace(" ", "")
|
sample = sample.replace(" ", "")
|
||||||
if sample not in ac_list:
|
# if sample not in ac_list:
|
||||||
|
if not any([ac.startswith(sample) for ac in ac_list]):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
for control in all_controls:
|
for control in all_controls:
|
||||||
diff = difflib.SequenceMatcher(a=sample, b=control.name).ratio()
|
diff = difflib.SequenceMatcher(a=sample, b=control.name).ratio()
|
||||||
if diff > 0.955:
|
# if diff > 0.955:
|
||||||
|
if control.name.startswith(sample):
|
||||||
logger.debug(f"Checking {sample} against {control.name}... {diff}")
|
logger.debug(f"Checking {sample} against {control.name}... {diff}")
|
||||||
# if sample == control.name:
|
# if sample == control.name:
|
||||||
logger.debug(f"Found match:\n\tSample: {sample}\n\tControl: {control.name}\n\tDifference: {diff}")
|
logger.debug(f"Found match:\n\tSample: {sample}\n\tControl: {control.name}\n\tDifference: {diff}")
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
from datetime import date
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QLabel, QVBoxLayout,
|
QLabel, QVBoxLayout,
|
||||||
QLineEdit, QComboBox, QDialog,
|
QLineEdit, QComboBox, QDialog,
|
||||||
QDialogButtonBox, QDateEdit, QTableView,
|
QDialogButtonBox, QDateEdit, QSizePolicy, QWidget,
|
||||||
QTextEdit, QSizePolicy, QWidget,
|
|
||||||
QGridLayout, QPushButton, QSpinBox,
|
QGridLayout, QPushButton, QSpinBox,
|
||||||
QScrollBar, QScrollArea, QHBoxLayout,
|
QScrollBar, QHBoxLayout,
|
||||||
QMessageBox, QFileDialog, QToolBar
|
QMessageBox
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QDate, QAbstractTableModel, QSize
|
from PyQt6.QtCore import Qt, QDate, QSize
|
||||||
from PyQt6.QtGui import QFontMetrics, QAction
|
# from PyQt6.QtGui import QFontMetrics, QAction
|
||||||
|
|
||||||
from backend.db import get_all_reagenttype_names, submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
|
from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from xhtml2pdf import pisa
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
@@ -26,55 +25,12 @@ else:
|
|||||||
loader = FileSystemLoader(loader_path)
|
loader = FileSystemLoader(loader_path)
|
||||||
env = Environment(loader=loader)
|
env = Environment(loader=loader)
|
||||||
|
|
||||||
class AddReagentQuestion(QDialog):
|
|
||||||
"""
|
|
||||||
dialog to ask about adding a new reagne to db
|
|
||||||
"""
|
|
||||||
def __init__(self, reagent_type:str, reagent_lot:str) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.setWindowTitle(f"Add {reagent_lot}?")
|
|
||||||
|
|
||||||
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
|
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
|
||||||
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
message = QLabel(f"Couldn't find reagent type {reagent_type.replace('_', ' ').title().strip('Lot')}: {reagent_lot} in the database.\nWould you like to add it?")
|
|
||||||
self.layout.addWidget(message)
|
|
||||||
self.layout.addWidget(self.buttonBox)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
|
|
||||||
|
|
||||||
class OverwriteSubQuestion(QDialog):
|
|
||||||
"""
|
|
||||||
dialog to ask about overwriting existing submission
|
|
||||||
"""
|
|
||||||
def __init__(self, message:str, rsl_plate_num:str) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.setWindowTitle(f"Overwrite {rsl_plate_num}?")
|
|
||||||
|
|
||||||
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
|
|
||||||
|
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
|
||||||
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
message = QLabel(message)
|
|
||||||
self.layout.addWidget(message)
|
|
||||||
self.layout.addWidget(self.buttonBox)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
|
|
||||||
|
|
||||||
class AddReagentForm(QDialog):
|
class AddReagentForm(QDialog):
|
||||||
"""
|
"""
|
||||||
dialog to add gather info about new reagent
|
dialog to add gather info about new reagent
|
||||||
"""
|
"""
|
||||||
def __init__(self, ctx:dict, reagent_lot:str|None, reagent_type:str|None) -> None:
|
def __init__(self, ctx:dict, reagent_lot:str|None, reagent_type:str|None, expiry:date|None=None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
if reagent_lot == None:
|
if reagent_lot == None:
|
||||||
@@ -92,7 +48,10 @@ class AddReagentForm(QDialog):
|
|||||||
lot_input.setText(reagent_lot)
|
lot_input.setText(reagent_lot)
|
||||||
# get expiry info
|
# get expiry info
|
||||||
exp_input = QDateEdit(calendarPopup=True)
|
exp_input = QDateEdit(calendarPopup=True)
|
||||||
|
if expiry == None:
|
||||||
exp_input.setDate(QDate.currentDate())
|
exp_input.setDate(QDate.currentDate())
|
||||||
|
else:
|
||||||
|
exp_input.setDate(expiry)
|
||||||
# get reagent type info
|
# get reagent type info
|
||||||
type_input = QComboBox()
|
type_input = QComboBox()
|
||||||
type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)])
|
type_input.addItems([item.replace("_", " ").title() for item in get_all_reagenttype_names(ctx=ctx)])
|
||||||
@@ -117,172 +76,6 @@ class AddReagentForm(QDialog):
|
|||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class pandasModel(QAbstractTableModel):
|
|
||||||
"""
|
|
||||||
pandas model for inserting summary sheet into gui
|
|
||||||
"""
|
|
||||||
def __init__(self, data) -> None:
|
|
||||||
QAbstractTableModel.__init__(self)
|
|
||||||
self._data = data
|
|
||||||
|
|
||||||
def rowCount(self, parent=None) -> int:
|
|
||||||
"""
|
|
||||||
does what it says
|
|
||||||
|
|
||||||
Args:
|
|
||||||
parent (_type_, optional): _description_. Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: number of rows in data
|
|
||||||
"""
|
|
||||||
return self._data.shape[0]
|
|
||||||
|
|
||||||
def columnCount(self, parnet=None) -> int:
|
|
||||||
"""
|
|
||||||
does what it says
|
|
||||||
|
|
||||||
Args:
|
|
||||||
parnet (_type_, optional): _description_. Defaults to None.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: number of columns in data
|
|
||||||
"""
|
|
||||||
return self._data.shape[1]
|
|
||||||
|
|
||||||
def data(self, index, role=Qt.ItemDataRole.DisplayRole) -> str|None:
|
|
||||||
if index.isValid():
|
|
||||||
if role == Qt.ItemDataRole.DisplayRole:
|
|
||||||
return str(self._data.iloc[index.row(), index.column()])
|
|
||||||
return None
|
|
||||||
|
|
||||||
def headerData(self, col, orientation, role):
|
|
||||||
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
|
|
||||||
return self._data.columns[col]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionsSheet(QTableView):
|
|
||||||
"""
|
|
||||||
presents submission summary to user in tab1
|
|
||||||
"""
|
|
||||||
def __init__(self, ctx:dict) -> None:
|
|
||||||
"""
|
|
||||||
initialize
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ctx (dict): settings passed from gui
|
|
||||||
"""
|
|
||||||
super().__init__()
|
|
||||||
self.ctx = ctx
|
|
||||||
self.setData()
|
|
||||||
self.resizeColumnsToContents()
|
|
||||||
self.resizeRowsToContents()
|
|
||||||
# self.clicked.connect(self.test)
|
|
||||||
self.doubleClicked.connect(self.show_details)
|
|
||||||
|
|
||||||
def setData(self) -> None:
|
|
||||||
"""
|
|
||||||
sets data in model
|
|
||||||
"""
|
|
||||||
self.data = submissions_to_df(ctx=self.ctx)
|
|
||||||
self.model = pandasModel(self.data)
|
|
||||||
self.setModel(self.model)
|
|
||||||
# self.resize(800,600)
|
|
||||||
|
|
||||||
def show_details(self) -> None:
|
|
||||||
"""
|
|
||||||
creates detailed data to show in seperate window
|
|
||||||
"""
|
|
||||||
index=(self.selectionModel().currentIndex())
|
|
||||||
# logger.debug(index)
|
|
||||||
value=index.sibling(index.row(),0).data()
|
|
||||||
dlg = SubmissionDetails(ctx=self.ctx, id=value)
|
|
||||||
# dlg.show()
|
|
||||||
if dlg.exec():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionDetails(QDialog):
|
|
||||||
"""
|
|
||||||
a window showing text details of submission
|
|
||||||
"""
|
|
||||||
def __init__(self, ctx:dict, id:int) -> None:
|
|
||||||
|
|
||||||
super().__init__()
|
|
||||||
self.ctx = ctx
|
|
||||||
self.setWindowTitle("Submission Details")
|
|
||||||
|
|
||||||
# create scrollable interior
|
|
||||||
interior = QScrollArea()
|
|
||||||
interior.setParent(self)
|
|
||||||
# get submision from db
|
|
||||||
data = lookup_submission_by_id(ctx=ctx, id=id)
|
|
||||||
self.base_dict = data.to_dict()
|
|
||||||
logger.debug(f"Base dict: {self.base_dict}")
|
|
||||||
# don't want id
|
|
||||||
del self.base_dict['id']
|
|
||||||
# convert sub objects to dicts
|
|
||||||
self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
|
||||||
self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
|
||||||
# retrieve jinja template
|
|
||||||
template = env.get_template("submission_details.txt")
|
|
||||||
# render using object dict
|
|
||||||
text = template.render(sub=self.base_dict)
|
|
||||||
# create text field
|
|
||||||
txt_editor = QTextEdit(self)
|
|
||||||
txt_editor.setReadOnly(True)
|
|
||||||
txt_editor.document().setPlainText(text)
|
|
||||||
# resize
|
|
||||||
font = txt_editor.document().defaultFont()
|
|
||||||
fontMetrics = QFontMetrics(font)
|
|
||||||
textSize = fontMetrics.size(0, txt_editor.toPlainText())
|
|
||||||
w = textSize.width() + 10
|
|
||||||
h = textSize.height() + 10
|
|
||||||
txt_editor.setMinimumSize(w, h)
|
|
||||||
txt_editor.setMaximumSize(w, h)
|
|
||||||
txt_editor.resize(w, h)
|
|
||||||
interior.resize(w,900)
|
|
||||||
txt_editor.setText(text)
|
|
||||||
interior.setWidget(txt_editor)
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
self.setFixedSize(w, 900)
|
|
||||||
btn = QPushButton("Export PDF")
|
|
||||||
btn.setParent(self)
|
|
||||||
btn.setFixedWidth(w)
|
|
||||||
btn.clicked.connect(self.export)
|
|
||||||
|
|
||||||
|
|
||||||
# def _create_actions(self):
|
|
||||||
# self.exportAction = QAction("Export", self)
|
|
||||||
|
|
||||||
|
|
||||||
def export(self):
|
|
||||||
template = env.get_template("submission_details.html")
|
|
||||||
html = template.render(sub=self.base_dict)
|
|
||||||
# logger.debug(f"Submission details: {self.base_dict}")
|
|
||||||
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
|
|
||||||
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
|
||||||
# logger.debug(f"report output name: {fname}")
|
|
||||||
# df.to_excel(fname, engine='openpyxl')
|
|
||||||
if fname.__str__() == ".":
|
|
||||||
logger.debug("Saving pdf was cancelled.")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
with open(fname, "w+b") as f:
|
|
||||||
pisa.CreatePDF(html, dest=f)
|
|
||||||
except PermissionError as e:
|
|
||||||
logger.error(f"Error saving pdf: {e}")
|
|
||||||
msg = QMessageBox()
|
|
||||||
msg.setText("Permission Error")
|
|
||||||
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
|
||||||
msg.setWindowTitle("Permission Error")
|
|
||||||
msg.exec()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ReportDatePicker(QDialog):
|
class ReportDatePicker(QDialog):
|
||||||
"""
|
"""
|
||||||
custom dialog to ask for report start/stop dates
|
custom dialog to ask for report start/stop dates
|
||||||
@@ -467,7 +260,7 @@ class ControlsDatePicker(QWidget):
|
|||||||
|
|
||||||
self.start_date = QDateEdit(calendarPopup=True)
|
self.start_date = QDateEdit(calendarPopup=True)
|
||||||
# start date is three month prior to end date by default
|
# start date is three month prior to end date by default
|
||||||
threemonthsago = QDate.currentDate().addDays(-90)
|
threemonthsago = QDate.currentDate().addDays(-60)
|
||||||
self.start_date.setDate(threemonthsago)
|
self.start_date.setDate(threemonthsago)
|
||||||
self.end_date = QDateEdit(calendarPopup=True)
|
self.end_date = QDateEdit(calendarPopup=True)
|
||||||
self.end_date.setDate(QDate.currentDate())
|
self.end_date.setDate(QDate.currentDate())
|
||||||
|
|||||||
78
src/submissions/frontend/custom_widgets/pop_ups.py
Normal file
78
src/submissions/frontend/custom_widgets/pop_ups.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# from datetime import date
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QLabel, QVBoxLayout, QDialog,
|
||||||
|
QDialogButtonBox, QMessageBox
|
||||||
|
)
|
||||||
|
# from PyQt6.QtCore import Qt, QDate, QSize
|
||||||
|
# from PyQt6.QtGui import QFontMetrics, QAction
|
||||||
|
|
||||||
|
# from backend.db import get_all_reagenttype_names, lookup_all_sample_types, create_kit_from_yaml
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
|
||||||
|
else:
|
||||||
|
loader_path = Path(__file__).parents[2].joinpath('templates').absolute().__str__()
|
||||||
|
loader = FileSystemLoader(loader_path)
|
||||||
|
env = Environment(loader=loader)
|
||||||
|
|
||||||
|
|
||||||
|
class AddReagentQuestion(QDialog):
|
||||||
|
"""
|
||||||
|
dialog to ask about adding a new reagne to db
|
||||||
|
"""
|
||||||
|
def __init__(self, reagent_type:str, reagent_lot:str) -> QDialog:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.setWindowTitle(f"Add {reagent_lot}?")
|
||||||
|
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
|
||||||
|
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
message = QLabel(f"Couldn't find reagent type {reagent_type.replace('_', ' ').title().strip('Lot')}: {reagent_lot} in the database.\n\nWould you like to add it?")
|
||||||
|
self.layout.addWidget(message)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
class OverwriteSubQuestion(QDialog):
|
||||||
|
"""
|
||||||
|
dialog to ask about overwriting existing submission
|
||||||
|
"""
|
||||||
|
def __init__(self, message:str, rsl_plate_num:str) -> QDialog:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.setWindowTitle(f"Overwrite {rsl_plate_num}?")
|
||||||
|
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Yes | QDialogButtonBox.StandardButton.No
|
||||||
|
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
message = QLabel(message)
|
||||||
|
self.layout.addWidget(message)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
|
||||||
|
class AlertPop(QMessageBox):
|
||||||
|
|
||||||
|
def __init__(self, message:str, status:str) -> QMessageBox:
|
||||||
|
super().__init__()
|
||||||
|
icon = getattr(QMessageBox.Icon, status.title())
|
||||||
|
self.setIcon(icon)
|
||||||
|
# msg.setText("Error")
|
||||||
|
self.setInformativeText(message)
|
||||||
|
self.setWindowTitle(status.title())
|
||||||
|
|
||||||
188
src/submissions/frontend/custom_widgets/sub_details.py
Normal file
188
src/submissions/frontend/custom_widgets/sub_details.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
from datetime import date
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QVBoxLayout, QDialog, QTableView,
|
||||||
|
QTextEdit, QPushButton, QScrollArea,
|
||||||
|
QMessageBox, QFileDialog
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import Qt, QAbstractTableModel
|
||||||
|
from PyQt6.QtGui import QFontMetrics
|
||||||
|
|
||||||
|
from backend.db import submissions_to_df, lookup_submission_by_id, lookup_all_sample_types, create_kit_from_yaml
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
from xhtml2pdf import pisa
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
loader_path = Path(sys._MEIPASS).joinpath("files", "templates")
|
||||||
|
else:
|
||||||
|
loader_path = Path(__file__).parents[2].joinpath('templates').absolute().__str__()
|
||||||
|
loader = FileSystemLoader(loader_path)
|
||||||
|
env = Environment(loader=loader)
|
||||||
|
|
||||||
|
class pandasModel(QAbstractTableModel):
|
||||||
|
"""
|
||||||
|
pandas model for inserting summary sheet into gui
|
||||||
|
"""
|
||||||
|
def __init__(self, data) -> None:
|
||||||
|
QAbstractTableModel.__init__(self)
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
def rowCount(self, parent=None) -> int:
|
||||||
|
"""
|
||||||
|
does what it says
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (_type_, optional): _description_. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: number of rows in data
|
||||||
|
"""
|
||||||
|
return self._data.shape[0]
|
||||||
|
|
||||||
|
def columnCount(self, parnet=None) -> int:
|
||||||
|
"""
|
||||||
|
does what it says
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parnet (_type_, optional): _description_. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: number of columns in data
|
||||||
|
"""
|
||||||
|
return self._data.shape[1]
|
||||||
|
|
||||||
|
def data(self, index, role=Qt.ItemDataRole.DisplayRole) -> str|None:
|
||||||
|
if index.isValid():
|
||||||
|
if role == Qt.ItemDataRole.DisplayRole:
|
||||||
|
return str(self._data.iloc[index.row(), index.column()])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def headerData(self, col, orientation, role):
|
||||||
|
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
|
||||||
|
return self._data.columns[col]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionsSheet(QTableView):
|
||||||
|
"""
|
||||||
|
presents submission summary to user in tab1
|
||||||
|
"""
|
||||||
|
def __init__(self, ctx:dict) -> None:
|
||||||
|
"""
|
||||||
|
initialize
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx (dict): settings passed from gui
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self.ctx = ctx
|
||||||
|
self.setData()
|
||||||
|
self.resizeColumnsToContents()
|
||||||
|
self.resizeRowsToContents()
|
||||||
|
# self.clicked.connect(self.test)
|
||||||
|
self.doubleClicked.connect(self.show_details)
|
||||||
|
|
||||||
|
def setData(self) -> None:
|
||||||
|
"""
|
||||||
|
sets data in model
|
||||||
|
"""
|
||||||
|
self.data = submissions_to_df(ctx=self.ctx)
|
||||||
|
self.model = pandasModel(self.data)
|
||||||
|
self.setModel(self.model)
|
||||||
|
# self.resize(800,600)
|
||||||
|
|
||||||
|
def show_details(self) -> None:
|
||||||
|
"""
|
||||||
|
creates detailed data to show in seperate window
|
||||||
|
"""
|
||||||
|
index=(self.selectionModel().currentIndex())
|
||||||
|
# logger.debug(index)
|
||||||
|
value=index.sibling(index.row(),0).data()
|
||||||
|
dlg = SubmissionDetails(ctx=self.ctx, id=value)
|
||||||
|
# dlg.show()
|
||||||
|
if dlg.exec():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionDetails(QDialog):
|
||||||
|
"""
|
||||||
|
a window showing text details of submission
|
||||||
|
"""
|
||||||
|
def __init__(self, ctx:dict, id:int) -> None:
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
self.ctx = ctx
|
||||||
|
self.setWindowTitle("Submission Details")
|
||||||
|
|
||||||
|
# create scrollable interior
|
||||||
|
interior = QScrollArea()
|
||||||
|
interior.setParent(self)
|
||||||
|
# get submision from db
|
||||||
|
data = lookup_submission_by_id(ctx=ctx, id=id)
|
||||||
|
self.base_dict = data.to_dict()
|
||||||
|
logger.debug(f"Base dict: {self.base_dict}")
|
||||||
|
# don't want id
|
||||||
|
del self.base_dict['id']
|
||||||
|
# convert sub objects to dicts
|
||||||
|
self.base_dict['reagents'] = [item.to_sub_dict() for item in data.reagents]
|
||||||
|
self.base_dict['samples'] = [item.to_sub_dict() for item in data.samples]
|
||||||
|
# retrieve jinja template
|
||||||
|
template = env.get_template("submission_details.txt")
|
||||||
|
# render using object dict
|
||||||
|
text = template.render(sub=self.base_dict)
|
||||||
|
# create text field
|
||||||
|
txt_editor = QTextEdit(self)
|
||||||
|
txt_editor.setReadOnly(True)
|
||||||
|
txt_editor.document().setPlainText(text)
|
||||||
|
# resize
|
||||||
|
font = txt_editor.document().defaultFont()
|
||||||
|
fontMetrics = QFontMetrics(font)
|
||||||
|
textSize = fontMetrics.size(0, txt_editor.toPlainText())
|
||||||
|
w = textSize.width() + 10
|
||||||
|
h = textSize.height() + 10
|
||||||
|
txt_editor.setMinimumSize(w, h)
|
||||||
|
txt_editor.setMaximumSize(w, h)
|
||||||
|
txt_editor.resize(w, h)
|
||||||
|
interior.resize(w,900)
|
||||||
|
txt_editor.setText(text)
|
||||||
|
interior.setWidget(txt_editor)
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
self.setFixedSize(w, 900)
|
||||||
|
btn = QPushButton("Export PDF")
|
||||||
|
btn.setParent(self)
|
||||||
|
btn.setFixedWidth(w)
|
||||||
|
btn.clicked.connect(self.export)
|
||||||
|
|
||||||
|
|
||||||
|
# def _create_actions(self):
|
||||||
|
# self.exportAction = QAction("Export", self)
|
||||||
|
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
template = env.get_template("submission_details.html")
|
||||||
|
html = template.render(sub=self.base_dict)
|
||||||
|
# logger.debug(f"Submission details: {self.base_dict}")
|
||||||
|
home_dir = Path(self.ctx["directory_path"]).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
|
||||||
|
fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
||||||
|
# logger.debug(f"report output name: {fname}")
|
||||||
|
# df.to_excel(fname, engine='openpyxl')
|
||||||
|
if fname.__str__() == ".":
|
||||||
|
logger.debug("Saving pdf was cancelled.")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(fname, "w+b") as f:
|
||||||
|
pisa.CreatePDF(html, dest=f)
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error(f"Error saving pdf: {e}")
|
||||||
|
msg = QMessageBox()
|
||||||
|
msg.setText("Permission Error")
|
||||||
|
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
||||||
|
msg.setWindowTitle("Permission Error")
|
||||||
|
msg.exec()
|
||||||
|
|
||||||
31
src/submissions/frontend/functions.py
Normal file
31
src/submissions/frontend/functions.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# from ..models import *
|
||||||
|
from backend.db.models import *
|
||||||
|
import logging
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
def check_kit_integrity(sub:BasicSubmission):
|
||||||
|
ext_kit_rtypes = [reagenttype.name for reagenttype in sub.extraction_kit.reagent_types]
|
||||||
|
logger.debug(f"Kit reagents: {ext_kit_rtypes}")
|
||||||
|
reagenttypes = [reagent.type.name for reagent in sub.reagents]
|
||||||
|
logger.debug(f"Submission reagents: {reagenttypes}")
|
||||||
|
check = set(ext_kit_rtypes) == set(reagenttypes)
|
||||||
|
logger.debug(f"Checking if reagents match kit contents: {check}")
|
||||||
|
common = list(set(ext_kit_rtypes).intersection(reagenttypes))
|
||||||
|
logger.debug(f"common reagents types: {common}")
|
||||||
|
if check:
|
||||||
|
result = None
|
||||||
|
else:
|
||||||
|
result = {'message' : f"Couldn't verify reagents match listed kit components.\n\nIt looks like you are missing: {[x.upper() for x in ext_kit_rtypes if x not in common]}\n\nAlternatively, you may have set the wrong extraction kit."}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def check_not_nan(cell_contents) -> bool:
|
||||||
|
try:
|
||||||
|
return not np.isnan(cell_contents)
|
||||||
|
except ValueError:
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Check encounteded unknown error: {e}")
|
||||||
|
return False
|
||||||
Reference in New Issue
Block a user