diff --git a/CHANGELOG.md b/CHANGELOG.md index 21fb47c..d7cc905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 202403.01 + +- Added navigation in submission details to sample details. +- Updated cost calculations. + ## 202402.04 - Addition of comments to gel box. diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index 9e03119..7c6fade 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -4,6 +4,7 @@ Contains all models for sqlalchemy import sys from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query from sqlalchemy.ext.declarative import declared_attr +# Load testing environment if 'pytest' in sys.modules: from pathlib import Path sys.path.append(Path(__file__).parents[4].absolute().joinpath("tests").__str__()) diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index cfac895..a60d827 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -3,7 +3,7 @@ Models for the main submission types. ''' from __future__ import annotations 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 tempfile import TemporaryDirectory 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)) except Exception as e: logger.error(f"Calculation error: {e}") + self.run_cost = round(self.run_cost, 2) def calculate_column_count(self) -> int: """ @@ -338,7 +339,7 @@ class BasicSubmission(BaseClass): logger.debug(f"Got {len(subs)} submissions.") df = pd.DataFrame.from_records(subs) # 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: df = df.drop(item, axis=1) except: @@ -873,8 +874,8 @@ class BasicSubmission(BaseClass): Returns: dict: dictionary of functions """ - names = ["Delete", "Details", "Add Comment", "Add Equipment", "Export"] - funcs = [self.delete, self.show_details, self.add_comment, self.add_equipment, self.backup] + names = ["Delete", "Details", "Edit", "Add Comment", "Add Equipment", "Export"] + 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)} return dicto @@ -915,6 +916,15 @@ class BasicSubmission(BaseClass): if dlg.exec(): 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): """ Creates widget for adding comments to submissions @@ -1300,25 +1310,6 @@ class WastewaterArtic(BasicSubmission): polymorphic_load="inline", 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: """ Extends parent class method to add controls to dict @@ -1859,7 +1850,7 @@ class BasicSample(BaseClass): Returns: 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() temp_name = f"{cls.__name__.lower()}_details.html" logger.debug(f"Returning template: {temp_name}") diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index c4ac06c..680c8ac 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -33,21 +33,10 @@ class SubmissionDetails(QDialog): self.app = parent.parent().parent().parent().parent().parent().parent() except AttributeError: self.app = None - self.setWindowTitle(f"Submission Details - {sub.rsl_plate_num}") + # self.setWindowTitle(f"Submission Details - {sub.rsl_plate_num}") # create scrollable interior interior = QScrollArea() 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.setMinimumSize(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) html = template.render(sample=base_dict) self.webview.setHtml(html) + self.setWindowTitle(f"Sample Details - {sample.submitter_id}") @pyqtSlot(str) 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.html = self.template.render(sub=self.base_dict) self.webview.setHtml(self.html) + self.setWindowTitle(f"Submission Details - {submission.rsl_plate_num}") def export(self): """ @@ -124,8 +115,6 @@ class SubmissionDetails(QDialog): self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8') del self.base_dict['platemap'] self.html2 = self.template.render(sub=self.base_dict) - with open("test.html", "w") as fw: - fw.write(self.html2) try: with open(fname, "w+b") as f: pisa.CreatePDF(self.html2, dest=f) diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index 54016b9..1d5e3f8 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -148,11 +148,11 @@ class SubmissionFormContainer(QWidget): logger.debug(f"Pydantic result: \n\n{pformat(self.pyd)}\n\n") self.form = self.pyd.toForm(parent=self) self.layout().addWidget(self.form) - kit_widget = self.form.find_widgets(object_name="extraction_kit")[0].input - logger.debug(f"Kitwidget {kit_widget}") - self.scrape_reagents(kit_widget.currentText()) - kit_widget.currentTextChanged.connect(self.scrape_reagents) - # compare obj.reagents with expected reagents in kit + # kit_widget = self.form.find_widgets(object_name="extraction_kit")[0].input + # logger.debug(f"Kitwidget {kit_widget}") + # self.scrape_reagents(kit_widget.currentText()) + # kit_widget.currentTextChanged.connect(self.scrape_reagents) + # # compare obj.reagents with expected reagents in kit if self.prsr.sample_result != None: report.add_result(msg=self.prsr.sample_result, status="Warning") self.report.add_result(report) @@ -220,7 +220,8 @@ class SubmissionFormContainer(QWidget): for reagent in self.form.reagents: logger.debug(f"Creating widget for {reagent}") 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: missing_reagents.append(reagent) logger.debug(f"Checking integrity of {self.ext_kit}") @@ -397,15 +398,15 @@ class SubmissionFormWidget(QWidget): super().__init__(parent) self.ignore = ['filepath', 'samples', 'reagents', 'csv', 'ctx', 'comment', 'equipment', 'source_plates'] self.recover = ['filepath', 'samples', 'csv', 'comment', 'equipment'] - layout = QVBoxLayout() + self.layout = QVBoxLayout() for k, v in kwargs.items(): if k not in self.ignore: add_widget = self.create_widget(key=k, value=v, submission_type=kwargs['submission_type']) if add_widget != None: - layout.addWidget(add_widget) + self.layout.addWidget(add_widget) else: 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": """ @@ -420,7 +421,15 @@ class SubmissionFormWidget(QWidget): self.InfoItem: Form widget to hold name:value """ 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 def clear_form(self):