diff --git a/CHANGELOG.md b/CHANGELOG.md index 439cf92..aaaa9fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 202406.02 + +- Attached Contact to Submission. +- Renamed ReagentType to ReagentRole to prevent confusion. + ## 202405.04 - Improved Webview of submission details. diff --git a/src/submissions/backend/db/models/organizations.py b/src/submissions/backend/db/models/organizations.py index 5544485..41fa635 100644 --- a/src/submissions/backend/db/models/organizations.py +++ b/src/submissions/backend/db/models/organizations.py @@ -83,6 +83,7 @@ class Contact(BaseClass): email = Column(String(64)) #: contact email phone = Column(String(32)) #: contact phone number organization = relationship("Organization", back_populates="contacts", uselist=True, secondary=orgs_contacts) #: relationship to joined organization + submissions = relationship("BasicSubmission", back_populates="contact") #: submissions this contact has submitted def __repr__(self) -> str: """ diff --git a/src/submissions/backend/db/models/submissions.py b/src/submissions/backend/db/models/submissions.py index 63095cf..687c42a 100644 --- a/src/submissions/backend/db/models/submissions.py +++ b/src/submissions/backend/db/models/submissions.py @@ -10,7 +10,7 @@ from zipfile import ZipFile from tempfile import TemporaryDirectory from operator import attrgetter, itemgetter from pprint import pformat -from . import BaseClass, Reagent, SubmissionType, KitType, Organization +from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case from sqlalchemy.orm import relationship, validates, Query from sqlalchemy.orm.attributes import flag_modified @@ -65,6 +65,9 @@ class BasicSubmission(BaseClass): String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name cost_centre = Column( String(64)) #: Permanent storage of used cost centre in case organization field changed in the future. + contact = relationship("Contact", back_populates="submissions") #: client org + contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL", + name="fk_BS_contact_id")) #: client lab id from _organizations submission_sample_associations = relationship( "SubmissionSampleAssociation", @@ -255,6 +258,7 @@ class BasicSubmission(BaseClass): "sample_count": self.sample_count, "extraction_kit": ext_kit, "cost": self.run_cost, + } if report: return output @@ -304,6 +308,9 @@ class BasicSubmission(BaseClass): output["equipment"] = equipment output["cost_centre"] = cost_centre output["signed_by"] = self.signed_by + output["contact"] = self.contact.name + output["contact_phone"] = self.contact.phone + return output def calculate_column_count(self) -> int: @@ -453,6 +460,8 @@ class BasicSubmission(BaseClass): # logger.debug(f"Looking up organization: {value}") field_value = Organization.query(name=value) # logger.debug(f"Got {field_value} for organization {value}") + case "contact": + field_value = Contact.query(name=value) case "samples": for sample in value: # logger.debug(f"Parsing {sample} to sql.") @@ -1196,7 +1205,7 @@ class BacterialCulture(BasicSubmission): new_lot = matched.group() try: pos_control_reg = \ - [reg for reg in input_dict['reagents'] if reg['type'] == "Bacterial-Positive Control"][0] + [reg for reg in input_dict['reagents'] if reg['role'] == "Bacterial-Positive Control"][0] except IndexError: logger.error(f"No positive control reagent listed") return input_dict diff --git a/src/submissions/backend/excel/writer.py b/src/submissions/backend/excel/writer.py index bd5c483..90bd85e 100644 --- a/src/submissions/backend/excel/writer.py +++ b/src/submissions/backend/excel/writer.py @@ -148,7 +148,7 @@ class ReagentWriter(object): output = [] for reagent in reagent_list: try: - mp_info = map[reagent['type']] + mp_info = map[reagent['role']] except KeyError: continue placeholder = copy(reagent) diff --git a/src/submissions/backend/validators/pydant.py b/src/submissions/backend/validators/pydant.py index 228298c..ae0ef1d 100644 --- a/src/submissions/backend/validators/pydant.py +++ b/src/submissions/backend/validators/pydant.py @@ -104,7 +104,7 @@ class PydReagent(BaseModel): if value != None: return convert_nans_to_nones(str(value)) else: - return values.data['type'] + return values.data['role'] def improved_dict(self) -> dict: try: @@ -123,7 +123,7 @@ class PydReagent(BaseModel): # output[k] = value return {k: getattr(self, k) for k in fields} - def toSQL(self, submission: BasicSubmission | str = None) -> Tuple[Reagent, SubmissionReagentAssociation]: + def toSQL(self, submission: BasicSubmission | str = None) -> Tuple[Reagent, SubmissionReagentAssociation, Report]: """ Converts this instance into a backend.db.models.kit.Reagent instance @@ -168,6 +168,7 @@ class PydReagent(BaseModel): # reagent.reagent_submission_associations.append(assoc) else: assoc = None + report.add_result(Result(owner = __name__, code=0, msg="New reagent created.", status="Information")) else: if submission is not None and reagent not in submission.reagents: assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission) @@ -177,7 +178,8 @@ class PydReagent(BaseModel): assoc = None # add end-of-life extension from reagent type to expiry date # NOTE: this will now be done only in the reporting phase to account for potential changes in end-of-life extensions - return reagent, assoc + + return reagent, assoc, report class PydSample(BaseModel, extra='allow'): @@ -353,6 +355,7 @@ class PydSubmission(BaseModel, extra='allow'): samples: List[PydSample] equipment: List[PydEquipment] | None = [] cost_centre: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) + contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) @field_validator('equipment', mode='before') @classmethod @@ -567,6 +570,17 @@ class PydSubmission(BaseModel, extra='allow'): case _: return value + @field_validator("contact") + @classmethod + def get_contact_from_org(cls, value, values): + check = Contact.query(name=value['value']) + if check is None: + org = Organization.query(name=values.data['submitting_lab']['value']) + contact = org.contacts[0].name + return dict(value=contact, missing=True) + else: + return value + def __init__(self, **data): super().__init__(**data) # this could also be done with default_factory @@ -664,7 +678,7 @@ class PydSubmission(BaseModel, extra='allow'): instance.submission_reagent_associations = [] # logger.debug(f"Looking through {self.reagents}") for reagent in self.reagents: - reagent, assoc = reagent.toSQL(submission=instance) + reagent, assoc, _ = reagent.toSQL(submission=instance) # logger.debug(f"Association: {assoc}") if assoc is not None:# and assoc not in instance.submission_reagent_associations: instance.submission_reagent_associations.append(assoc) diff --git a/src/submissions/frontend/widgets/app.py b/src/submissions/frontend/widgets/app.py index d376abb..1048797 100644 --- a/src/submissions/frontend/widgets/app.py +++ b/src/submissions/frontend/widgets/app.py @@ -153,7 +153,7 @@ class App(QMainWindow): # logger.debug(f"We've got some results!") for result in self.report.results: # logger.debug(f"Showing result: {result}") - if result != None: + if result is not None: alert = result.report() if alert.exec(): pass diff --git a/src/submissions/frontend/widgets/submission_widget.py b/src/submissions/frontend/widgets/submission_widget.py index 07b2966..99453af 100644 --- a/src/submissions/frontend/widgets/submission_widget.py +++ b/src/submissions/frontend/widgets/submission_widget.py @@ -33,9 +33,10 @@ class SubmissionFormContainer(QWidget): # logger.debug(f"Setting form widget...") super().__init__(parent) self.app = self.parent().parent() + # logger.debug(f"App: {self.app}") self.report = Report() self.setAcceptDrops(True) - # if import_drag is emitted, importSubmission will fire + # NOTE: if import_drag is emitted, importSubmission will fire self.import_drag.connect(self.importSubmission) def dragEnterEvent(self, event): @@ -138,9 +139,10 @@ class SubmissionFormContainer(QWidget): # NOTE: create reagent object reagent = PydReagent(ctx=self.app.ctx, **info, missing=False) # NOTE: send reagent to db - sqlobj, result = reagent.toSQL() + sqlobj, assoc, result = reagent.toSQL() sqlobj.save() report.add_result(result) + self.app.report.add_result(report) self.app.result_reporter() return reagent @@ -310,10 +312,14 @@ class SubmissionFormWidget(QWidget): else: self.app.ctx.database_session.rollback() report.add_result(Result(msg="Overwrite cancelled", status="Information")) + self.app.report.add_result(report) + self.app.result_reporter() return # code 2: No RSL plate number given case 2: report.add_result(result) + self.app.report.add_result(report) + self.app.result_reporter() return case _: pass @@ -585,7 +591,7 @@ class SubmissionFormWidget(QWidget): # NOTE: if reagent doesn't exist in database, offer to add it (uses App.add_reagent) if wanted_reagent == None: dlg = QuestionAsker(title=f"Add {lot}?", - message=f"Couldn't find reagent type {self.reagent.type}: {lot} in the database.\n\nWould you like to add it?") + message=f"Couldn't find reagent type {self.reagent.role}: {lot} in the database.\n\nWould you like to add it?") if dlg.exec(): wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot, reagent_role=self.reagent.role, expiry=self.reagent.expiry, @@ -686,3 +692,4 @@ class SubmissionFormWidget(QWidget): # logger.debug(f"New relevant reagents: {relevant_reagents}") self.setObjectName(f"lot_{reagent.role}") self.addItems(relevant_reagents) + self.setStyleSheet("{ background-color: white }") diff --git a/src/submissions/tools.py b/src/submissions/tools.py index 47aded3..0e32d6e 100644 --- a/src/submissions/tools.py +++ b/src/submissions/tools.py @@ -375,7 +375,10 @@ class CustomFormatter(logging.Formatter): def format(self, record): log_fmt = self.FORMATS.get(record.levelno) formatter = logging.Formatter(log_fmt) - return formatter.format(record) + if check_if_app(): + return record + else: + return formatter.format(record) class StreamToLogger(object): @@ -473,7 +476,7 @@ def jinja_template_loading() -> Environment: Returns jinja2 template environment. Returns: - _type_: _description_ + Environment: jinja2 environment object """ # NOTE: determine if pyinstaller launcher is being used if check_if_app(): @@ -577,7 +580,7 @@ class Report(BaseModel): def add_result(self, result: Result | Report | None): match result: case Result(): - logger.debug(f"Adding {result} to results.") + logger.info(f"Adding {result} to results.") try: self.results.append(result) except AttributeError: @@ -585,11 +588,13 @@ class Report(BaseModel): case Report(): # logger.debug(f"Adding all results in report to new report") for res in result.results: - logger.debug(f"Adding {res} from to results.") + logger.info(f"Adding {res} from {result} to results.") self.results.append(res) case _: logger.error(f"Unknown variable type: {type(result)}") + def is_empty(self): + return bool(self.results) def rreplace(s, old, new): return (s[::-1].replace(old[::-1], new[::-1], 1))[::-1]