Various bug fixes for new forms.
This commit is contained in:
@@ -85,10 +85,10 @@ class AddReagentForm(QDialog):
|
||||
Returns:
|
||||
dict: Output info
|
||||
"""
|
||||
return dict(name=self.name_input.currentText(),
|
||||
lot=self.lot_input.text(),
|
||||
return dict(name=self.name_input.currentText().strip(),
|
||||
lot=self.lot_input.text().strip(),
|
||||
expiry=self.exp_input.date().toPyDate(),
|
||||
type=self.type_input.currentText())
|
||||
type=self.type_input.currentText().strip())
|
||||
|
||||
def update_names(self):
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@ from PyQt6.QtWidgets import (
|
||||
)
|
||||
from tools import jinja_template_loading
|
||||
import logging
|
||||
from backend.db.models import KitType, SubmissionType
|
||||
from backend.db import models
|
||||
from typing import Literal
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -45,16 +45,18 @@ class AlertPop(QMessageBox):
|
||||
self.setInformativeText(message)
|
||||
self.setWindowTitle(f"{owner} - {status.title()}")
|
||||
|
||||
class KitSelector(QDialog):
|
||||
class ObjectSelector(QDialog):
|
||||
"""
|
||||
dialog to input KitType manually
|
||||
dialog to input BaseClass type manually
|
||||
"""
|
||||
def __init__(self, title:str, message:str) -> QDialog:
|
||||
def __init__(self, title:str, message:str, obj_type:str|models.BaseClass) -> QDialog:
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
kits = [item.name for item in KitType.query()]
|
||||
self.widget.addItems(kits)
|
||||
if isinstance(obj_type, str):
|
||||
obj_type: models.BaseClass = getattr(models, obj_type)
|
||||
items = [item.name for item in obj_type.query()]
|
||||
self.widget.addItems(items)
|
||||
self.widget.setEditable(False)
|
||||
# set yes/no buttons
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
@@ -78,36 +80,69 @@ class KitSelector(QDialog):
|
||||
"""
|
||||
return self.widget.currentText()
|
||||
|
||||
class SubmissionTypeSelector(QDialog):
|
||||
"""
|
||||
dialog to input SubmissionType manually
|
||||
"""
|
||||
def __init__(self, title:str, message:str) -> QDialog:
|
||||
super().__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.widget = QComboBox()
|
||||
# sub_type = [item.name for item in lookup_submission_type(ctx=ctx)]
|
||||
sub_type = [item.name for item in SubmissionType.query()]
|
||||
self.widget.addItems(sub_type)
|
||||
self.widget.setEditable(False)
|
||||
# set yes/no buttons
|
||||
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 = QVBoxLayout()
|
||||
# Text for the yes/no question
|
||||
message = QLabel(message)
|
||||
self.layout.addWidget(message)
|
||||
self.layout.addWidget(self.widget)
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.setLayout(self.layout)
|
||||
# class KitSelector(QDialog):
|
||||
# """
|
||||
# dialog to input KitType manually
|
||||
# """
|
||||
# def __init__(self, title:str, message:str) -> QDialog:
|
||||
# super().__init__()
|
||||
# self.setWindowTitle(title)
|
||||
# self.widget = QComboBox()
|
||||
# kits = [item.name for item in KitType.query()]
|
||||
# self.widget.addItems(kits)
|
||||
# self.widget.setEditable(False)
|
||||
# # set yes/no buttons
|
||||
# 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 = QVBoxLayout()
|
||||
# # Text for the yes/no question
|
||||
# message = QLabel(message)
|
||||
# self.layout.addWidget(message)
|
||||
# self.layout.addWidget(self.widget)
|
||||
# self.layout.addWidget(self.buttonBox)
|
||||
# self.setLayout(self.layout)
|
||||
|
||||
def parse_form(self) -> str:
|
||||
"""
|
||||
Pulls SubmissionType(str) from widget
|
||||
# def getValues(self) -> str:
|
||||
# """
|
||||
# Get KitType(str) from widget
|
||||
|
||||
Returns:
|
||||
str: SubmissionType as str
|
||||
"""
|
||||
return self.widget.currentText()
|
||||
# Returns:
|
||||
# str: KitType as str
|
||||
# """
|
||||
# return self.widget.currentText()
|
||||
|
||||
# class SubmissionTypeSelector(QDialog):
|
||||
# """
|
||||
# dialog to input SubmissionType manually
|
||||
# """
|
||||
# def __init__(self, title:str, message:str) -> QDialog:
|
||||
# super().__init__()
|
||||
# self.setWindowTitle(title)
|
||||
# self.widget = QComboBox()
|
||||
# # sub_type = [item.name for item in lookup_submission_type(ctx=ctx)]
|
||||
# sub_type = [item.name for item in SubmissionType.query()]
|
||||
# self.widget.addItems(sub_type)
|
||||
# self.widget.setEditable(False)
|
||||
# # set yes/no buttons
|
||||
# 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 = QVBoxLayout()
|
||||
# # Text for the yes/no question
|
||||
# message = QLabel(message)
|
||||
# self.layout.addWidget(message)
|
||||
# self.layout.addWidget(self.widget)
|
||||
# self.layout.addWidget(self.buttonBox)
|
||||
# self.setLayout(self.layout)
|
||||
|
||||
# def parse_form(self) -> str:
|
||||
# """
|
||||
# Pulls SubmissionType(str) from widget
|
||||
|
||||
# Returns:
|
||||
# str: SubmissionType as str
|
||||
# """
|
||||
# return self.widget.currentText()
|
||||
|
||||
@@ -5,12 +5,12 @@ from PyQt6.QtWebChannel import QWebChannel
|
||||
from PyQt6.QtCore import Qt, pyqtSlot
|
||||
|
||||
from backend.db.models import BasicSubmission, BasicSample
|
||||
from tools import check_if_app, check_authorization, is_power_user
|
||||
from tools import is_power_user, html_to_pdf
|
||||
from .functions import select_save_file
|
||||
from io import BytesIO
|
||||
from tempfile import TemporaryFile, TemporaryDirectory
|
||||
from pathlib import Path
|
||||
from xhtml2pdf import pisa
|
||||
# from xhtml2pdf import pisa
|
||||
import logging, base64
|
||||
from getpass import getuser
|
||||
from datetime import datetime
|
||||
@@ -54,6 +54,7 @@ class SubmissionDetails(QDialog):
|
||||
self.channel = QWebChannel()
|
||||
self.channel.registerObject('backend', self)
|
||||
self.submission_details(submission=sub)
|
||||
self.rsl_plate_num = sub.rsl_plate_num
|
||||
self.webview.page().setWebChannel(self.channel)
|
||||
|
||||
@pyqtSlot(str)
|
||||
@@ -86,9 +87,9 @@ class SubmissionDetails(QDialog):
|
||||
logger.debug(f"Submission details data:\n{pformat({k:v for k,v in self.base_dict.items() if k != 'samples'})}")
|
||||
# don't want id
|
||||
del self.base_dict['id']
|
||||
logger.debug(f"Creating barcode.")
|
||||
if not check_if_app():
|
||||
self.base_dict['barcode'] = base64.b64encode(submission.make_plate_barcode(width=120, height=30)).decode('utf-8')
|
||||
# logger.debug(f"Creating barcode.")
|
||||
# if not check_if_app():
|
||||
# self.base_dict['barcode'] = base64.b64encode(submission.make_plate_barcode(width=120, height=30)).decode('utf-8')
|
||||
logger.debug(f"Making platemap...")
|
||||
self.base_dict['platemap'] = submission.make_plate_map()
|
||||
self.base_dict, self.template = submission.get_details_template(base_dict=self.base_dict)
|
||||
@@ -103,8 +104,9 @@ class SubmissionDetails(QDialog):
|
||||
logger.debug(f"Signing off on {submission} - ({getuser()})")
|
||||
if isinstance(submission, str):
|
||||
submission = BasicSubmission.query(rsl_number=submission)
|
||||
submission.uploaded_by = getuser()
|
||||
submission.signed_by = getuser()
|
||||
submission.save()
|
||||
self.submission_details(submission=self.rsl_plate_num)
|
||||
|
||||
def export(self):
|
||||
"""
|
||||
@@ -113,7 +115,7 @@ class SubmissionDetails(QDialog):
|
||||
fname = select_save_file(obj=self, default_name=self.base_dict['Plate Number'], extension="pdf")
|
||||
image_io = BytesIO()
|
||||
temp_dir = Path(TemporaryDirectory().name)
|
||||
hti = Html2Image(output_path=temp_dir, size=(1200, 750))
|
||||
hti = Html2Image(output_path=temp_dir, size=(2400, 1500))
|
||||
temp_file = Path(TemporaryFile(dir=temp_dir, suffix=".png").name)
|
||||
screenshot = hti.screenshot(self.base_dict['platemap'], save_as=temp_file.name)
|
||||
export_map = Image.open(screenshot[0])
|
||||
@@ -126,8 +128,9 @@ class SubmissionDetails(QDialog):
|
||||
del self.base_dict['platemap']
|
||||
self.html2 = self.template.render(sub=self.base_dict)
|
||||
try:
|
||||
with open(fname, "w+b") as f:
|
||||
pisa.CreatePDF(self.html2, dest=f)
|
||||
# with open(fname, "w+b") as f:
|
||||
# pisa.CreatePDF(self.html2, dest=f)
|
||||
html_to_pdf(html=self.html2, output_file=fname)
|
||||
except PermissionError as e:
|
||||
logger.error(f"Error saving pdf: {e}")
|
||||
msg = QMessageBox()
|
||||
|
||||
@@ -8,8 +8,8 @@ from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
||||
from PyQt6.QtGui import QAction, QCursor
|
||||
from backend.db.models import BasicSubmission
|
||||
from backend.excel import make_report_html, make_report_xlsx
|
||||
from tools import Report, Result, row_map, get_first_blank_df_row
|
||||
from xhtml2pdf import pisa
|
||||
from tools import Report, Result, row_map, get_first_blank_df_row, html_to_pdf
|
||||
# from xhtml2pdf import pisa
|
||||
from .functions import select_save_file, select_open_file
|
||||
from .misc import ReportDatePicker
|
||||
import pandas as pd
|
||||
@@ -324,8 +324,9 @@ class SubmissionsSheet(QTableView):
|
||||
html = make_report_html(df=summary_df, start_date=info['start_date'], end_date=info['end_date'])
|
||||
# get save location of report
|
||||
fname = select_save_file(obj=self, default_name=f"Submissions_Report_{info['start_date']}-{info['end_date']}.pdf", extension="pdf")
|
||||
with open(fname, "w+b") as f:
|
||||
pisa.CreatePDF(html, dest=f)
|
||||
# with open(fname, "w+b") as f:
|
||||
# pisa.CreatePDF(html, dest=f)
|
||||
html_to_pdf(html=html, output_file=fname)
|
||||
writer = pd.ExcelWriter(fname.with_suffix(".xlsx"), engine='openpyxl')
|
||||
summary_df.to_excel(writer, sheet_name="Report")
|
||||
detailed_df.to_excel(writer, sheet_name="Details", index=False)
|
||||
|
||||
@@ -135,7 +135,7 @@ class SubmissionFormContainer(QWidget):
|
||||
info = dlg.parse_form()
|
||||
logger.debug(f"Reagent info: {info}")
|
||||
# create reagent object
|
||||
reagent = PydReagent(ctx=self.app.ctx, **info)
|
||||
reagent = PydReagent(ctx=self.app.ctx, **info, missing=False)
|
||||
# send reagent to db
|
||||
sqlobj, result = reagent.toSQL()
|
||||
sqlobj.save()
|
||||
@@ -150,45 +150,29 @@ class SubmissionFormWidget(QWidget):
|
||||
# self.report = Report()
|
||||
self.app = parent.app
|
||||
self.pyd = submission
|
||||
# self.input = [{k:v} for k,v in kwargs.items()]
|
||||
# self.samples = []
|
||||
self.missing_info = []
|
||||
self.ignore = ['filepath', 'samples', 'reagents', 'csv', 'ctx', 'comment',
|
||||
'equipment', 'gel_controls', 'id', 'cost', 'extraction_info',
|
||||
'controls', 'pcr_info', 'gel_info', 'gel_image']
|
||||
self.recover = ['filepath', 'samples', 'csv', 'comment', 'equipment']
|
||||
st = SubmissionType.query(name=self.pyd.submission_type['value']).get_submission_class()
|
||||
defaults = st.get_default_info("form_recover", "form_ignore")
|
||||
self.recover = defaults['form_recover']
|
||||
self.ignore = defaults['form_ignore']
|
||||
# self.ignore += self.recover
|
||||
# logger.debug(f"Attempting to extend ignore list with {self.pyd.submission_type['value']}")
|
||||
self.layout = QVBoxLayout()
|
||||
# for k, v in kwargs.items():
|
||||
for k in list(self.pyd.model_fields.keys()) + list(self.pyd.model_extra.keys()):
|
||||
if k not in self.ignore:
|
||||
try:
|
||||
value = self.pyd.__getattribute__(k)
|
||||
except AttributeError:
|
||||
logger.error(f"Couldn't get attribute from pyd: {k}")
|
||||
value = dict(value=None, missing=True)
|
||||
add_widget = self.create_widget(key=k, value=value, submission_type=self.pyd.submission_type['value'])
|
||||
if add_widget != None:
|
||||
self.layout.addWidget(add_widget)
|
||||
if k == "extraction_kit":
|
||||
add_widget.input.currentTextChanged.connect(self.scrape_reagents)
|
||||
# else:
|
||||
# self.__setattr__(k, v)
|
||||
# self.scrape_reagents(self.extraction_kit['value'])
|
||||
if k in self.ignore:
|
||||
continue
|
||||
try:
|
||||
value = self.pyd.__getattribute__(k)
|
||||
except AttributeError:
|
||||
logger.error(f"Couldn't get attribute from pyd: {k}")
|
||||
value = dict(value=None, missing=True)
|
||||
add_widget = self.create_widget(key=k, value=value, submission_type=self.pyd.submission_type['value'])
|
||||
if add_widget != None:
|
||||
self.layout.addWidget(add_widget)
|
||||
if k == "extraction_kit":
|
||||
add_widget.input.currentTextChanged.connect(self.scrape_reagents)
|
||||
self.scrape_reagents(self.pyd.extraction_kit)
|
||||
# extraction kit must be added last so widget order makes sense.
|
||||
# self.layout.addWidget(self.create_widget(key="extraction_kit", value=self.extraction_kit, submission_type=self.submission_type))
|
||||
# if hasattr(self.pyd, "csv"):
|
||||
# export_csv_btn = QPushButton("Export CSV")
|
||||
# export_csv_btn.setObjectName("export_csv_btn")
|
||||
# self.layout.addWidget(export_csv_btn)
|
||||
# export_csv_btn.clicked.connect(self.export_csv_function)
|
||||
# submit_btn = QPushButton("Submit")
|
||||
# submit_btn.setObjectName("submit_btn")
|
||||
# self.layout.addWidget(submit_btn)
|
||||
# submit_btn.clicked.connect(self.submit_new_sample_function)
|
||||
# self.setLayout(self.layout)
|
||||
# self.app.report.add_result(self.report)
|
||||
# self.app.result_reporter()
|
||||
|
||||
def create_widget(self, key:str, value:dict|PydReagent, submission_type:str|None=None, extraction_kit:str|None=None) -> "self.InfoItem":
|
||||
"""
|
||||
@@ -633,6 +617,11 @@ class SubmissionFormWidget(QWidget):
|
||||
self.reagent = reagent
|
||||
self.extraction_kit = extraction_kit
|
||||
layout = QVBoxLayout()
|
||||
# layout = QGridLayout()
|
||||
# self.check_box = QCheckBox(self)
|
||||
# self.check_box.setChecked(True)
|
||||
# self.check_box.stateChanged.connect(self.check_uncheck)
|
||||
# layout.addWidget(self.check_box, 0,0)
|
||||
self.label = self.ReagentParsedLabel(reagent=reagent)
|
||||
layout.addWidget(self.label)
|
||||
self.lot = self.ReagentLot(reagent=reagent, extraction_kit=extraction_kit)
|
||||
@@ -645,6 +634,14 @@ class SubmissionFormWidget(QWidget):
|
||||
# If changed set self.missing to True and update self.label
|
||||
self.lot.currentTextChanged.connect(self.updated)
|
||||
|
||||
# def check_uncheck(self):
|
||||
# if self.check_box.isChecked():
|
||||
# self.lot.setCurrentIndex(0)
|
||||
# self.lot.setEnabled(True)
|
||||
# else:
|
||||
# self.lot.setCurrentText("Not Applicable")
|
||||
# self.lot.setEnabled(False)
|
||||
|
||||
def parse_form(self) -> Tuple[PydReagent, dict]:
|
||||
"""
|
||||
Pulls form info into PydReagent
|
||||
@@ -652,6 +649,8 @@ class SubmissionFormWidget(QWidget):
|
||||
Returns:
|
||||
Tuple[PydReagent, dict]: PydReagent and Report(?)
|
||||
"""
|
||||
# if not self.check_box.isChecked():
|
||||
# return None, None
|
||||
lot = self.lot.currentText()
|
||||
logger.debug(f"Using this lot for the reagent {self.reagent}: {lot}")
|
||||
wanted_reagent = Reagent.query(lot_number=lot, reagent_type=self.reagent.type)
|
||||
@@ -671,7 +670,7 @@ class SubmissionFormWidget(QWidget):
|
||||
rt = ReagentType.query(name=self.reagent.type)
|
||||
if rt == None:
|
||||
rt = ReagentType.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
||||
return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, type=rt.name, expiry=wanted_reagent.expiry, parsed=not self.missing), None
|
||||
return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, type=rt.name, expiry=wanted_reagent.expiry, missing=False), None
|
||||
|
||||
def updated(self):
|
||||
"""
|
||||
@@ -736,8 +735,11 @@ class SubmissionFormWidget(QWidget):
|
||||
looked_up_reg = None
|
||||
# logger.debug(f"Because there was no reagent listed for {reagent.lot}, we will insert the last lot used: {looked_up_reg}")
|
||||
if looked_up_reg != None:
|
||||
relevant_reagents.remove(str(looked_up_reg.lot))
|
||||
relevant_reagents.insert(0, str(looked_up_reg.lot))
|
||||
try:
|
||||
relevant_reagents.remove(str(looked_up_reg.lot))
|
||||
relevant_reagents.insert(0, str(looked_up_reg.lot))
|
||||
except ValueError as e:
|
||||
logger.error(f"Error reordering relevant reagents: {e}")
|
||||
else:
|
||||
if len(relevant_reagents) > 1:
|
||||
# logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. Moving to front of list.")
|
||||
|
||||
Reference in New Issue
Block a user