Moments before disaster
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
## 202403.01
|
||||||
|
|
||||||
|
- Added navigation in submission details to sample details.
|
||||||
|
- Updated cost calculations.
|
||||||
|
|
||||||
## 202402.04
|
## 202402.04
|
||||||
|
|
||||||
- Addition of comments to gel box.
|
- Addition of comments to gel box.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Contains all models for sqlalchemy
|
|||||||
import sys
|
import sys
|
||||||
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query
|
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
# Load testing environment
|
||||||
if 'pytest' in sys.modules:
|
if 'pytest' in sys.modules:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
|
sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__())
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Models for the main submission types.
|
|||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
import math, json, logging, uuid, tempfile, re, yaml, base64
|
import json, logging, uuid, tempfile, re, yaml, base64
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from reportlab.graphics.barcode import createBarcodeImageInMemory
|
from reportlab.graphics.barcode import createBarcodeImageInMemory
|
||||||
@@ -237,6 +237,7 @@ class BasicSubmission(BaseClass):
|
|||||||
self.run_cost = assoc.constant_cost + (assoc.mutable_cost_column * cols_count_96) + (assoc.mutable_cost_sample * int(self.sample_count))
|
self.run_cost = assoc.constant_cost + (assoc.mutable_cost_column * cols_count_96) + (assoc.mutable_cost_sample * int(self.sample_count))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Calculation error: {e}")
|
logger.error(f"Calculation error: {e}")
|
||||||
|
self.run_cost = round(self.run_cost, 2)
|
||||||
|
|
||||||
def calculate_column_count(self) -> int:
|
def calculate_column_count(self) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -338,7 +339,7 @@ class BasicSubmission(BaseClass):
|
|||||||
logger.debug(f"Got {len(subs)} submissions.")
|
logger.debug(f"Got {len(subs)} submissions.")
|
||||||
df = pd.DataFrame.from_records(subs)
|
df = pd.DataFrame.from_records(subs)
|
||||||
# Exclude sub information
|
# Exclude sub information
|
||||||
for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents', 'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number']:
|
for item in ['controls', 'extraction_info', 'pcr_info', 'comment', 'comments', 'samples', 'reagents', 'equipment', 'gel_info', 'gel_image', 'dna_core_submission_number', 'source_plates']:
|
||||||
try:
|
try:
|
||||||
df = df.drop(item, axis=1)
|
df = df.drop(item, axis=1)
|
||||||
except:
|
except:
|
||||||
@@ -873,8 +874,8 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: dictionary of functions
|
dict: dictionary of functions
|
||||||
"""
|
"""
|
||||||
names = ["Delete", "Details", "Add Comment", "Add Equipment", "Export"]
|
names = ["Delete", "Details", "Edit", "Add Comment", "Add Equipment", "Export"]
|
||||||
funcs = [self.delete, self.show_details, self.add_comment, self.add_equipment, self.backup]
|
funcs = [self.delete, self.show_details, self.edit, self.add_comment, self.add_equipment, self.backup]
|
||||||
dicto = {item[0]:item[1] for item in zip(names, funcs)}
|
dicto = {item[0]:item[1] for item in zip(names, funcs)}
|
||||||
return dicto
|
return dicto
|
||||||
|
|
||||||
@@ -915,6 +916,15 @@ class BasicSubmission(BaseClass):
|
|||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def edit(self, obj):
|
||||||
|
from frontend.widgets.submission_widget import SubmissionFormWidget
|
||||||
|
for widg in obj.app.table_widget.formwidget.findChildren(SubmissionFormWidget):
|
||||||
|
logger.debug(widg)
|
||||||
|
widg.setParent(None)
|
||||||
|
pyd = self.to_pydantic(backup=True)
|
||||||
|
form = pyd.toForm(parent=obj)
|
||||||
|
obj.app.table_widget.formwidget.layout().addWidget(form)
|
||||||
|
|
||||||
def add_comment(self, obj):
|
def add_comment(self, obj):
|
||||||
"""
|
"""
|
||||||
Creates widget for adding comments to submissions
|
Creates widget for adding comments to submissions
|
||||||
@@ -1300,25 +1310,6 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
polymorphic_load="inline",
|
polymorphic_load="inline",
|
||||||
inherit_condition=(id == BasicSubmission.id))
|
inherit_condition=(id == BasicSubmission.id))
|
||||||
|
|
||||||
def calculate_base_cost(self):
|
|
||||||
"""
|
|
||||||
This method overrides parent method due to multiple output plates from a single submission
|
|
||||||
"""
|
|
||||||
logger.debug(f"Hello from calculate base cost in WWArtic")
|
|
||||||
try:
|
|
||||||
cols_count_96 = math.ceil(int(self.sample_count) / 8)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Column count error: {e}")
|
|
||||||
assoc = [item for item in self.extraction_kit.kit_submissiontype_associations if item.submission_type == self.submission_type][0]
|
|
||||||
# Since we have multiple output plates per submission form, the constant cost will have to reflect this.
|
|
||||||
output_plate_count = math.ceil(int(self.sample_count) / 16)
|
|
||||||
logger.debug(f"Looks like we have {output_plate_count} output plates.")
|
|
||||||
const_cost = assoc.constant_cost * output_plate_count
|
|
||||||
try:
|
|
||||||
self.run_cost = const_cost + (assoc.mutable_cost_column * cols_count_96) + (assoc.mutable_cost_sample * int(self.sample_count))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Calculation error: {e}")
|
|
||||||
|
|
||||||
def to_dict(self, full_data:bool=False, backup:bool=False) -> dict:
|
def to_dict(self, full_data:bool=False, backup:bool=False) -> dict:
|
||||||
"""
|
"""
|
||||||
Extends parent class method to add controls to dict
|
Extends parent class method to add controls to dict
|
||||||
@@ -1859,7 +1850,7 @@ class BasicSample(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple(dict, Template): (Updated dictionary, Template to be rendered)
|
Tuple(dict, Template): (Updated dictionary, Template to be rendered)
|
||||||
"""
|
"""
|
||||||
base_dict['excluded'] = ['submissions', 'excluded']
|
base_dict['excluded'] = ['submissions', 'excluded', 'colour', 'tooltip']
|
||||||
env = jinja_template_loading()
|
env = jinja_template_loading()
|
||||||
temp_name = f"{cls.__name__.lower()}_details.html"
|
temp_name = f"{cls.__name__.lower()}_details.html"
|
||||||
logger.debug(f"Returning template: {temp_name}")
|
logger.debug(f"Returning template: {temp_name}")
|
||||||
|
|||||||
@@ -33,21 +33,10 @@ class SubmissionDetails(QDialog):
|
|||||||
self.app = parent.parent().parent().parent().parent().parent().parent()
|
self.app = parent.parent().parent().parent().parent().parent().parent()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.app = None
|
self.app = None
|
||||||
self.setWindowTitle(f"Submission Details - {sub.rsl_plate_num}")
|
# self.setWindowTitle(f"Submission Details - {sub.rsl_plate_num}")
|
||||||
# create scrollable interior
|
# create scrollable interior
|
||||||
interior = QScrollArea()
|
interior = QScrollArea()
|
||||||
interior.setParent(self)
|
interior.setParent(self)
|
||||||
# self.base_dict = sub.to_dict(full_data=True)
|
|
||||||
# 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(sub.make_plate_barcode(width=120, height=30)).decode('utf-8')
|
|
||||||
# logger.debug(f"Making platemap...")
|
|
||||||
# self.base_dict['platemap'] = sub.make_plate_map()
|
|
||||||
# self.base_dict, self.template = sub.get_details_template(base_dict=self.base_dict)
|
|
||||||
# self.html = self.template.render(sub=self.base_dict)
|
|
||||||
self.webview = QWebEngineView(parent=self)
|
self.webview = QWebEngineView(parent=self)
|
||||||
self.webview.setMinimumSize(900, 500)
|
self.webview.setMinimumSize(900, 500)
|
||||||
self.webview.setMaximumSize(900, 500)
|
self.webview.setMaximumSize(900, 500)
|
||||||
@@ -80,6 +69,7 @@ class SubmissionDetails(QDialog):
|
|||||||
base_dict, template = sample.get_details_template(base_dict=base_dict)
|
base_dict, template = sample.get_details_template(base_dict=base_dict)
|
||||||
html = template.render(sample=base_dict)
|
html = template.render(sample=base_dict)
|
||||||
self.webview.setHtml(html)
|
self.webview.setHtml(html)
|
||||||
|
self.setWindowTitle(f"Sample Details - {sample.submitter_id}")
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def submission_details(self, submission:str|BasicSubmission):
|
def submission_details(self, submission:str|BasicSubmission):
|
||||||
@@ -104,6 +94,7 @@ class SubmissionDetails(QDialog):
|
|||||||
self.base_dict, self.template = submission.get_details_template(base_dict=self.base_dict)
|
self.base_dict, self.template = submission.get_details_template(base_dict=self.base_dict)
|
||||||
self.html = self.template.render(sub=self.base_dict)
|
self.html = self.template.render(sub=self.base_dict)
|
||||||
self.webview.setHtml(self.html)
|
self.webview.setHtml(self.html)
|
||||||
|
self.setWindowTitle(f"Submission Details - {submission.rsl_plate_num}")
|
||||||
|
|
||||||
def export(self):
|
def export(self):
|
||||||
"""
|
"""
|
||||||
@@ -124,8 +115,6 @@ class SubmissionDetails(QDialog):
|
|||||||
self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
||||||
del self.base_dict['platemap']
|
del self.base_dict['platemap']
|
||||||
self.html2 = self.template.render(sub=self.base_dict)
|
self.html2 = self.template.render(sub=self.base_dict)
|
||||||
with open("test.html", "w") as fw:
|
|
||||||
fw.write(self.html2)
|
|
||||||
try:
|
try:
|
||||||
with open(fname, "w+b") as f:
|
with open(fname, "w+b") as f:
|
||||||
pisa.CreatePDF(self.html2, dest=f)
|
pisa.CreatePDF(self.html2, dest=f)
|
||||||
|
|||||||
@@ -148,11 +148,11 @@ class SubmissionFormContainer(QWidget):
|
|||||||
logger.debug(f"Pydantic result: \n\n{pformat(self.pyd)}\n\n")
|
logger.debug(f"Pydantic result: \n\n{pformat(self.pyd)}\n\n")
|
||||||
self.form = self.pyd.toForm(parent=self)
|
self.form = self.pyd.toForm(parent=self)
|
||||||
self.layout().addWidget(self.form)
|
self.layout().addWidget(self.form)
|
||||||
kit_widget = self.form.find_widgets(object_name="extraction_kit")[0].input
|
# kit_widget = self.form.find_widgets(object_name="extraction_kit")[0].input
|
||||||
logger.debug(f"Kitwidget {kit_widget}")
|
# logger.debug(f"Kitwidget {kit_widget}")
|
||||||
self.scrape_reagents(kit_widget.currentText())
|
# self.scrape_reagents(kit_widget.currentText())
|
||||||
kit_widget.currentTextChanged.connect(self.scrape_reagents)
|
# kit_widget.currentTextChanged.connect(self.scrape_reagents)
|
||||||
# compare obj.reagents with expected reagents in kit
|
# # compare obj.reagents with expected reagents in kit
|
||||||
if self.prsr.sample_result != None:
|
if self.prsr.sample_result != None:
|
||||||
report.add_result(msg=self.prsr.sample_result, status="Warning")
|
report.add_result(msg=self.prsr.sample_result, status="Warning")
|
||||||
self.report.add_result(report)
|
self.report.add_result(report)
|
||||||
@@ -220,7 +220,8 @@ class SubmissionFormContainer(QWidget):
|
|||||||
for reagent in self.form.reagents:
|
for reagent in self.form.reagents:
|
||||||
logger.debug(f"Creating widget for {reagent}")
|
logger.debug(f"Creating widget for {reagent}")
|
||||||
add_widget = ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.ext_kit)
|
add_widget = ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.ext_kit)
|
||||||
self.form.layout().addWidget(add_widget)
|
# self.form.layout().addWidget(add_widget)
|
||||||
|
self.form.layout.addWidget(add_widget)
|
||||||
if reagent.missing:
|
if reagent.missing:
|
||||||
missing_reagents.append(reagent)
|
missing_reagents.append(reagent)
|
||||||
logger.debug(f"Checking integrity of {self.ext_kit}")
|
logger.debug(f"Checking integrity of {self.ext_kit}")
|
||||||
@@ -397,15 +398,15 @@ class SubmissionFormWidget(QWidget):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.ignore = ['filepath', 'samples', 'reagents', 'csv', 'ctx', 'comment', 'equipment', 'source_plates']
|
self.ignore = ['filepath', 'samples', 'reagents', 'csv', 'ctx', 'comment', 'equipment', 'source_plates']
|
||||||
self.recover = ['filepath', 'samples', 'csv', 'comment', 'equipment']
|
self.recover = ['filepath', 'samples', 'csv', 'comment', 'equipment']
|
||||||
layout = QVBoxLayout()
|
self.layout = QVBoxLayout()
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
if k not in self.ignore:
|
if k not in self.ignore:
|
||||||
add_widget = self.create_widget(key=k, value=v, submission_type=kwargs['submission_type'])
|
add_widget = self.create_widget(key=k, value=v, submission_type=kwargs['submission_type'])
|
||||||
if add_widget != None:
|
if add_widget != None:
|
||||||
layout.addWidget(add_widget)
|
self.layout.addWidget(add_widget)
|
||||||
else:
|
else:
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
self.setLayout(layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
def create_widget(self, key:str, value:dict, submission_type:str|None=None) -> "self.InfoItem":
|
def create_widget(self, key:str, value:dict, submission_type:str|None=None) -> "self.InfoItem":
|
||||||
"""
|
"""
|
||||||
@@ -420,7 +421,15 @@ class SubmissionFormWidget(QWidget):
|
|||||||
self.InfoItem: Form widget to hold name:value
|
self.InfoItem: Form widget to hold name:value
|
||||||
"""
|
"""
|
||||||
if key not in self.ignore:
|
if key not in self.ignore:
|
||||||
return self.InfoItem(self, key=key, value=value, submission_type=submission_type)
|
widget = self.InfoItem(self, key=key, value=value, submission_type=submission_type)
|
||||||
|
# match key:
|
||||||
|
# case "extraction_kit":
|
||||||
|
# # compare obj.reagents with expected reagents in kit
|
||||||
|
# self.scrape_reagents(widget.currentText())
|
||||||
|
# widget.currentTextChanged.connect(self.scrape_reagents)
|
||||||
|
# case _:
|
||||||
|
# pass
|
||||||
|
return widget
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def clear_form(self):
|
def clear_form(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user