Bug fixing for new AddEdit forms.
This commit is contained in:
@@ -243,7 +243,10 @@ class BaseClass(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_pydantic_model(cls):
|
def get_pydantic_model(cls):
|
||||||
from backend.validators import pydant
|
from backend.validators import pydant
|
||||||
|
try:
|
||||||
model = getattr(pydant, f"Pyd{cls.__name__}")
|
model = getattr(pydant, f"Pyd{cls.__name__}")
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,10 @@ class Control(BaseClass):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
self.__database_session__.delete(self)
|
||||||
|
self.__database_session__.commit()
|
||||||
|
|
||||||
|
|
||||||
class PCRControl(Control):
|
class PCRControl(Control):
|
||||||
"""
|
"""
|
||||||
@@ -296,7 +300,7 @@ class PCRControl(Control):
|
|||||||
subtype = Column(String(16)) #: PC or NC
|
subtype = Column(String(16)) #: PC or NC
|
||||||
target = Column(String(16)) #: N1, N2, etc.
|
target = Column(String(16)) #: N1, N2, etc.
|
||||||
ct = Column(FLOAT) #: PCR result
|
ct = Column(FLOAT) #: PCR result
|
||||||
reagent_lot = Column(String(64), ForeignKey("_reagent.name", ondelete="SET NULL",
|
reagent_lot = Column(String(64), ForeignKey("_reagent.lot", ondelete="SET NULL",
|
||||||
name="fk_reagent_lot"))
|
name="fk_reagent_lot"))
|
||||||
reagent = relationship("Reagent", foreign_keys=reagent_lot) #: reagent used for this control
|
reagent = relationship("Reagent", foreign_keys=reagent_lot) #: reagent used for this control
|
||||||
|
|
||||||
|
|||||||
@@ -540,6 +540,24 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
report.add_result(Result(msg=f"Updating last used {rt} was not performed.", status="Information"))
|
report.add_result(Result(msg=f"Updating last used {rt} was not performed.", status="Information"))
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def query_or_create(cls, **kwargs) -> Reagent:
|
||||||
|
from backend.validators.pydant import PydReagent
|
||||||
|
new = False
|
||||||
|
instance = cls.query(**kwargs)
|
||||||
|
if not instance or isinstance(instance, list):
|
||||||
|
if "role" not in kwargs:
|
||||||
|
try:
|
||||||
|
kwargs['role'] = kwargs['name']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
instance = PydReagent(**kwargs)
|
||||||
|
new = True
|
||||||
|
instance, _ = instance.toSQL()
|
||||||
|
logger.debug(f"Instance: {instance}")
|
||||||
|
return instance, new
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
|
|||||||
@@ -936,14 +936,20 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
Generator[dict, None, None]: Dictionaries of row values.
|
Generator[dict, None, None]: Dictionaries of row values.
|
||||||
"""
|
"""
|
||||||
location_map = cls.get_submission_type().sample_map['pcr_controls']
|
location_map = cls.get_submission_type().sample_map['pcr_controls']
|
||||||
|
# logger.debug(f"Location map: {location_map}")
|
||||||
submission = cls.query(rsl_plate_num=rsl_plate_num)
|
submission = cls.query(rsl_plate_num=rsl_plate_num)
|
||||||
name_column = 1
|
name_column = 1
|
||||||
for item in location_map:
|
for item in location_map:
|
||||||
|
logger.debug(f"Checking {item}")
|
||||||
worksheet = xl[item['sheet']]
|
worksheet = xl[item['sheet']]
|
||||||
for iii, row in enumerate(worksheet.iter_rows(max_row=len(worksheet['A']), max_col=name_column), start=1):
|
for iii, row in enumerate(worksheet.iter_rows(max_row=len(worksheet['A']), max_col=name_column), start=1):
|
||||||
|
logger.debug(f"Checking row {row}, {iii}")
|
||||||
for cell in row:
|
for cell in row:
|
||||||
|
logger.debug(f"Checking cell: {cell}, with value {cell.value} against {item['name']}")
|
||||||
if cell.value == item['name']:
|
if cell.value == item['name']:
|
||||||
subtype, target = item['name'].split("-")
|
subtype, _ = item['name'].split("-")
|
||||||
|
target = item['target']
|
||||||
|
logger.debug(f"Subtype: {subtype}, target: {target}")
|
||||||
ct = worksheet.cell(row=iii, column=item['ct_column']).value
|
ct = worksheet.cell(row=iii, column=item['ct_column']).value
|
||||||
# NOTE: Kind of a stop gap solution to find control reagents.
|
# NOTE: Kind of a stop gap solution to find control reagents.
|
||||||
if subtype == "PC":
|
if subtype == "PC":
|
||||||
@@ -955,6 +961,9 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
ctrl = next((assoc.reagent for assoc in submission.submission_reagent_associations
|
ctrl = next((assoc.reagent for assoc in submission.submission_reagent_associations
|
||||||
if any(["molecular grade water" in item.name.lower() for item in
|
if any(["molecular grade water" in item.name.lower() for item in
|
||||||
assoc.reagent.role])), None)
|
assoc.reagent.role])), None)
|
||||||
|
else:
|
||||||
|
ctrl = None
|
||||||
|
logger.debug(f"Control reagent: {ctrl.__dict__}")
|
||||||
try:
|
try:
|
||||||
ct = float(ct)
|
ct = float(ct)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -963,13 +972,15 @@ class BasicSubmission(BaseClass, LogMixin):
|
|||||||
ctrl = ctrl.lot
|
ctrl = ctrl.lot
|
||||||
else:
|
else:
|
||||||
ctrl = None
|
ctrl = None
|
||||||
yield dict(
|
output = dict(
|
||||||
name=f"{rsl_plate_num}<{item['name']}>",
|
name=f"{rsl_plate_num}<{item['name']}-{target}>",
|
||||||
ct=ct,
|
ct=ct,
|
||||||
subtype=subtype,
|
subtype=subtype,
|
||||||
target=target,
|
target=target,
|
||||||
reagent_lot=ctrl
|
reagent_lot=ctrl
|
||||||
)
|
)
|
||||||
|
logger.debug(f"Control output: {pformat(output)}")
|
||||||
|
yield output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filename_template(cls) -> str:
|
def filename_template(cls) -> str:
|
||||||
@@ -1663,10 +1674,12 @@ class Wastewater(BasicSubmission):
|
|||||||
submitted_date = datetime.strptime(" ".join(parser.pcr['run_start_date/time'].split(" ")[:-1]),
|
submitted_date = datetime.strptime(" ".join(parser.pcr['run_start_date/time'].split(" ")[:-1]),
|
||||||
"%Y-%m-%d %I:%M:%S %p")
|
"%Y-%m-%d %I:%M:%S %p")
|
||||||
for control in pcr_controls:
|
for control in pcr_controls:
|
||||||
|
logger.debug(f"Control coming into save: {control}")
|
||||||
new_control = PCRControl(**control)
|
new_control = PCRControl(**control)
|
||||||
new_control.submitted_date = submitted_date
|
new_control.submitted_date = submitted_date
|
||||||
new_control.controltype = controltype
|
new_control.controltype = controltype
|
||||||
new_control.submission = self
|
new_control.submission = self
|
||||||
|
logger.debug(f"Control coming into save: {new_control.__dict__}")
|
||||||
new_control.save()
|
new_control.save()
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|||||||
@@ -257,9 +257,10 @@ class ReagentParser(object):
|
|||||||
extraction_kit = extraction_kit['value']
|
extraction_kit = extraction_kit['value']
|
||||||
self.kit_object = KitType.query(name=extraction_kit)
|
self.kit_object = KitType.query(name=extraction_kit)
|
||||||
self.map = self.fetch_kit_info_map(submission_type=submission_type)
|
self.map = self.fetch_kit_info_map(submission_type=submission_type)
|
||||||
|
logger.debug(f"Setting map: {self.map}")
|
||||||
self.xl = xl
|
self.xl = xl
|
||||||
|
|
||||||
@report_result
|
# @report_result
|
||||||
def fetch_kit_info_map(self, submission_type: str | SubmissionType) -> Tuple[Report, dict]:
|
def fetch_kit_info_map(self, submission_type: str | SubmissionType) -> Tuple[Report, dict]:
|
||||||
"""
|
"""
|
||||||
Gets location of kit reagents from database
|
Gets location of kit reagents from database
|
||||||
@@ -298,7 +299,8 @@ class ReagentParser(object):
|
|||||||
msg=f"No kit map found for {self.kit_object.name}.\n\n"
|
msg=f"No kit map found for {self.kit_object.name}.\n\n"
|
||||||
f"Are you sure you put the right kit in:\n\n{location_string}?",
|
f"Are you sure you put the right kit in:\n\n{location_string}?",
|
||||||
status="Critical"))
|
status="Critical"))
|
||||||
return report, reagent_map
|
logger.debug(f"Here is the map coming out: {reagent_map}")
|
||||||
|
return reagent_map
|
||||||
|
|
||||||
def parse_reagents(self) -> Generator[dict, None, None]:
|
def parse_reagents(self) -> Generator[dict, None, None]:
|
||||||
"""
|
"""
|
||||||
@@ -310,7 +312,7 @@ class ReagentParser(object):
|
|||||||
for sheet in self.xl.sheetnames:
|
for sheet in self.xl.sheetnames:
|
||||||
ws = self.xl[sheet]
|
ws = self.xl[sheet]
|
||||||
relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['sheet']}
|
relevant = {k.strip(): v for k, v in self.map.items() if sheet in self.map[k]['sheet']}
|
||||||
if relevant == {}:
|
if not relevant:
|
||||||
continue
|
continue
|
||||||
for item in relevant:
|
for item in relevant:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
class PydReagent(BaseModel):
|
class PydReagent(BaseModel):
|
||||||
lot: str | None
|
lot: str | None
|
||||||
role: str | None
|
role: str | None
|
||||||
expiry: date | Literal['NA'] | None
|
expiry: date | datetime | Literal['NA'] | None = Field(default=None, validate_default=True)
|
||||||
name: str | None
|
name: str | None = Field(default=None, validate_default=True)
|
||||||
missing: bool = Field(default=True)
|
missing: bool = Field(default=True)
|
||||||
comment: str | None = Field(default="", validate_default=True)
|
comment: str | None = Field(default="", validate_default=True)
|
||||||
|
|
||||||
@@ -79,6 +79,8 @@ class PydReagent(BaseModel):
|
|||||||
case str():
|
case str():
|
||||||
return parse(value)
|
return parse(value)
|
||||||
case date():
|
case date():
|
||||||
|
return datetime.combine(value, datetime.max.time())
|
||||||
|
case datetime():
|
||||||
return value
|
return value
|
||||||
case _:
|
case _:
|
||||||
return convert_nans_to_nones(str(value))
|
return convert_nans_to_nones(str(value))
|
||||||
@@ -939,6 +941,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
ext_kit.get_reagents(required=True, submission_type=self.submission_type['value'])]
|
ext_kit.get_reagents(required=True, submission_type=self.submission_type['value'])]
|
||||||
# NOTE: Exclude any reagenttype found in this pyd not expected in kit.
|
# NOTE: Exclude any reagenttype found in this pyd not expected in kit.
|
||||||
expected_check = [item.role for item in ext_kit_rtypes]
|
expected_check = [item.role for item in ext_kit_rtypes]
|
||||||
|
logger.debug(self.reagents)
|
||||||
output_reagents = [rt for rt in self.reagents if rt.role in expected_check]
|
output_reagents = [rt for rt in self.reagents if rt.role in expected_check]
|
||||||
missing_check = [item.role for item in output_reagents]
|
missing_check = [item.role for item in output_reagents]
|
||||||
missing_reagents = [rt for rt in ext_kit_rtypes if rt.role not in missing_check and rt.role not in exempt]
|
missing_reagents = [rt for rt in ext_kit_rtypes if rt.role not in missing_check and rt.role not in exempt]
|
||||||
@@ -956,7 +959,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
return output_reagents, report, missing_reagents
|
return output_reagents, report, missing_reagents
|
||||||
|
|
||||||
def check_reagent_expiries(self, exempt: List[PydReagent]=[]):
|
def check_reagent_expiries(self, exempt: List[PydReagent] = []):
|
||||||
report = Report()
|
report = Report()
|
||||||
expired = []
|
expired = []
|
||||||
for reagent in self.reagents:
|
for reagent in self.reagents:
|
||||||
@@ -971,14 +974,11 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
if expired:
|
if expired:
|
||||||
output = '\n'.join(expired)
|
output = '\n'.join(expired)
|
||||||
result = Result(status="Warning",
|
result = Result(status="Warning",
|
||||||
msg = f"The following reagents are expired:\n\n{output}"
|
msg=f"The following reagents are expired:\n\n{output}"
|
||||||
)
|
)
|
||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def export_csv(self, filename: Path | str):
|
def export_csv(self, filename: Path | str):
|
||||||
try:
|
try:
|
||||||
worksheet = self.csv
|
worksheet = self.csv
|
||||||
@@ -1009,14 +1009,34 @@ class PydContact(BaseModel):
|
|||||||
logger.debug(f"Output phone: {value}")
|
logger.debug(f"Output phone: {value}")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def toSQL(self) -> Contact:
|
def toSQL(self) -> Tuple[Contact, Report]:
|
||||||
"""
|
"""
|
||||||
Converts this instance into a backend.db.models.organization.Contact instance
|
Converts this instance into a backend.db.models.organization. Contact instance.
|
||||||
|
Does not query for existing contacts.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Contact: Contact instance
|
Contact: Contact instance
|
||||||
"""
|
"""
|
||||||
return Contact(name=self.name, phone=self.phone, email=self.email)
|
report = Report()
|
||||||
|
instance = Contact.query(name=self.name, phone=self.phone, email=self.email)
|
||||||
|
if not instance or isinstance(instance, list):
|
||||||
|
instance = Contact()
|
||||||
|
try:
|
||||||
|
all_fields = self.model_fields + self.model_extra
|
||||||
|
except TypeError:
|
||||||
|
all_fields = self.model_fields
|
||||||
|
for field in all_fields:
|
||||||
|
value = getattr(self, field)
|
||||||
|
match field:
|
||||||
|
case "organization":
|
||||||
|
value = [Organization.query(name=value)]
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
instance.__setattr__(field, value)
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Could not set {instance} {field} to {value} due to {e}")
|
||||||
|
return instance, report
|
||||||
|
|
||||||
|
|
||||||
class PydOrganization(BaseModel):
|
class PydOrganization(BaseModel):
|
||||||
|
|||||||
@@ -16,3 +16,5 @@ from .submission_table import *
|
|||||||
from .submission_widget import *
|
from .submission_widget import *
|
||||||
from .summary import *
|
from .summary import *
|
||||||
from .turnaround import *
|
from .turnaround import *
|
||||||
|
from .omni_add_edit import *
|
||||||
|
from .omni_manager import *
|
||||||
|
|||||||
@@ -1,59 +1,78 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Any
|
from pprint import pformat
|
||||||
|
from typing import Any, List, Tuple
|
||||||
|
from pydantic import BaseModel
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QLabel, QDialog, QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit
|
QLabel, QDialog, QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit
|
||||||
)
|
)
|
||||||
from sqlalchemy import String, TIMESTAMP
|
from sqlalchemy import String, TIMESTAMP
|
||||||
from sqlalchemy.orm import InstrumentedAttribute
|
from sqlalchemy.orm import InstrumentedAttribute, ColumnProperty
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||||
|
|
||||||
|
from tools import Report, Result
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
class AddEdit(QDialog):
|
class AddEdit(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, instance: Any|None=None):
|
def __init__(self, parent, instance: Any | None = None, manager: str = ""):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.object_type = instance.__class__
|
self.object_type = instance.__class__
|
||||||
self.layout = QGridLayout(self)
|
self.layout = QGridLayout(self)
|
||||||
|
logger.debug(f"Manager: {manager}")
|
||||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
# fields = {k: v for k, v in self.object_type.__dict__.items() if
|
fields = {key: dict(class_attr=getattr(self.object_type, key), instance_attr=getattr(self.instance, key))
|
||||||
# isinstance(v, InstrumentedAttribute) and k != "id"}
|
for key in dir(self.object_type) if isinstance(getattr(self.object_type, key), InstrumentedAttribute)
|
||||||
fields = {k: v for k, v in self.object_type.__dict__.items() if k != "id"}
|
and "id" not in key and key != manager}
|
||||||
for key, field in fields.items():
|
# NOTE: Move 'name' to the front
|
||||||
logger.debug(f"")
|
|
||||||
try:
|
try:
|
||||||
widget = EditProperty(self, key=key, column_type=field.property.expression.type,
|
fields = {'name': fields.pop('name'), **fields}
|
||||||
value=getattr(self.instance, key))
|
except KeyError:
|
||||||
|
pass
|
||||||
|
logger.debug(pformat(fields, indent=4))
|
||||||
|
height_counter = 0
|
||||||
|
for key, field in fields.items():
|
||||||
|
try:
|
||||||
|
value = getattr(self.instance, key)
|
||||||
|
except AttributeError:
|
||||||
|
value = None
|
||||||
|
try:
|
||||||
|
logger.debug(f"{key} property: {type(field['class_attr'].property)}")
|
||||||
|
# widget = EditProperty(self, key=key, column_type=field.property.expression.type,
|
||||||
|
# value=getattr(self.instance, key))
|
||||||
|
logger.debug(f"Column type: {field}, Value: {value}")
|
||||||
|
widget = EditProperty(self, key=key, column_type=field, value=value)
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
logger.error(f"Problem setting widget {key}: {e}")
|
logger.error(f"Problem setting widget {key}: {e}")
|
||||||
continue
|
continue
|
||||||
|
if widget:
|
||||||
self.layout.addWidget(widget, self.layout.rowCount(), 0)
|
self.layout.addWidget(widget, self.layout.rowCount(), 0)
|
||||||
|
height_counter += 1
|
||||||
self.layout.addWidget(self.buttonBox)
|
self.layout.addWidget(self.buttonBox)
|
||||||
self.setWindowTitle(f"Add/Edit {self.object_type.__name__}")
|
self.setWindowTitle(f"Add/Edit {self.object_type.__name__}")
|
||||||
self.setMinimumSize(600, 50 * len(fields))
|
self.setMinimumSize(600, 50 * height_counter)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
def parse_form(self):
|
def parse_form(self) -> Tuple[BaseModel, Report]:
|
||||||
results = {result[0]:result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)]}
|
report = Report()
|
||||||
# logger.debug(results)
|
parsed = {result[0].strip(":"): result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)] if result[0]}
|
||||||
|
logger.debug(parsed)
|
||||||
model = self.object_type.get_pydantic_model()
|
model = self.object_type.get_pydantic_model()
|
||||||
model = model(**results)
|
# NOTE: Hand-off to pydantic model for validation.
|
||||||
try:
|
# NOTE: Also, why am I not just using the toSQL method here. I could write one for contacts.
|
||||||
extras = list(model.model_extra.keys())
|
model = model(**parsed)
|
||||||
except AttributeError:
|
# output, result = model.toSQL()
|
||||||
extras = []
|
# report.add_result(result)
|
||||||
fields = list(model.model_fields.keys()) + extras
|
# if len(report.results) < 1:
|
||||||
for field in fields:
|
# report.add_result(Result(msg="Added new regeant.", icon="Information", owner=__name__))
|
||||||
# logger.debug(result)
|
return model, report
|
||||||
self.instance.__setattr__(field, model.__getattribute__(field))
|
|
||||||
return self.instance
|
|
||||||
|
|
||||||
|
|
||||||
class EditProperty(QWidget):
|
class EditProperty(QWidget):
|
||||||
@@ -64,7 +83,36 @@ class EditProperty(QWidget):
|
|||||||
self.layout = QGridLayout()
|
self.layout = QGridLayout()
|
||||||
self.layout.addWidget(self.label, 0, 0, 1, 1)
|
self.layout.addWidget(self.label, 0, 0, 1, 1)
|
||||||
self.setObjectName(key)
|
self.setObjectName(key)
|
||||||
match column_type:
|
match column_type['class_attr'].property:
|
||||||
|
case ColumnProperty():
|
||||||
|
self.column_property_set(column_type, value=value)
|
||||||
|
case _RelationshipDeclared():
|
||||||
|
self.relationship_property_set(column_type, value=value)
|
||||||
|
case _:
|
||||||
|
logger.error(f"{column_type} not a supported type.")
|
||||||
|
return
|
||||||
|
self.layout.addWidget(self.widget, 0, 1, 1, 3)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def relationship_property_set(self, relationship_property, value=None):
|
||||||
|
# print(relationship_property)
|
||||||
|
self.property_class = relationship_property['class_attr'].property.entity.class_
|
||||||
|
self.is_list = relationship_property['class_attr'].property.uselist
|
||||||
|
choices = [item.name for item in self.property_class.query()]
|
||||||
|
try:
|
||||||
|
instance_value = getattr(self.parent().instance, self.objectName().strip(":"))
|
||||||
|
except AttributeError:
|
||||||
|
logger.debug(f"Unable to get instance {self.parent().instance} attribute: {self.objectName()}")
|
||||||
|
instance_value = None
|
||||||
|
if isinstance(instance_value, list):
|
||||||
|
instance_value = next((item.name for item in instance_value), None)
|
||||||
|
if instance_value:
|
||||||
|
choices.insert(0, choices.pop(choices.index(instance_value)))
|
||||||
|
self.widget = QComboBox()
|
||||||
|
self.widget.addItems(choices)
|
||||||
|
|
||||||
|
def column_property_set(self, column_property, value=None):
|
||||||
|
match column_property['class_attr'].expression.type:
|
||||||
case String():
|
case String():
|
||||||
if not value:
|
if not value:
|
||||||
value = ""
|
value = ""
|
||||||
@@ -76,22 +124,25 @@ class EditProperty(QWidget):
|
|||||||
value = date.today()
|
value = date.today()
|
||||||
self.widget.setDate(value)
|
self.widget.setDate(value)
|
||||||
case _:
|
case _:
|
||||||
logger.error(f"{column_type} not a supported type.")
|
logger.error(f"{column_property} not a supported property.")
|
||||||
self.widget = None
|
self.widget = None
|
||||||
return
|
|
||||||
self.layout.addWidget(self.widget, 0, 1, 1, 3)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
|
|
||||||
def parse_form(self):
|
def parse_form(self):
|
||||||
|
try:
|
||||||
|
check = self.widget
|
||||||
|
except AttributeError:
|
||||||
|
return None, None
|
||||||
match self.widget:
|
match self.widget:
|
||||||
case QLineEdit():
|
case QLineEdit():
|
||||||
value = self.widget.text()
|
value = self.widget.text()
|
||||||
case QDateEdit():
|
case QDateEdit():
|
||||||
value = self.widget.date()
|
value = self.widget.date().toPyDate()
|
||||||
|
case QComboBox():
|
||||||
|
value = self.widget.currentText()
|
||||||
|
# if self.is_list:
|
||||||
|
# value = [self.property_class.query(name=prelim)]
|
||||||
|
# else:
|
||||||
|
# value = self.property_class.query(name=prelim)
|
||||||
case _:
|
case _:
|
||||||
value = None
|
value = None
|
||||||
return self.objectName(), value
|
return self.objectName(), value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class ManagerWindow(QDialog):
|
|||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
def add_new(self):
|
def add_new(self):
|
||||||
dlg = AddEdit(parent=self, instance=self.object_type())
|
dlg = AddEdit(parent=self, instance=self.object_type(), manager=self.object_type.__name__.lower())
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
new_instance = dlg.parse_form()
|
new_instance = dlg.parse_form()
|
||||||
# logger.debug(new_instance.__dict__)
|
# logger.debug(new_instance.__dict__)
|
||||||
@@ -182,7 +182,7 @@ class EditRelationship(QWidget):
|
|||||||
def add_new(self, instance: Any = None):
|
def add_new(self, instance: Any = None):
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = self.entity()
|
instance = self.entity()
|
||||||
dlg = AddEdit(self, instance=instance)
|
dlg = AddEdit(self, instance=instance, manager=self.parent().object_type.__name__.lower())
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
new_instance = dlg.parse_form()
|
new_instance = dlg.parse_form()
|
||||||
# logger.debug(new_instance.__dict__)
|
# logger.debug(new_instance.__dict__)
|
||||||
@@ -190,16 +190,15 @@ class EditRelationship(QWidget):
|
|||||||
if isinstance(addition, InstrumentedList):
|
if isinstance(addition, InstrumentedList):
|
||||||
addition.append(new_instance)
|
addition.append(new_instance)
|
||||||
self.parent().instance.save()
|
self.parent().instance.save()
|
||||||
|
|
||||||
self.parent().update_data()
|
self.parent().update_data()
|
||||||
|
|
||||||
def add_existing(self):
|
def add_existing(self):
|
||||||
dlg = SearchBox(self, object_type=self.entity, returnable=True, extras=[])
|
dlg = SearchBox(self, object_type=self.entity, returnable=True, extras=[])
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
rows = dlg.return_selected_rows()
|
rows = dlg.return_selected_rows()
|
||||||
# print(f"Rows selected: {[row for row in rows]}")
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
instance = self.entity.query(**row)
|
instance = self.entity.query(**row)
|
||||||
# logger.debug(instance)
|
|
||||||
addition = getattr(self.parent().instance, self.objectName())
|
addition = getattr(self.parent().instance, self.objectName())
|
||||||
if isinstance(addition, InstrumentedList):
|
if isinstance(addition, InstrumentedList):
|
||||||
addition.append(instance)
|
addition.append(instance)
|
||||||
|
|||||||
@@ -140,14 +140,22 @@ class SubmissionFormContainer(QWidget):
|
|||||||
self.layout().addWidget(self.form)
|
self.layout().addWidget(self.form)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
@report_result
|
||||||
def new_add_reagent(self):
|
def new_add_reagent(self, instance: Reagent | None = None):
|
||||||
|
report = Report()
|
||||||
|
if not instance:
|
||||||
instance = Reagent()
|
instance = Reagent()
|
||||||
dlg = AddEdit(parent=self, instance=instance)
|
dlg = AddEdit(parent=self, instance=instance)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
obj = dlg.parse_form()
|
reagent, result = dlg.parse_form()
|
||||||
print(obj)
|
reagent.missing = False
|
||||||
|
logger.debug(f"Reagent: {reagent}, result: {result}")
|
||||||
|
report.add_result(result)
|
||||||
|
# NOTE: send reagent to db
|
||||||
|
sqlobj, result = reagent.toSQL()
|
||||||
|
sqlobj.save()
|
||||||
|
report.add_result(result)
|
||||||
|
return reagent, report
|
||||||
|
|
||||||
@report_result
|
@report_result
|
||||||
def add_reagent(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
def add_reagent(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
|
||||||
@@ -183,7 +191,6 @@ class SubmissionFormContainer(QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class SubmissionFormWidget(QWidget):
|
class SubmissionFormWidget(QWidget):
|
||||||
|
|
||||||
update_reagent_fields = ['extraction_kit']
|
update_reagent_fields = ['extraction_kit']
|
||||||
|
|
||||||
def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None:
|
def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None:
|
||||||
@@ -235,7 +242,6 @@ class SubmissionFormWidget(QWidget):
|
|||||||
for reagent in self.findChildren(self.ReagentFormWidget):
|
for reagent in self.findChildren(self.ReagentFormWidget):
|
||||||
reagent.flip_check(self.disabler.checkbox.isChecked())
|
reagent.flip_check(self.disabler.checkbox.isChecked())
|
||||||
|
|
||||||
|
|
||||||
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType | None = None,
|
def create_widget(self, key: str, value: dict | PydReagent, submission_type: str | SubmissionType | None = None,
|
||||||
extraction_kit: str | None = None, sub_obj: BasicSubmission | None = None,
|
extraction_kit: str | None = None, sub_obj: BasicSubmission | None = None,
|
||||||
disable: bool = False) -> "self.InfoItem":
|
disable: bool = False) -> "self.InfoItem":
|
||||||
@@ -294,8 +300,10 @@ class SubmissionFormWidget(QWidget):
|
|||||||
if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton):
|
if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton):
|
||||||
reagent.setParent(None)
|
reagent.setParent(None)
|
||||||
reagents, integrity_report, missing_reagents = self.pyd.check_kit_integrity(extraction_kit=self.extraction_kit)
|
reagents, integrity_report, missing_reagents = self.pyd.check_kit_integrity(extraction_kit=self.extraction_kit)
|
||||||
|
logger.debug(f"Reagents: {reagents}")
|
||||||
expiry_report = self.pyd.check_reagent_expiries(exempt=missing_reagents)
|
expiry_report = self.pyd.check_reagent_expiries(exempt=missing_reagents)
|
||||||
for reagent in reagents:
|
for reagent in reagents:
|
||||||
|
|
||||||
add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.extraction_kit)
|
add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.extraction_kit)
|
||||||
self.layout.addWidget(add_widget)
|
self.layout.addWidget(add_widget)
|
||||||
report.add_result(integrity_report)
|
report.add_result(integrity_report)
|
||||||
@@ -432,7 +440,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
for widget in self.findChildren(QWidget):
|
for widget in self.findChildren(QWidget):
|
||||||
match widget:
|
match widget:
|
||||||
case self.ReagentFormWidget():
|
case self.ReagentFormWidget():
|
||||||
reagent, _ = widget.parse_form()
|
reagent = widget.parse_form()
|
||||||
if reagent is not None:
|
if reagent is not None:
|
||||||
reagents.append(reagent)
|
reagents.append(reagent)
|
||||||
case self.InfoItem():
|
case self.InfoItem():
|
||||||
@@ -440,6 +448,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
if field is not None:
|
if field is not None:
|
||||||
info[field] = value
|
info[field] = value
|
||||||
self.pyd.reagents = reagents
|
self.pyd.reagents = reagents
|
||||||
|
logger.debug(f"Reagents from form: {reagents}")
|
||||||
for item in self.recover:
|
for item in self.recover:
|
||||||
if hasattr(self, item):
|
if hasattr(self, item):
|
||||||
value = getattr(self, item)
|
value = getattr(self, item)
|
||||||
@@ -661,7 +670,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
# NOTE: If changed set self.missing to True and update self.label
|
# NOTE: If changed set self.missing to True and update self.label
|
||||||
self.lot.currentTextChanged.connect(self.updated)
|
self.lot.currentTextChanged.connect(self.updated)
|
||||||
|
|
||||||
def flip_check(self, checked:bool):
|
def flip_check(self, checked: bool):
|
||||||
with QSignalBlocker(self.check) as b:
|
with QSignalBlocker(self.check) as b:
|
||||||
self.check.setChecked(checked)
|
self.check.setChecked(checked)
|
||||||
self.lot.setEnabled(checked)
|
self.lot.setEnabled(checked)
|
||||||
@@ -675,6 +684,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.parent().disabler.checkbox.setChecked(True)
|
self.parent().disabler.checkbox.setChecked(True)
|
||||||
|
|
||||||
|
@report_result
|
||||||
def parse_form(self) -> Tuple[PydReagent | None, Report]:
|
def parse_form(self) -> Tuple[PydReagent | None, Report]:
|
||||||
"""
|
"""
|
||||||
Pulls form info into PydReagent
|
Pulls form info into PydReagent
|
||||||
@@ -686,18 +696,23 @@ class SubmissionFormWidget(QWidget):
|
|||||||
if not self.lot.isEnabled():
|
if not self.lot.isEnabled():
|
||||||
return None, report
|
return None, report
|
||||||
lot = self.lot.currentText()
|
lot = self.lot.currentText()
|
||||||
wanted_reagent = Reagent.query(lot=lot, role=self.reagent.role)
|
wanted_reagent, new = Reagent.query_or_create(lot=lot, role=self.reagent.role)
|
||||||
# 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 is None:
|
logger.debug(f"Wanted reagent: {wanted_reagent}, New: {new}")
|
||||||
|
# if wanted_reagent is None:
|
||||||
|
if new:
|
||||||
dlg = QuestionAsker(title=f"Add {lot}?",
|
dlg = QuestionAsker(title=f"Add {lot}?",
|
||||||
message=f"Couldn't find reagent type {self.reagent.role}: {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,
|
# wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot,
|
||||||
reagent_role=self.reagent.role,
|
# reagent_role=self.reagent.role,
|
||||||
expiry=self.reagent.expiry,
|
# expiry=self.reagent.expiry,
|
||||||
name=self.reagent.name,
|
# name=self.reagent.name,
|
||||||
kit=self.extraction_kit
|
# kit=self.extraction_kit
|
||||||
)
|
# )
|
||||||
|
wanted_reagent = self.parent().parent().new_add_reagent(instance=wanted_reagent)
|
||||||
|
|
||||||
return wanted_reagent, report
|
return wanted_reagent, report
|
||||||
else:
|
else:
|
||||||
# NOTE: In this case we will have an empty reagent and the submission will fail kit integrity check
|
# NOTE: In this case we will have an empty reagent and the submission will fail kit integrity check
|
||||||
@@ -707,10 +722,13 @@ class SubmissionFormWidget(QWidget):
|
|||||||
# NOTE: Since this now gets passed in directly from the parser -> pyd -> form and the parser gets the name
|
# NOTE: Since this now gets passed in directly from the parser -> pyd -> form and the parser gets the name
|
||||||
# from the db, it should no longer be necessary to query the db with reagent/kit, but with rt name directly.
|
# from the db, it should no longer be necessary to query the db with reagent/kit, but with rt name directly.
|
||||||
rt = ReagentRole.query(name=self.reagent.role)
|
rt = ReagentRole.query(name=self.reagent.role)
|
||||||
|
logger.debug(f"Reagent role: {rt}")
|
||||||
if rt is None:
|
if rt is None:
|
||||||
rt = ReagentRole.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
rt = ReagentRole.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
||||||
return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
final = PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||||
expiry=wanted_reagent.expiry, missing=False), report
|
expiry=wanted_reagent.expiry.date(), missing=False)
|
||||||
|
logger.debug(f"Final Reagent: {final}")
|
||||||
|
return final, report
|
||||||
|
|
||||||
def updated(self):
|
def updated(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1032,18 +1032,19 @@ def report_result(func):
|
|||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
logger.info(f"Report result being called by {func.__name__}")
|
logger.info(f"Report result being called by {func.__name__}")
|
||||||
output = func(*args, **kwargs)
|
output = func(*args, **kwargs)
|
||||||
|
# logger.debug(f"Function output: {output}")
|
||||||
match output:
|
match output:
|
||||||
case Report():
|
case Report():
|
||||||
report = output
|
report = output
|
||||||
case tuple():
|
case tuple():
|
||||||
try:
|
# try:
|
||||||
report = [item for item in output if isinstance(item, Report)][0]
|
report = next((item for item in output if isinstance(item, Report)), None)
|
||||||
except IndexError:
|
# except IndexError:
|
||||||
report = None
|
# report = None
|
||||||
case _:
|
case _:
|
||||||
report = None
|
report = Report()
|
||||||
return report
|
# return report
|
||||||
logger.info(f"Got report: {report}")
|
# logger.info(f"Got report: {report}")
|
||||||
try:
|
try:
|
||||||
results = report.results
|
results = report.results
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -1058,6 +1059,7 @@ def report_result(func):
|
|||||||
logger.error(result.msg)
|
logger.error(result.msg)
|
||||||
if output:
|
if output:
|
||||||
true_output = tuple(item for item in output if not isinstance(item, Report))
|
true_output = tuple(item for item in output if not isinstance(item, Report))
|
||||||
|
# logger.debug(f"True output: {true_output}")
|
||||||
if len(true_output) == 1:
|
if len(true_output) == 1:
|
||||||
true_output = true_output[0]
|
true_output = true_output[0]
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user