Renaming ReagentType to ReagentRole
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
## 202406.02
|
||||||
|
|
||||||
|
- Attached Contact to Submission.
|
||||||
|
- Renamed ReagentType to ReagentRole to prevent confusion.
|
||||||
|
|
||||||
## 202405.04
|
## 202405.04
|
||||||
|
|
||||||
- Improved Webview of submission details.
|
- Improved Webview of submission details.
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class Contact(BaseClass):
|
|||||||
email = Column(String(64)) #: contact email
|
email = Column(String(64)) #: contact email
|
||||||
phone = Column(String(32)) #: contact phone number
|
phone = Column(String(32)) #: contact phone number
|
||||||
organization = relationship("Organization", back_populates="contacts", uselist=True, secondary=orgs_contacts) #: relationship to joined organization
|
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:
|
def __repr__(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from zipfile import ZipFile
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from operator import attrgetter, itemgetter
|
from operator import attrgetter, itemgetter
|
||||||
from pprint import pformat
|
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 import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case
|
||||||
from sqlalchemy.orm import relationship, validates, Query
|
from sqlalchemy.orm import relationship, validates, Query
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
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
|
String(64)) #: ["Research", "Diagnostic", "Surveillance", "Validation"], else defaults to submission_type_name
|
||||||
cost_centre = Column(
|
cost_centre = Column(
|
||||||
String(64)) #: Permanent storage of used cost centre in case organization field changed in the future.
|
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(
|
submission_sample_associations = relationship(
|
||||||
"SubmissionSampleAssociation",
|
"SubmissionSampleAssociation",
|
||||||
@@ -255,6 +258,7 @@ class BasicSubmission(BaseClass):
|
|||||||
"sample_count": self.sample_count,
|
"sample_count": self.sample_count,
|
||||||
"extraction_kit": ext_kit,
|
"extraction_kit": ext_kit,
|
||||||
"cost": self.run_cost,
|
"cost": self.run_cost,
|
||||||
|
|
||||||
}
|
}
|
||||||
if report:
|
if report:
|
||||||
return output
|
return output
|
||||||
@@ -304,6 +308,9 @@ class BasicSubmission(BaseClass):
|
|||||||
output["equipment"] = equipment
|
output["equipment"] = equipment
|
||||||
output["cost_centre"] = cost_centre
|
output["cost_centre"] = cost_centre
|
||||||
output["signed_by"] = self.signed_by
|
output["signed_by"] = self.signed_by
|
||||||
|
output["contact"] = self.contact.name
|
||||||
|
output["contact_phone"] = self.contact.phone
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def calculate_column_count(self) -> int:
|
def calculate_column_count(self) -> int:
|
||||||
@@ -453,6 +460,8 @@ class BasicSubmission(BaseClass):
|
|||||||
# logger.debug(f"Looking up organization: {value}")
|
# logger.debug(f"Looking up organization: {value}")
|
||||||
field_value = Organization.query(name=value)
|
field_value = Organization.query(name=value)
|
||||||
# logger.debug(f"Got {field_value} for organization {value}")
|
# logger.debug(f"Got {field_value} for organization {value}")
|
||||||
|
case "contact":
|
||||||
|
field_value = Contact.query(name=value)
|
||||||
case "samples":
|
case "samples":
|
||||||
for sample in value:
|
for sample in value:
|
||||||
# logger.debug(f"Parsing {sample} to sql.")
|
# logger.debug(f"Parsing {sample} to sql.")
|
||||||
@@ -1196,7 +1205,7 @@ class BacterialCulture(BasicSubmission):
|
|||||||
new_lot = matched.group()
|
new_lot = matched.group()
|
||||||
try:
|
try:
|
||||||
pos_control_reg = \
|
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:
|
except IndexError:
|
||||||
logger.error(f"No positive control reagent listed")
|
logger.error(f"No positive control reagent listed")
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class ReagentWriter(object):
|
|||||||
output = []
|
output = []
|
||||||
for reagent in reagent_list:
|
for reagent in reagent_list:
|
||||||
try:
|
try:
|
||||||
mp_info = map[reagent['type']]
|
mp_info = map[reagent['role']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
placeholder = copy(reagent)
|
placeholder = copy(reagent)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class PydReagent(BaseModel):
|
|||||||
if value != None:
|
if value != None:
|
||||||
return convert_nans_to_nones(str(value))
|
return convert_nans_to_nones(str(value))
|
||||||
else:
|
else:
|
||||||
return values.data['type']
|
return values.data['role']
|
||||||
|
|
||||||
def improved_dict(self) -> dict:
|
def improved_dict(self) -> dict:
|
||||||
try:
|
try:
|
||||||
@@ -123,7 +123,7 @@ class PydReagent(BaseModel):
|
|||||||
# output[k] = value
|
# output[k] = value
|
||||||
return {k: getattr(self, k) for k in fields}
|
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
|
Converts this instance into a backend.db.models.kit.Reagent instance
|
||||||
|
|
||||||
@@ -168,6 +168,7 @@ class PydReagent(BaseModel):
|
|||||||
# reagent.reagent_submission_associations.append(assoc)
|
# reagent.reagent_submission_associations.append(assoc)
|
||||||
else:
|
else:
|
||||||
assoc = None
|
assoc = None
|
||||||
|
report.add_result(Result(owner = __name__, code=0, msg="New reagent created.", status="Information"))
|
||||||
else:
|
else:
|
||||||
if submission is not None and reagent not in submission.reagents:
|
if submission is not None and reagent not in submission.reagents:
|
||||||
assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission)
|
assoc = SubmissionReagentAssociation(reagent=reagent, submission=submission)
|
||||||
@@ -177,7 +178,8 @@ class PydReagent(BaseModel):
|
|||||||
assoc = None
|
assoc = None
|
||||||
# add end-of-life extension from reagent type to expiry date
|
# 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
|
# 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'):
|
class PydSample(BaseModel, extra='allow'):
|
||||||
@@ -353,6 +355,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
samples: List[PydSample]
|
samples: List[PydSample]
|
||||||
equipment: List[PydEquipment] | None = []
|
equipment: List[PydEquipment] | None = []
|
||||||
cost_centre: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
|
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')
|
@field_validator('equipment', mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -567,6 +570,17 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
case _:
|
case _:
|
||||||
return value
|
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):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
# this could also be done with default_factory
|
# this could also be done with default_factory
|
||||||
@@ -664,7 +678,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
instance.submission_reagent_associations = []
|
instance.submission_reagent_associations = []
|
||||||
# logger.debug(f"Looking through {self.reagents}")
|
# logger.debug(f"Looking through {self.reagents}")
|
||||||
for reagent in self.reagents:
|
for reagent in self.reagents:
|
||||||
reagent, assoc = reagent.toSQL(submission=instance)
|
reagent, assoc, _ = reagent.toSQL(submission=instance)
|
||||||
# logger.debug(f"Association: {assoc}")
|
# logger.debug(f"Association: {assoc}")
|
||||||
if assoc is not None:# and assoc not in instance.submission_reagent_associations:
|
if assoc is not None:# and assoc not in instance.submission_reagent_associations:
|
||||||
instance.submission_reagent_associations.append(assoc)
|
instance.submission_reagent_associations.append(assoc)
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class App(QMainWindow):
|
|||||||
# logger.debug(f"We've got some results!")
|
# logger.debug(f"We've got some results!")
|
||||||
for result in self.report.results:
|
for result in self.report.results:
|
||||||
# logger.debug(f"Showing result: {result}")
|
# logger.debug(f"Showing result: {result}")
|
||||||
if result != None:
|
if result is not None:
|
||||||
alert = result.report()
|
alert = result.report()
|
||||||
if alert.exec():
|
if alert.exec():
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# logger.debug(f"Setting form widget...")
|
# logger.debug(f"Setting form widget...")
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.app = self.parent().parent()
|
self.app = self.parent().parent()
|
||||||
|
# logger.debug(f"App: {self.app}")
|
||||||
self.report = Report()
|
self.report = Report()
|
||||||
self.setAcceptDrops(True)
|
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)
|
self.import_drag.connect(self.importSubmission)
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
@@ -138,9 +139,10 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# NOTE: create reagent object
|
# NOTE: create reagent object
|
||||||
reagent = PydReagent(ctx=self.app.ctx, **info, missing=False)
|
reagent = PydReagent(ctx=self.app.ctx, **info, missing=False)
|
||||||
# NOTE: send reagent to db
|
# NOTE: send reagent to db
|
||||||
sqlobj, result = reagent.toSQL()
|
sqlobj, assoc, result = reagent.toSQL()
|
||||||
sqlobj.save()
|
sqlobj.save()
|
||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
|
self.app.report.add_result(report)
|
||||||
self.app.result_reporter()
|
self.app.result_reporter()
|
||||||
return reagent
|
return reagent
|
||||||
|
|
||||||
@@ -310,10 +312,14 @@ class SubmissionFormWidget(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.app.ctx.database_session.rollback()
|
self.app.ctx.database_session.rollback()
|
||||||
report.add_result(Result(msg="Overwrite cancelled", status="Information"))
|
report.add_result(Result(msg="Overwrite cancelled", status="Information"))
|
||||||
|
self.app.report.add_result(report)
|
||||||
|
self.app.result_reporter()
|
||||||
return
|
return
|
||||||
# code 2: No RSL plate number given
|
# code 2: No RSL plate number given
|
||||||
case 2:
|
case 2:
|
||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
|
self.app.report.add_result(report)
|
||||||
|
self.app.result_reporter()
|
||||||
return
|
return
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
@@ -585,7 +591,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
# NOTE: if reagent doesn't exist in database, offer to add it (uses App.add_reagent)
|
# NOTE: if reagent doesn't exist in database, offer to add it (uses App.add_reagent)
|
||||||
if wanted_reagent == None:
|
if wanted_reagent == None:
|
||||||
dlg = QuestionAsker(title=f"Add {lot}?",
|
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():
|
if dlg.exec():
|
||||||
wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot, reagent_role=self.reagent.role,
|
wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot, reagent_role=self.reagent.role,
|
||||||
expiry=self.reagent.expiry,
|
expiry=self.reagent.expiry,
|
||||||
@@ -686,3 +692,4 @@ class SubmissionFormWidget(QWidget):
|
|||||||
# logger.debug(f"New relevant reagents: {relevant_reagents}")
|
# logger.debug(f"New relevant reagents: {relevant_reagents}")
|
||||||
self.setObjectName(f"lot_{reagent.role}")
|
self.setObjectName(f"lot_{reagent.role}")
|
||||||
self.addItems(relevant_reagents)
|
self.addItems(relevant_reagents)
|
||||||
|
self.setStyleSheet("{ background-color: white }")
|
||||||
|
|||||||
@@ -375,6 +375,9 @@ class CustomFormatter(logging.Formatter):
|
|||||||
def format(self, record):
|
def format(self, record):
|
||||||
log_fmt = self.FORMATS.get(record.levelno)
|
log_fmt = self.FORMATS.get(record.levelno)
|
||||||
formatter = logging.Formatter(log_fmt)
|
formatter = logging.Formatter(log_fmt)
|
||||||
|
if check_if_app():
|
||||||
|
return record
|
||||||
|
else:
|
||||||
return formatter.format(record)
|
return formatter.format(record)
|
||||||
|
|
||||||
|
|
||||||
@@ -473,7 +476,7 @@ def jinja_template_loading() -> Environment:
|
|||||||
Returns jinja2 template environment.
|
Returns jinja2 template environment.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
_type_: _description_
|
Environment: jinja2 environment object
|
||||||
"""
|
"""
|
||||||
# NOTE: determine if pyinstaller launcher is being used
|
# NOTE: determine if pyinstaller launcher is being used
|
||||||
if check_if_app():
|
if check_if_app():
|
||||||
@@ -577,7 +580,7 @@ class Report(BaseModel):
|
|||||||
def add_result(self, result: Result | Report | None):
|
def add_result(self, result: Result | Report | None):
|
||||||
match result:
|
match result:
|
||||||
case Result():
|
case Result():
|
||||||
logger.debug(f"Adding {result} to results.")
|
logger.info(f"Adding {result} to results.")
|
||||||
try:
|
try:
|
||||||
self.results.append(result)
|
self.results.append(result)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -585,11 +588,13 @@ class Report(BaseModel):
|
|||||||
case Report():
|
case Report():
|
||||||
# logger.debug(f"Adding all results in report to new report")
|
# logger.debug(f"Adding all results in report to new report")
|
||||||
for res in result.results:
|
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)
|
self.results.append(res)
|
||||||
case _:
|
case _:
|
||||||
logger.error(f"Unknown variable type: {type(result)}")
|
logger.error(f"Unknown variable type: {type(result)}")
|
||||||
|
|
||||||
|
def is_empty(self):
|
||||||
|
return bool(self.results)
|
||||||
|
|
||||||
def rreplace(s, old, new):
|
def rreplace(s, old, new):
|
||||||
return (s[::-1].replace(old[::-1], new[::-1], 1))[::-1]
|
return (s[::-1].replace(old[::-1], new[::-1], 1))[::-1]
|
||||||
|
|||||||
Reference in New Issue
Block a user