From b6e1c0dee2cbd215dedec00e0b823d8661c805d4 Mon Sep 17 00:00:00 2001 From: lwark Date: Thu, 4 Sep 2025 14:35:44 -0500 Subject: [PATCH] Code cleanup for excel.parsers complete. --- src/submissions/backend/db/models/__init__.py | 5 +- .../backend/db/models/procedures.py | 63 ++++++++-- .../backend/excel/parsers/__init__.py | 13 +- .../excel/parsers/clientsubmission_parser.py | 23 +--- .../parsers/procedure_parsers/__init__.py | 7 ++ .../results_parsers/pcr_results_parser.py | 4 +- .../backend/validators/__init__.py | 2 +- src/submissions/backend/validators/pydant.py | 117 ++++++++++-------- .../frontend/widgets/procedure_creation.py | 61 ++++----- .../frontend/widgets/submission_details.py | 2 +- .../frontend/widgets/submission_table.py | 2 +- 11 files changed, 161 insertions(+), 138 deletions(-) diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index ac69862..1bddfd6 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -538,7 +538,10 @@ class BaseClass(Base): case _: return super().__setattr__(key, value) else: - return super().__setattr__(key, value) + try: + return super().__setattr__(key, value) + except AttributeError: + raise AttributeError(f"Can't set {key} to {value}") def delete(self, **kwargs): logger.error(f"Delete has not been implemented for {self.__class__.__name__}") diff --git a/src/submissions/backend/db/models/procedures.py b/src/submissions/backend/db/models/procedures.py index 40332f2..7ee12a2 100644 --- a/src/submissions/backend/db/models/procedures.py +++ b/src/submissions/backend/db/models/procedures.py @@ -1027,7 +1027,8 @@ class ProcedureTypeReagentRoleAssociation(BaseClass): reagentrole_id = Column(INTEGER, ForeignKey("_reagentrole.id"), primary_key=True) #: id of associated reagentrole - proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id"), primary_key=True) #: id of associated proceduretype + proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id"), + primary_key=True) #: id of associated proceduretype uses = Column(JSON) #: map to location on excel sheets of different procedure types required = Column(INTEGER) #: whether the reagent type is required for the kittype (Boolean 1 or 0) last_used = Column(String(32)) #: last used lot number of this type of reagent @@ -1324,7 +1325,7 @@ class ProcedureReagentLotAssociation(BaseClass): output.update(relevant) output['reagentrole'] = self.reagentrole output['misc_info'] = misc - logger.debug(f"Output: {pformat(output)}") + # logger.debug(f"Output: {pformat(output)}") return output def delete(self, **kwargs): @@ -1544,7 +1545,6 @@ class Equipment(BaseClass, LogMixin): else: return {k: v for k, v in self.__dict__.items()} - @classmethod @setup_lookup def query(cls, @@ -1842,7 +1842,7 @@ class ProcessVersion(BaseClass): @classmethod def query(cls, - version: str | None = None, + version: str | float | None = None, name: str | None = None, limit: int = 0, **kwargs) -> ReagentLot | List[ReagentLot]: @@ -1853,8 +1853,8 @@ class ProcessVersion(BaseClass): case _: pass match version: - case str(): - query = query.filter(cls.version == version) + case str() | float(): + query = query.filter(cls.version == float(version)) case _: pass return cls.execute_query(query=query, limit=limit) @@ -1879,6 +1879,9 @@ class Tips(BaseClass): @setup_lookup def query(cls, name: str | None = None, + manufacturer: str | None = None, + capacity: str | None = None, + ref: str | None = None, limit: int = 0, **kwargs) -> Tips | List[Tips]: query = cls.__database_session__.query(cls) @@ -1888,6 +1891,22 @@ class Tips(BaseClass): limit = 1 case _: pass + match manufacturer: + case str(): + query = query.filter(cls.manufacturer == manufacturer) + case _: + pass + match capacity: + case int(): + query = query.filter(cls.capacity == capacity) + case _: + pass + match ref: + case str(): + query = query.filter(cls.ref == ref) + limit = 1 + case _: + pass return cls.execute_query(query=query, limit=limit) @check_authorization @@ -1944,15 +1963,21 @@ class TipsLot(BaseClass, LogMixin): @property def name(self) -> str: - return f"{self.tips.manufacturer}-{self.tips.capacity}-{self.lot}" + return f"{self.tips.manufacturer}-{self.tips.ref}-{self.lot}" @classmethod - def query(cls, name: str | None = None, lot: str | None = None, limit: int = 0, **kwargs) -> Tips | List[Tips]: + def query(cls, + manufacturer: str | None = None, + ref: str | None = None, + lot: str | None = None, + limit: int = 0, + **kwargs) -> Tips | List[Tips]: """ Lookup tips Args: - name (str | None, optional): Informal name of tips. Defaults to None. + manufacturer (str | None, optional): Name of parent tip manufacturer. Defaults to None. + ref (str | None, optional): Name of parent tip reference number. Defaults to None. lot (str | None, optional): Lot number. Defaults to None. limit (int, optional): Maximum number of results to return (0=all). Defaults to 0. @@ -1960,9 +1985,17 @@ class TipsLot(BaseClass, LogMixin): Tips | List[Tips]: Tips matching criteria """ query = cls.__database_session__.query(cls) - match name: + if manufacturer is not None and ref is not None: + manufacturer = None + match manufacturer: case str(): - query = query.filter(cls.name == name) + logger.debug(f"Looking for {manufacturer}") + query = query.join(Tips).filter(Tips.manufacturer == manufacturer) + case _: + pass + match ref: + case str(): + query = query.join(Tips).filter(Tips.ref == ref) case _: pass match lot: @@ -2032,7 +2065,8 @@ class ProcedureEquipmentAssociation(BaseClass): equipment = relationship(Equipment, back_populates="equipmentprocedureassociation") #: associated equipment - processversion = relationship(ProcessVersion, back_populates="procedureequipmentassociation") #: Associated process version + processversion = relationship(ProcessVersion, + back_populates="procedureequipmentassociation") #: Associated process version tipslot_id = Column(INTEGER, ForeignKey("_tipslot.id", ondelete="SET NULL", name="SEA_Tipslot_id")) @@ -2153,7 +2187,10 @@ class ProcedureEquipmentAssociation(BaseClass): output.update(relevant) output['misc_info'] = misc output['equipment_role'] = self.equipmentrole - output['processversion'] = self.processversion.details_dict() + try: + output['processversion'] = self.processversion.details_dict() + except AttributeError: + output['processversion'] = None try: output['tips'] = self.tipslot.details_dict() except AttributeError: diff --git a/src/submissions/backend/excel/parsers/__init__.py b/src/submissions/backend/excel/parsers/__init__.py index fde094a..892443f 100644 --- a/src/submissions/backend/excel/parsers/__init__.py +++ b/src/submissions/backend/excel/parsers/__init__.py @@ -1,5 +1,5 @@ """ - +Default Parser archetypes. """ from __future__ import annotations import logging, re @@ -43,7 +43,8 @@ class DefaultParser(object): *args (): **kwargs (): """ - logger.debug(f"\n\nHello from {self.__class__.__name__}\n\n") + logger.info(f"\n\nHello from {self.__class__.__name__}\n\n") + self.filepath = filepath self.proceduretype = proceduretype try: self._pyd_object = getattr(pydant, @@ -61,10 +62,8 @@ class DefaultParser(object): self.worksheet = self.workbook[self.sheet] self.start_row = self.delineate_start_row(start_row=start_row) self.end_row = self.delineate_end_row(start_row=self.start_row) - logger.debug(f"Start row: {self.start_row}, End row: {self.end_row}") def to_pydantic(self): - # data = {key: value['value'] for key, value in self.parsed_info.items()} data = self.parsed_info data['filepath'] = self.filepath return self._pyd_object(**data) @@ -100,7 +99,6 @@ class DefaultKEYVALUEParser(DefaultParser): rows = range(self.start_row, self.end_row) for row in rows: check_row = [item for item in self.worksheet.rows][row-1] - logger.debug(f"Checking row {row-1}, {check_row} for merged cells.") if any([isinstance(cell, MergedCell) for cell in check_row]): continue key = self.worksheet.cell(row, 1).value @@ -110,9 +108,7 @@ class DefaultKEYVALUEParser(DefaultParser): key = key.lower().replace(":", "").strip().replace(" ", "_") value = self.worksheet.cell(row, 2).value missing = False if value else True - # location_map = dict(row=row, key_column=1, value_column=2, sheet=self.worksheet.title) value = dict(value=value, missing=missing)#, location=location_map) - logger.debug(f"Yielding {value} for {key}") yield key, value @@ -123,7 +119,6 @@ class DefaultTABLEParser(DefaultParser): @property def parsed_info(self) -> Generator[dict, None, None]: - logger.debug(f"creating dataframe from {self.start_row} to {self.end_row}") df = DataFrame( [item for item in self.worksheet.values][self.start_row - 1:self.end_row - 1]) df.columns = df.iloc[0] @@ -131,12 +126,10 @@ class DefaultTABLEParser(DefaultParser): df = df.dropna(axis=1, how='all') for ii, row in enumerate(df.iterrows()): output = {} - # for key, value in row[1].to_dict().items(): for key, value in row[1].details_dict().items(): if isinstance(key, str): key = key.lower().replace(" ", "_") key = re.sub(r"_(\(.*\)|#)", "", key) - # logger.debug(f"Row {ii} values: {key}: {value}") output[key] = value yield output diff --git a/src/submissions/backend/excel/parsers/clientsubmission_parser.py b/src/submissions/backend/excel/parsers/clientsubmission_parser.py index 10cd7c9..4554542 100644 --- a/src/submissions/backend/excel/parsers/clientsubmission_parser.py +++ b/src/submissions/backend/excel/parsers/clientsubmission_parser.py @@ -1,11 +1,11 @@ """ - +Module for clientsubmission parsing """ from __future__ import annotations import logging from pathlib import Path from string import ascii_lowercase -from typing import Generator, TYPE_CHECKING, Literal +from typing import Generator, TYPE_CHECKING from openpyxl.reader.excel import load_workbook from tools import row_keys from . import DefaultKEYVALUEParser, DefaultTABLEParser @@ -122,20 +122,6 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin): else: self.submissiontype = submissiontype super().__init__(filepath=filepath, sheet="Client Info", start_row=1, **kwargs) - # NOTE: move to the manager class. - # allowed_procedure_types = [item.name for item in self.submissiontype.proceduretype] - # for name in allowed_procedure_types: - # if name in self.workbook.sheetnames: - # # TODO: check if run with name already exists - # add_run = QuestionAsker(title="Add Run?", message="We've detected a sheet corresponding to an associated procedure type.\nWould you like to add a new run?") - # if add_run.accepted: - # # NOTE: recruit parser. - # try: - # manager = getattr(procedure_managers, name) - # except AttributeError: - # manager = procedure_managers.DefaultManager - # self.manager = manager(proceduretype=name) - # pass @property def parsed_info(self): @@ -144,13 +130,11 @@ class ClientSubmissionInfoParser(DefaultKEYVALUEParser, SubmissionTyperMixin): output['clientlab'] = output['client_lab'] except KeyError: pass - # output['submissiontype'] = dict(value=self.submissiontype.name.title()) try: output['submissiontype'] = output['submission_type'] output['submissiontype']['value'] = self.submissiontype.name.title() except KeyError: pass - logger.debug(f"Data: {output}") return output @@ -173,8 +157,6 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin): def parsed_info(self) -> Generator[dict, None, None]: output = super().parsed_info for ii, sample in enumerate(output, start=1): - # logger.debug(f"Parsed info sample: {sample}") - if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]: try: sample["row"] = row_keys[sample["row"]] @@ -184,5 +166,4 @@ class ClientSubmissionSampleParser(DefaultTABLEParser, SubmissionTyperMixin): yield sample def to_pydantic(self): - logger.debug(f"Attempting to pydantify: {self._pyd_object}") return [self._pyd_object(**sample) for sample in self.parsed_info if sample['sample_id']] diff --git a/src/submissions/backend/excel/parsers/procedure_parsers/__init__.py b/src/submissions/backend/excel/parsers/procedure_parsers/__init__.py index e00b897..25f0302 100644 --- a/src/submissions/backend/excel/parsers/procedure_parsers/__init__.py +++ b/src/submissions/backend/excel/parsers/procedure_parsers/__init__.py @@ -8,6 +8,13 @@ if TYPE_CHECKING: logger = logging.getLogger(f"submissions.{__name__}") +""" +TODO + +- range dicts should hopefully not be necessary in this type of parser. Hopefully all procedure parsers are the same. +""" + + class ProcedureInfoParser(DefaultKEYVALUEParser): default_range_dict = [dict( diff --git a/src/submissions/backend/excel/parsers/results_parsers/pcr_results_parser.py b/src/submissions/backend/excel/parsers/results_parsers/pcr_results_parser.py index 07ead5b..4d4701b 100644 --- a/src/submissions/backend/excel/parsers/results_parsers/pcr_results_parser.py +++ b/src/submissions/backend/excel/parsers/results_parsers/pcr_results_parser.py @@ -1,5 +1,5 @@ """ - +Parser for pcr results from Design and Analysis Studio """ from __future__ import annotations import logging @@ -15,7 +15,7 @@ logger = logging.getLogger(f"submissions.{__name__}") class PCRInfoParser(DefaultResultsInfoParser): - def __init__(self, filepath: Path | str, sheet: str | None = None, start_row: int = 1, procedure=None, **kwargs): + def __init__(self, filepath: Path | str, procedure=None, **kwargs): self.results_type = "PCR" self.procedure = procedure super().__init__(filepath=filepath, proceduretype=self.procedure.proceduretype) diff --git a/src/submissions/backend/validators/__init__.py b/src/submissions/backend/validators/__init__.py index 5b33b58..eba5dc6 100644 --- a/src/submissions/backend/validators/__init__.py +++ b/src/submissions/backend/validators/__init__.py @@ -49,7 +49,7 @@ class ClientSubmissionNamer(DefaultNamer): logger.warning(f"Getting submissiontype from regex failed, using default submissiontype.") sub_type = SubmissionType.query(name="Default") logger.debug(f"Submission Type: {sub_type}") - sys.exit() + # sys.exit() return sub_type def get_subtype_from_regex(self) -> SubmissionType: diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 96ec78b..9c45232 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -357,7 +357,7 @@ class PydTips(PydBaseClass): # return value @report_result - def to_sql(self, procedure: Run) -> Tuple[Tips, Report]: + def to_sql(self) -> Tuple[Tips, Report]: """ Convert this object to the SQL version for database storage. @@ -382,7 +382,7 @@ class PydEquipment(PydBaseClass): # process: List[dict] | None process: List[PydProcess] | PydProcess | None equipmentrole: str | PydEquipmentRole | None - # tips: List[PydTips] | PydTips | None = Field(default=[]) + tips: List[PydTips] | PydTips | None = Field(default=[]) @field_validator('equipmentrole', mode='before') @classmethod @@ -400,7 +400,7 @@ class PydEquipment(PydBaseClass): @field_validator('process', mode='before') @classmethod - def make_empty_list(cls, value, values): + def process_to_pydantic(cls, value, values): # if isinstance(value, dict): # value = value['processes'] if isinstance(value, GeneratorType): @@ -408,17 +408,48 @@ class PydEquipment(PydBaseClass): value = convert_nans_to_nones(value) if not value: value = [] - try: - # value = [item.strip() for item in value] - d: Process = next((process for process in value if values.data['name'] in [item.name for item in process.equipment]), None) - print(f"Next process: {d.details_dict()}") - if d: - # value = PydProcess(**d.details_dict()) - value = d.to_pydantic() - # value = next((process.to_pydantic() for process in value)) - except AttributeError as e: - logger.error(f"Process Validation error due to {e}") - pass + if isinstance(value, ProcessVersion): + value = value.to_pydantic(pyd_model_name="PydProcess") + else: + try: + # value = [item.strip() for item in value] + d: Process = next((process for process in value if values.data['name'] in [item.name for item in process.equipment]), None) + print(f"Next process: {d.details_dict()}") + if d: + # value = PydProcess(**d.details_dict()) + value = d.to_pydantic() + # value = next((process.to_pydantic() for process in value)) + except AttributeError as e: + logger.error(f"Process Validation error due to {e}") + pass + return value + + @field_validator('tips', mode='before') + @classmethod + def tips_to_pydantic(cls, value, values): + # if isinstance(value, dict): + # value = value['processes'] + if isinstance(value, GeneratorType): + value = [item for item in value] + value = convert_nans_to_nones(value) + if not value: + value = [] + if isinstance(value, TipsLot): + value = value.to_pydantic(pyd_model_name="PydTips") + else: + try: + # value = [item.strip() for item in value] + d: Tips = next( + (tips for tips in value if values.data['name'] in [item.name for item in tips.equipment]), + None) + print(f"Next process: {d.details_dict()}") + if d: + # value = PydProcess(**d.details_dict()) + value = d.to_pydantic() + # value = next((process.to_pydantic() for process in value)) + except AttributeError as e: + logger.error(f"Process Validation error due to {e}") + pass return value # @field_validator('tips', mode='before') @@ -1172,7 +1203,7 @@ class PydEquipmentRole(BaseModel): class PydProcess(PydBaseClass, extra="allow"): name: str - version: str = Field(default="1") + version: str = Field(default="1.0") # equipment: List[str] tips: List[PydTips] @@ -1198,12 +1229,20 @@ class PydProcess(PydBaseClass, extra="allow"): value = [item for item in value if item] return value + @field_validator("version", mode="before") + @classmethod + def enforce_float_string(cls, value): + if isinstance(value, float): + value = str(value) + return value + @report_result def to_sql(self): report = Report() + name = self.name.split("-")[0] logger.debug(f"Query process: {self.name}, version = {self.version}") # NOTE: can't use query_or_create due to name not being part of ProcessVersion - instance = ProcessVersion.query(name=self.name, version=self.version, limit=1) + instance = ProcessVersion.query(name=name, version=self.version, limit=1) logger.debug(f"Got instance: {instance}") if not instance: instance = ProcessVersion() @@ -1245,8 +1284,6 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): technician: dict = Field(default=dict(value="NA", missing=True)) repeat: bool = Field(default=False) repeat_of: Procedure | None = Field(default=None) - # kittype: dict = Field(default=dict(value="NA", missing=True)) - # possible_kits: list | None = Field(default=[], validate_default=True) plate_map: str | None = Field(default=None) reagent: list | None = Field(default=[]) reagentrole: dict | None = Field(default={}, validate_default=True) @@ -1291,17 +1328,6 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): value['missing'] = True return value - # @field_validator("possible_kits") - # @classmethod - # def rescue_possible_kits(cls, value, values): - # if not value: - # try: - # if values.data['proceduretype']: - # value = [kittype.__dict__['name'] for kittype in values.data['proceduretype'].kittype] - # except KeyError: - # pass - # return value - @field_validator("name", "technician")#, "kittype") @classmethod def set_colour(cls, value): @@ -1471,8 +1497,6 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): sql = Procedure() else: sql = super().to_sql() - logger.debug(f"Initial PYD: {pformat(self.__dict__)}") - # sql.results = [result.to_sql() for result in self.results] if isinstance(self.name, dict): sql.name = self.name['value'] else: @@ -1481,7 +1505,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): sql.technician = self.technician['value'] else: sql.technician = self.technician - sql.repeat = self.repeat + # sql.repeat = int(self.repeat) if sql.repeat: regex = re.compile(r".*\dR\d$") repeats = [item for item in self.run.procedure if @@ -1493,21 +1517,12 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): sql.run = self.run if self.proceduretype: sql.proceduretype = self.proceduretype - # Note: convert any new reagents to sql and save - # for reagentrole, reagents in self.reagentrole.items(): - # for reagent in self.reagent: - # if not reagent.lot or reagent.name == "--New--": - # continue - # self.update_new_reagents(reagent) # NOTE: reset reagent associations. - # sql.procedurereagentassociation = [] for reagent in self.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.reagentlot: # NOTE: Remove any previous association for this role. if sql.id: @@ -1529,36 +1544,36 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True): start_index = 1 relevant_samples = [sample for sample in self.sample if not sample.sample_id.startswith("blank_") and not sample.sample_id == ""] - # logger.debug(f"start index: {start_index}") assoc_id_range = range(start_index, start_index + len(relevant_samples) + 1) - # logger.debug(f"Association id range: {assoc_id_range}") for iii, sample in enumerate(relevant_samples): sample_sql = sample.to_sql() if sql.run: if sample_sql not in sql.run.sample: - logger.debug(f"sample {sample_sql} not found in {sql.run.sample}") run_assoc = RunSampleAssociation(sample=sample_sql, run=self.run, row=sample.row, column=sample.column) - # else: - # logger.debug(f"sample {sample_sql} found in {sql.run.sample}") if sample_sql not in sql.sample: proc_assoc = ProcedureSampleAssociation(new_id=assoc_id_range[iii], procedure=sql, sample=sample_sql, row=sample.row, column=sample.column, procedure_rank=sample.procedure_rank) - # sys.exit(pformat(self.equipment)) for equipment in self.equipment: - logger.debug(f"Equipment: {equipment}") - # equip = Equipment.query(name=equipment.name) equip, _ = equipment.to_sql() - logger.debug(f"Process: {equipment.process}") if isinstance(equipment.process, list): equipment.process = equipment.process[0] + if isinstance(equipment.tips, list): + try: + equipment.tips = equipment.tips[0] + except IndexError: + equipment.tips = None if equip not in sql.equipment: equip_assoc = ProcedureEquipmentAssociation(equipment=equip, procedure=sql, equipmentrole=equip.equipmentrole[0]) process = equipment.process.to_sql() equip_assoc.processversion = process - # sys.exit(pformat([item.__dict__ for item in sql.procedureequipmentassociation])) + try: + tipslot = equipment.tips.to_sql() + except AttributeError: + tipslot = None + equip_assoc.tipslot = tipslot return sql, None diff --git a/src/submissions/frontend/widgets/procedure_creation.py b/src/submissions/frontend/widgets/procedure_creation.py index b2aad8e..99b79fe 100644 --- a/src/submissions/frontend/widgets/procedure_creation.py +++ b/src/submissions/frontend/widgets/procedure_creation.py @@ -1,18 +1,15 @@ """ - +Main module to construct the procedure form """ from __future__ import annotations -import sys, logging, os, re, datetime -from pathlib import Path +import sys, logging, re, datetime from pprint import pformat from PyQt6.QtCore import pyqtSlot, Qt -from PyQt6.QtGui import QContextMenuEvent, QAction from PyQt6.QtWebChannel import QWebChannel from PyQt6.QtWebEngineWidgets import QWebEngineView -from PyQt6.QtWidgets import QDialog, QGridLayout, QMenu, QDialogButtonBox +from PyQt6.QtWidgets import QDialog, QGridLayout, QDialogButtonBox from typing import TYPE_CHECKING, Any, List if TYPE_CHECKING: - from backend.db.models import Run, Procedure from backend.validators import PydProcedure, PydEquipment from tools import get_application_from_parent, render_details_template, sanitize_object_for_json @@ -26,7 +23,6 @@ class ProcedureCreation(QDialog): self.edit = edit self.run = procedure.run self.procedure = procedure - # logger.debug(f"procedure: {pformat(self.procedure.__dict__)}") self.proceduretype = procedure.proceduretype self.setWindowTitle(f"New {self.proceduretype.name} for {self.run.rsl_plate_number}") self.plate_map = self.proceduretype.construct_plate_map(sample_dicts=self.procedure.sample) @@ -75,17 +71,14 @@ class ProcedureCreation(QDialog): equipmentrole['equipment'].index(item_in_er_list))) proceduretype_dict['equipment_section'] = EquipmentUsage.construct_html(procedure=self.procedure, child=True) proceduretype_dict['equipment'] = [sanitize_object_for_json(object) for object in proceduretype_dict['equipment']] - logger.debug(proceduretype_dict['equipment']) self.update_equipment = EquipmentUsage.update_equipment regex = re.compile(r".*R\d$") proceduretype_dict['previous'] = [""] + [item.name for item in self.run.procedure if item.proceduretype == self.proceduretype and not bool(regex.match(item.name))] html = render_details_template( template_name="procedure_creation", - # css_in=['new_context_menu'], js_in=["procedure_form", "grid_drag", "context_menu"], proceduretype=proceduretype_dict, run=self.run.details_dict(), - # procedure=self.procedure.__dict__, procedure=self.procedure, plate_map=self.plate_map, edit=self.edit @@ -94,8 +87,12 @@ class ProcedureCreation(QDialog): @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") + from backend.db.models import Equipment, ProcessVersion, TipsLot + logger.debug(f"Updating equipment with" + f"\n\tEquipment role: {equipmentrole}" + f"\n\tEquipment: {equipment}" + f"\n\tProcess: {process}" + f"\n\tTips: {tips}") try: equipment_of_interest = next( (item for item in self.procedure.equipment if item.equipmentrole == equipmentrole)) @@ -109,14 +106,25 @@ class ProcedureCreation(QDialog): eoi.name = equipment.name eoi.asset_number = equipment.asset_number eoi.nickname = equipment.nickname - # logger.warning("Setting processes.") - eoi.process = [process for process in equipment.get_processes(equipmentrole=equipmentrole)] + process_name, version = process.split("-v") + process = ProcessVersion.query(name=process_name, version=version, limit=1) + eoi.process = process + # sys.exit(f"Process:\n{pformat(eoi.process.__dict__)}") + try: + tips_manufacturer, tipsref, lot = [item if item != "" else None for item in tips.split("-")] + logger.debug(f"Querying with '{tips_manufacturer}', '{tipsref}', '{lot}'") + tips = TipsLot.query(manufacturer=tips_manufacturer, ref=tipsref, lot=lot) + logger.debug(f"Found tips: {tips}") + eoi.tips = tips + except ValueError: + logger.warning(f"No tips info to unpack") + # tips = TipsLot.query(manufacturer=tips_manufacturer, ref=tipsref, lot=lot) + # eoi.tips = tips self.procedure.equipment.append(eoi) - # logger.debug(f"Updated equipment: {pformat(self.procedure.equipment)}") + logger.debug(f"Updated equipment:\n{pformat([item.__dict__ for item in self.procedure.equipment])}") @pyqtSlot(str, str) def text_changed(self, key: str, new_value: str): - logger.debug(f"New value for {key}: {new_value}") match key: case "rsl_plate_num": setattr(self.procedure.run, key, new_value) @@ -131,20 +139,14 @@ class ProcedureCreation(QDialog): attribute['value'] = new_value.strip('\"') case _: setattr(self.procedure, key, new_value.strip('\"')) - logger.debug(f"Set value for {key}: {getattr(self.procedure, key)}") - # sys.exit() - - @pyqtSlot(str, bool) def check_toggle(self, key: str, ischecked: bool): - logger.debug(f"{key} is checked: {ischecked}") setattr(self.procedure, key, ischecked) @pyqtSlot(str) def update_kit(self, kittype): self.procedure.update_kittype_reagentroles(kittype=kittype) - logger.debug({k: v for k, v in self.procedure.__dict__.items() if k != "plate_map"}) self.set_html() @pyqtSlot(list) @@ -160,9 +162,7 @@ class ProcedureCreation(QDialog): from backend.validators.pydant import PydReagent expiry = datetime.datetime.strptime(expiry, "%Y-%m-%d") pyd = PydReagent(reagentrole=reagentrole, name=name, lot=lot, expiry=expiry) - logger.debug(pyd) self.procedure.reagentrole[reagentrole].insert(0, pyd) - logger.debug(pformat(self.procedure.__dict__)) self.set_html() @pyqtSlot(str, str) @@ -177,16 +177,3 @@ class ProcedureCreation(QDialog): def return_sql(self, new: bool = False): return self.procedure.to_sql(new=new) - -# class ProcedureWebViewer(QWebEngineView): -# -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# -# def contextMenuEvent(self, event: QContextMenuEvent): -# self.menu = self.page().createStandardContextMenu() -# self.menu = self.createStandardContextMenu() -# add_sample = QAction("Add Sample") -# self.menu = QMenu() -# self.menu.addAction(add_sample) -# self.menu.popup(event.globalPos()) diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index 8b7d68c..27f52c6 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -62,7 +62,7 @@ class SubmissionDetails(QDialog): css = f.read() key = object.__class__.__name__.lower() d = {key: details} - logger.debug(f"Using details: {pformat(d['procedure']['equipment'])}") + # logger.debug(f"Using details: {pformat(d['procedure']['equipment'])}") html = template.render(**d, css=[css]) self.webview.setHtml(html) self.setWindowTitle(f"{object.__class__.__name__} Details - {object.name}") diff --git a/src/submissions/frontend/widgets/submission_table.py b/src/submissions/frontend/widgets/submission_table.py index 5aec320..4230cd7 100644 --- a/src/submissions/frontend/widgets/submission_table.py +++ b/src/submissions/frontend/widgets/submission_table.py @@ -372,7 +372,7 @@ class SubmissionsTree(QTreeView): self.clear() self.data = [item.to_dict(full_data=True) for item in ClientSubmission.query(chronologic=True, page=page, page_size=page_size)] - logger.debug(f"setting data:\n {pformat(self.data)}") + # logger.debug(f"setting data:\n {pformat(self.data)}") # sys.exit() root = self.model.invisibleRootItem() for submission in self.data: