From 53c6668ce164bac5eae597712a7fbeb4d68e980e Mon Sep 17 00:00:00 2001 From: lwark Date: Wed, 16 Jul 2025 14:12:03 -0500 Subject: [PATCH] Updates to details interface. --- src/submissions/backend/db/models/__init__.py | 62 +++++++++++++++++-- src/submissions/backend/db/models/kits.py | 37 ++++++++--- .../backend/db/models/submissions.py | 58 ++++++++++++++--- src/submissions/backend/validators/pydant.py | 44 +++++++++++-- .../frontend/widgets/procedure_creation.py | 25 ++++++++ .../frontend/widgets/submission_details.py | 11 ++-- .../frontend/widgets/submission_widget.py | 23 ++++--- .../templates/clientsubmission_details.html | 14 ++--- src/submissions/templates/details.html | 17 +---- .../templates/procedure_details.html | 40 ++++++++++-- src/submissions/templates/run_details.html | 10 +-- .../templates/support/reagent_list.html | 6 ++ 12 files changed, 276 insertions(+), 71 deletions(-) create mode 100644 src/submissions/templates/support/reagent_list.html diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index 27490b0..27f0f81 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -342,6 +342,18 @@ class BaseClass(Base): Add the object to the database and commit """ report = Report() + del_keys = [] + try: + items = self._misc_info.items() + except AttributeError: + items = [] + for key, value in items: + try: + json.dumps(value) + except TypeError as e: + del_keys.append(key) + for dk in del_keys: + del self._misc_info[dk] try: self.__database_session__.add(self) self.__database_session__.commit() @@ -566,7 +578,7 @@ class BaseClass(Base): else: return super().__setattr__(key, value) - def delete(self): + def delete(self, **kwargs): logger.error(f"Delete has not been implemented for {self.__class__.__name__}") def rectify_query_date(input_date: datetime, eod: bool = False) -> str: @@ -600,6 +612,7 @@ class BaseClass(Base): relevant = {k: v for k, v in self.__class__.__dict__.items() if isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)} output = {} + output['excluded'] = ["excluded", "misc_info", "_misc_info", "id"] for k, v in relevant.items(): try: check = v.foreign_keys @@ -611,14 +624,55 @@ class BaseClass(Base): value = getattr(self, k) except AttributeError: continue + # match value: + # case datetime(): + # value = value.strftime("%Y-%m-%d %H:%M:%S") + # case bytes(): + # continue + # case dict(): + # try: + # value = value['name'] + # except KeyError: + # if k == "_misc_info": + # value = value + # else: + # continue + # case _: + # pass + output[k.strip("_")] = value + # output = self.clean_details_dict(output) + return output + + @classmethod + def clean_details_dict(cls, dictionary): + output = {} + for k, value in dictionary.items(): match value: - case datetime(): - value = value.strftime("%Y-%m-%d %H:%M:%S") + case datetime() | date(): + value = value.strftime("%Y-%m-%d") + case bytes(): + continue + case dict(): + try: + value = value['name'] + except KeyError: + if k == "_misc_info" or k == "misc_info": + value = value + else: + continue + case x if issubclass(value.__class__, cls): + try: + value = value.name + except AttributeError: + continue case _: pass - output[k.strip("_")] = value + output[k] = value + return output + + def to_pydantic(self, pyd_model_name:str|None=None, **kwargs): from backend.validators import pydant if not pyd_model_name: diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index bbe796c..ad5b4c0 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -17,6 +17,9 @@ from pandas import ExcelFile from pathlib import Path from . import Base, BaseClass, ClientLab, LogMixin from io import BytesIO +from sqlalchemy.exc import OperationalError as AlcOperationalError, IntegrityError as AlcIntegrityError, StatementError, \ + ArgumentError +from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as SQLIntegrityError if TYPE_CHECKING: from backend.db.models.submissions import Run, ProcedureSampleAssociation @@ -804,6 +807,8 @@ class Reagent(BaseClass, LogMixin): output = super().details_dict() if reagentrole: output['reagentrole'] = reagentrole + else: + output['reagentrole'] = self.reagentrole[0].name return output @@ -1358,6 +1363,8 @@ class Procedure(BaseClass): id = Column(INTEGER, primary_key=True) name = Column(String, unique=True) repeat = Column(INTEGER, nullable=False) + started_date = Column(TIMESTAMP) + completed_date = Column(TIMESTAMP) technician = Column(String(64)) #: name of processing tech(s) results = relationship("Results", back_populates="procedure", uselist=True) @@ -1510,7 +1517,8 @@ class Procedure(BaseClass): def details_dict(self, **kwargs): output = super().details_dict() output['kittype'] = output['kittype'].details_dict() - output['proceduretype'] = output['proceduretype'].details_dict() + output['kit_type'] = self.kittype.name + output['proceduretype'] = output['proceduretype'].details_dict()['name'] output['results'] = [result.details_dict() for result in output['results']] run_samples = [sample for sample in self.run.sample] active_samples = [sample.details_dict() for sample in output['proceduresampleassociation'] @@ -1530,10 +1538,11 @@ class Procedure(BaseClass): output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']] output['tips'] = [tips.details_dict() for tips in output['proceduretipsassociation']] output['repeat'] = bool(output['repeat']) - output['excluded'] = ['id', "results", "proceduresampleassociation", "sample", "procedurereagentassociation", + output['run'] = self.run.name + output['excluded'] += ['id', "results", "proceduresampleassociation", "sample", "procedurereagentassociation", "procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment", - "tips", - "excluded"] + "tips", "control", "kittype"] + # output = self.clean_details_dict(output) return output def to_pydantic(self, **kwargs): @@ -1546,7 +1555,7 @@ class Procedure(BaseClass): for reagent in output.reagent: match reagent: case dict(): - reagent['reagentrole'] = next((reagentrole.name for reagentrole in self.kittype.reagentrole if reagentrole in reagent['reagentrole']), None) + reagent['reagentrole'] = next((reagentrole.name for reagentrole in self.kittype.reagentrole if reagentrole == reagent['reagentrole']), None) reagents.append(PydReagent(**reagent)) case PydReagent(): reagents.append(reagent) @@ -1942,6 +1951,7 @@ class ProcedureReagentAssociation(BaseClass): reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure + reagentrole = Column(String(64)) comments = Column(String(1024)) #: Comments about reagents procedure = relationship("Procedure", @@ -1960,12 +1970,13 @@ class ProcedureReagentAssociation(BaseClass): logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!") return f"" - def __init__(self, reagent=None, procedure=None): + def __init__(self, reagent=None, procedure=None, reagentrole=""): if isinstance(reagent, list): logger.warning(f"Got list for reagent. Likely no lot was provided. Using {reagent[0]}") reagent = reagent[0] self.reagent = reagent self.procedure = procedure + self.reagentrole = reagentrole self.comments = "" @classmethod @@ -1973,6 +1984,7 @@ class ProcedureReagentAssociation(BaseClass): def query(cls, procedure: Procedure | str | int | None = None, reagent: Reagent | str | None = None, + reagentrole: str | None = None, limit: int = 0) -> ProcedureReagentAssociation | List[ProcedureReagentAssociation]: """ Lookup SubmissionReagentAssociations of interest. @@ -2003,6 +2015,8 @@ class ProcedureReagentAssociation(BaseClass): query = query.join(Procedure).filter(Procedure.id == procedure) case _: pass + if reagentrole: + query = query.filter(cls.reagentrole==reagentrole) return cls.execute_query(query=query, limit=limit) def to_sub_dict(self, kittype) -> dict: @@ -2034,6 +2048,14 @@ class ProcedureReagentAssociation(BaseClass): # output['results'] = [result.details_dict() for result in output['results']] return output + def delete(self, **kwargs): + self.__database_session__.delete(self) + try: + self.__database_session__.commit() + except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e: + self.__database_session__.rollback() + raise e + class Equipment(BaseClass, LogMixin): """ @@ -2547,6 +2569,7 @@ class ProcedureEquipmentAssociation(BaseClass): misc = output['misc_info'] output.update(relevant) output['misc_info'] = misc + output['equipment_role'] = self.equipmentrole output['process'] = self.process.details_dict() try: output['tips'] = self.tips.details_dict() @@ -2752,7 +2775,7 @@ class Process(BaseClass): output = {} for k, v in self.details_dict().items(): if isinstance(v, list): - output[k] = [item.name for item in v] + output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v] elif issubclass(v.__class__, BaseClass): output[k] = v.name else: diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 366fc41..2f1d7fb 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -288,7 +288,7 @@ class ClientSubmission(BaseClass, LogMixin): except AssertionError: logger.warning(f"Converting {sample} to sql.") sample = sample.to_sql() - logger.debug(sample.__dict__) + # logger.debug(sample.__dict__) try: row = sample._misc_info['row'] except (KeyError, AttributeError): @@ -297,8 +297,10 @@ class ClientSubmission(BaseClass, LogMixin): column = sample._misc_info['column'] except KeyError: column = 0 - logger.debug(f"Sample: {sample}") + # logger.debug(f"Sample: {sample}") submission_rank = sample._misc_info['submission_rank'] + if sample in self.sample: + return assoc = ClientSubmissionSampleAssociation( sample=sample, submission=self, @@ -362,9 +364,11 @@ class ClientSubmission(BaseClass, LogMixin): output['name'] = self.name output['client_lab'] = output['clientlab'] output['submission_type'] = output['submissiontype'] - output['excluded'] = ['run', "sample", "clientsubmissionsampleassociation", "excluded", + output['excluded'] += ['run', "sample", "clientsubmissionsampleassociation", "excluded", "expanded", 'clientlab', 'submissiontype', 'id'] output['expanded'] = ["clientlab", "contact", "submissiontype"] + # output = self.clean_details_dict(output) + logger.debug(f"{self.__class__.__name__}\n\n{pformat(output)}") return output def to_pydantic(self, filepath: Path | str | None = None, **kwargs): @@ -383,14 +387,14 @@ class Run(BaseClass, LogMixin): clientsubmission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL", name="fk_BS_clientsub_id")) #: client lab id from _organizations) clientsubmission = relationship("ClientSubmission", back_populates="run") - started_date = Column(TIMESTAMP) #: Date this procedure was started. + _started_date = Column(TIMESTAMP) #: Date this procedure was started. run_cost = Column( FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kittype costs at time of creation. signed_by = Column(String(32)) #: user name of person who submitted the procedure to the database. comment = Column(JSON) #: user notes custom = Column(JSON) - completed_date = Column(TIMESTAMP) + _completed_date = Column(TIMESTAMP) procedure = relationship("Procedure", back_populates="run", uselist=True) @@ -429,6 +433,38 @@ class Run(BaseClass, LogMixin): def plate_number(self): return self.rsl_plate_number + @hybrid_property + def started_date(self): + if self._started_date: + return self._started_date + else: + value = min([proc.started_date for proc in self.procedure]) + return value + + @started_date.setter + def started_date(self, value): + if value: + self._started_date = value + else: + self._started_date = min([proc.started_date for proc in self.procedure]) + + @hybrid_property + def completed_date(self): + if not self.signed_by: + return None + if self._completed_date: + return self._completed_date + else: + value = max([proc.completed_date for proc in self.procedure]) + return value + + @completed_date.setter + def completed_date(self, value): + if value: + self._completed_date = value + else: + self._completed_date = min([proc.started_date for proc in self.procedure]) + @classmethod def get_default_info(cls, *args, submissiontype: SubmissionType | None = None) -> dict: """ @@ -635,6 +671,8 @@ class Run(BaseClass, LogMixin): def sample_count(self): return len(self.sample) + + def details_dict(self, **kwargs): output = super().details_dict() output['plate_number'] = self.plate_number @@ -654,9 +692,12 @@ class Run(BaseClass, LogMixin): output['sample'] = active_samples + inactive_samples output['procedure'] = [procedure.details_dict() for procedure in output['procedure']] output['permission'] = is_power_user() - output['excluded'] = ['procedure', "runsampleassociation", 'excluded', 'expanded', 'sample', 'id', 'custom', - 'permission'] + output['excluded'] += ['procedure', "runsampleassociation", 'excluded', 'expanded', 'sample', 'id', 'custom', + 'permission', "clientsubmission"] output['sample_count'] = self.sample_count + output['client_submission'] = self.clientsubmission.name + output['started_date'] = self.started_date + output['completed_date'] = self.completed_date return output @classmethod @@ -1715,6 +1756,8 @@ class ClientSubmissionSampleAssociation(BaseClass): output['misc_info'] = misc # output['sample'] = temp # output.update(output['sample'].details_dict()) + + # sys.exit() return output def to_pydantic(self) -> "PydSample": @@ -2132,6 +2175,7 @@ class RunSampleAssociation(BaseClass): # logger.debug(f"Output from sample: {pformat(output)}") output.update(relevant) output['misc_info'] = misc + return output diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 5b9e457..ed3c518 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -1500,7 +1500,7 @@ class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True): class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): proceduretype: ProcedureType | None = Field(default=None) - run: Run | None = Field(default=None) + run: Run | str | None = Field(default=None) name: dict = Field(default=dict(value="NA", missing=True), validate_default=True) technician: dict = Field(default=dict(value="NA", missing=True)) repeat: bool = Field(default=False) @@ -1590,6 +1590,13 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): value = {item.name: item.reagent for item in kittype.reagentrole} return value + @field_validator("run") + @classmethod + def lookup_run(cls, value): + if isinstance(value, str): + value = Run.query(name=value) + return value + def update_kittype_reagentroles(self, kittype: str | KitType): if kittype == self.__class__.model_fields['kittype'].default['value']: return @@ -1701,13 +1708,24 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): # NOTE: reset reagent associations. sql.procedurereagentassociation = [] for reagent in self.reagent: - logger.debug(reagent) if isinstance(reagent, dict): reagent = PydReagent(**reagent) + logger.debug(reagent) + reagentrole = reagent.reagentrole reagent = reagent.to_sql() + logger.debug(reagentrole) if reagent not in sql.reagent: + # NOTE: Remove any previous association for this role. + removable = ProcedureReagentAssociation.query(procedure=sql, reagentrole=reagentrole) + logger.debug(f"Removable: {removable}") + if removable: + if isinstance(removable, list): + for r in removable: + r.delete() + else: + removable.delete() logger.debug(f"Adding {reagent} to {sql}") - reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql) + reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql, reagentrole=reagentrole) try: start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1 except ValueError: @@ -1733,7 +1751,11 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): kittype = KitType.query(name=self.kittype['value'], limit=1) if kittype: sql.kittype = kittype - logger.debug(self.reagent) + # logger.debug(self.reagent) + # for reagent in self.reagent: + # reagent = reagent.to_sql() + # if reagent not in sql.reagent: + # reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql) for equipment in self.equipment: equip = Equipment.query(name=equipment.name) if equip not in sql.equipment: @@ -1742,6 +1764,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): process = equipment.process.to_sql() equip_assoc.process = process # logger.debug(f"Output sql: {[pformat(item.__dict__) for item in sql.procedureequipmentassociation]}") + logger.debug(pformat(sql.__dict__)) return sql, None @@ -1854,20 +1877,29 @@ class PydClientSubmission(PydBaseClass): """ from frontend.widgets.submission_widget import ClientSubmissionFormWidget if not samples: - samples = self.samples + samples = self.sample return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable) def to_sql(self): sql = super().to_sql() + assert not any([isinstance(item, PydSample) for item in sql.sample]) + sql.sample = [] if "info_placement" not in sql._misc_info: sql._misc_info['info_placement'] = [] info_placement = [] for k in list(self.model_fields.keys()) + list(self.model_extra.keys()): + logger.debug(f"Running {k}") attribute = getattr(self, k) match k: case "filepath": sql._misc_info[k] = attribute.__str__() continue + # case "sample": + # sample = [item.to_sql() for item in self.sample] + # logger.debug(sample) + # for s in sample: + # logger.debug(f"adding {s}") + # sql.add_sample(sample=s) case _: pass logger.debug(f"Setting {k} to {attribute}") @@ -1876,7 +1908,7 @@ class PydClientSubmission(PydBaseClass): info_placement.append(dict(name=k, location=attribute['location'])) else: info_placement.append(dict(name=k, location=None)) - max_row = max([value['location']['row'] for value in info_placement if value]) + # max_row = max([value['location']['row'] for value in info_placement if value]) sql._misc_info['info_placement'] = info_placement return sql diff --git a/src/submissions/frontend/widgets/procedure_creation.py b/src/submissions/frontend/widgets/procedure_creation.py index 656004d..badd694 100644 --- a/src/submissions/frontend/widgets/procedure_creation.py +++ b/src/submissions/frontend/widgets/procedure_creation.py @@ -80,6 +80,7 @@ class ProcedureCreation(QDialog): equipmentrole['equipment'].insert(0, equipmentrole['equipment'].pop( equipmentrole['equipment'].index(item_in_er_list))) proceduretype_dict['equipment_section'] = EquipmentUsage.construct_html(procedure=self.procedure, child=True) + self.update_equipment = EquipmentUsage.update_equipment html = render_details_template( template_name="procedure_creation", # css_in=['new_context_menu'], @@ -94,6 +95,30 @@ class ProcedureCreation(QDialog): f.write(html) self.webview.setHtml(html) + @pyqtSlot(str, str, str, str) + def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str): + from backend.db.models import Equipment + logger.debug("Updating equipment") + 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}") + @pyqtSlot(str, str) def text_changed(self, key: str, new_value: str): logger.debug(f"New value for {key}: {new_value}") diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index 5976e3b..6849235 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -63,17 +63,19 @@ class SubmissionDetails(QDialog): self.webview.page().setWebChannel(self.channel) def object_details(self, object): - details = object.details_dict() + details = object.clean_details_dict(object.details_dict()) template = object.details_template template_path = Path(template.environment.loader.__getattribute__("searchpath")[0]) with open(template_path.joinpath("css", "styles.css"), "r") as f: css = f.read() key = object.__class__.__name__.lower() d = {key: details} - logger.debug(f"Using details: {d}") - html = template.render(**d, css=css) + logger.debug(f"Using details: {pformat(d)}") + html = template.render(**d, css=[css]) self.webview.setHtml(html) self.setWindowTitle(f"{object.__class__.__name__} Details - {object.name}") + with open(f"{object.__class__.__name__}_details_rendered.html", "w") as f: + f.write(html) @@ -226,7 +228,7 @@ class SubmissionDetails(QDialog): Args: run (str | BasicRun): Submission of interest. """ - logger.debug(f"Submission details.") + logger.debug(f"Run details.") if isinstance(run, str): run = Run.query(name=run) self.rsl_plate_number = run.rsl_plate_number @@ -242,6 +244,7 @@ class SubmissionDetails(QDialog): self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css) self.webview.setHtml(self.html) + @pyqtSlot(str) def sign_off(self, run: str | Run) -> None: """ diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index a06558e..9a81f5e 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -1,6 +1,8 @@ """ Contains all procedure related frontend functions """ +import sys + from PyQt6.QtWidgets import ( QWidget, QPushButton, QVBoxLayout, QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout @@ -142,9 +144,9 @@ class SubmissionFormContainer(QWidget): # 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, fname=fname) + self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, input_object=fname) self.pydclientsubmission = self.clientsubmission_manager.parse() - checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.samples) + checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.sample) if checker.exec(): # logger.debug(pformat(self.pydclientsubmission.sample)) try: @@ -189,7 +191,7 @@ class SubmissionFormContainer(QWidget): class SubmissionFormWidget(QWidget): update_reagent_fields = ['kittype'] - def __init__(self, parent: QWidget, pyd: PydRun, disable: list | None = None) -> None: + def __init__(self, parent: QWidget, pyd: PydClientSubmission, disable: list | None = None) -> None: super().__init__(parent) if disable is None: disable = [] @@ -816,8 +818,8 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): except AttributeError: pass # save_btn = QPushButton("Save") - self.samples = samples - logger.debug(f"Samples: {self.samples}") + self.sample = samples + logger.debug(f"Samples: {self.sample}") start_run_btn = QPushButton("Save") # self.layout.addWidget(save_btn) self.layout.addWidget(start_run_btn) @@ -867,12 +869,19 @@ class ClientSubmissionFormWidget(SubmissionFormWidget): @report_result def create_new_submission(self, *args) -> Report: pyd = self.to_pydantic() + logger.debug(f"Pydantic: {pyd}") sql = pyd.to_sql() - for sample in self.samples: + for sample in pyd.sample: if isinstance(sample, PydSample): sample = sample.to_sql() + assert not isinstance(sample, PydSample) + # if sample not in sql.sample: sql.add_sample(sample=sample) - logger.debug(sql.__dict__) + logger.debug(pformat(sql.__dict__)) + try: + del sql._misc_info['sample'] + except KeyError: + pass sql.save() self.app.table_widget.sub_wid.set_data() self.setParent(None) diff --git a/src/submissions/templates/clientsubmission_details.html b/src/submissions/templates/clientsubmission_details.html index a7b2827..647a1ab 100644 --- a/src/submissions/templates/clientsubmission_details.html +++ b/src/submissions/templates/clientsubmission_details.html @@ -16,22 +16,22 @@

{% if clientsubmission['sample'] %} - -
+ +

{% for sample in clientsubmission['sample'] %} -     {{ sample['sample_id']}}
+     {{ sample['sample_id'] }}
{% endfor %}

-
+ {% endif %} {% if clientsubmission['run'] %} - -
+ + {% for run in clientsubmission['run'] %} {% with run=run, child=True %} {% include "run_details.html" %} {% endwith %} {% endfor %} -
+ {% endif %} {% endblock %} diff --git a/src/submissions/templates/details.html b/src/submissions/templates/details.html index ef608a3..795ef9e 100644 --- a/src/submissions/templates/details.html +++ b/src/submissions/templates/details.html @@ -24,23 +24,7 @@ {% block script %} {% if not child %} - - - - - - - - - - - - - - - - {% for j in js%} {% endfor %} +{% endif %} {% endblock %} {% if not child %} diff --git a/src/submissions/templates/procedure_details.html b/src/submissions/templates/procedure_details.html index 21a3ca4..8ea45d8 100644 --- a/src/submissions/templates/procedure_details.html +++ b/src/submissions/templates/procedure_details.html @@ -15,23 +15,51 @@

{% for key, value in procedure.items() if key not in procedure['excluded'] %}     {{ key | replace("_", " ") | title | replace("Pcr", "PCR") }}: {{ value }}
{% endfor %}

+ {% if procedure['reagent'] %} + +      + + + + + + + {% for reg in procedure['reagent'] %} + {% with reagent=reg, child=True %} + {% include "support/reagent_list.html" %} + {% endwith %} + {% endfor %}

+
Reagent RoleReagent NameLotExpiry

+ {% endif %} + {% if procedure['equipment'] %} + +      + + + + + + + {% for eq in procedure['equipment'] %} + {% with equipment=eq, child=True %} + {% include "support/equipment_list.html" %} + {% endwith %} + {% endfor %} +
Equipment RoleEquipment NameProcessTips

+ {% endif %} {% if procedure['results'] %} - -
+ {% for result in procedure['results'] %}

{% for k, v in result['result'].items() %} {{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}: {{ value }}
{% endfor %}

{% endfor %} -
{% endif %} {% if procedure['sample'] %} - -
+

{% for sample in procedure['sample'] %}     {{ sample['sample_id']}}
{% endfor %}

-
{% endif %} {% endblock %} diff --git a/src/submissions/templates/run_details.html b/src/submissions/templates/run_details.html index 8b14604..487e40c 100644 --- a/src/submissions/templates/run_details.html +++ b/src/submissions/templates/run_details.html @@ -15,24 +15,22 @@     {{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}: {{ value }}
{% endfor %}

{% if run['sample'] %} - +

{% for sample in run['sample'] %}     {{ sample['sample_id']}}
{% endfor %}

{% endif %} {% if run['procedure'] %} - -
+ {% for procedure in run['procedure'] %} {% with procedure=procedure, child=True %} {% include "procedure_details.html" %} {% endwith %} {% endfor %} -
{% endif %} {% endblock %} {% block signing_button %} - + {% endblock %}
@@ -55,5 +53,3 @@ }); {% endblock %} - - diff --git a/src/submissions/templates/support/reagent_list.html b/src/submissions/templates/support/reagent_list.html new file mode 100644 index 0000000..b295470 --- /dev/null +++ b/src/submissions/templates/support/reagent_list.html @@ -0,0 +1,6 @@ + + {{ reagent['reagentrole'] }} + {{ reagent['name'] }} + {{ reagent['lot'] }} + {{ reagent['expiry'].strftime('%Y-%m-%d') }} + \ No newline at end of file