Bug fixing for new AddEdit forms.
This commit is contained in:
@@ -16,3 +16,5 @@ from .submission_table import *
|
||||
from .submission_widget import *
|
||||
from .summary import *
|
||||
from .turnaround import *
|
||||
from .omni_add_edit import *
|
||||
from .omni_manager import *
|
||||
|
||||
@@ -1,59 +1,78 @@
|
||||
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 (
|
||||
QLabel, QDialog, QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit
|
||||
)
|
||||
from sqlalchemy import String, TIMESTAMP
|
||||
from sqlalchemy.orm import InstrumentedAttribute
|
||||
from sqlalchemy.orm import InstrumentedAttribute, ColumnProperty
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
|
||||
from tools import Report, Result
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
class AddEdit(QDialog):
|
||||
|
||||
def __init__(self, parent, instance: Any|None=None):
|
||||
def __init__(self, parent, instance: Any | None = None, manager: str = ""):
|
||||
super().__init__(parent)
|
||||
self.instance = instance
|
||||
self.object_type = instance.__class__
|
||||
self.layout = QGridLayout(self)
|
||||
|
||||
logger.debug(f"Manager: {manager}")
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
# fields = {k: v for k, v in self.object_type.__dict__.items() if
|
||||
# isinstance(v, InstrumentedAttribute) and k != "id"}
|
||||
fields = {k: v for k, v in self.object_type.__dict__.items() if k != "id"}
|
||||
fields = {key: dict(class_attr=getattr(self.object_type, key), instance_attr=getattr(self.instance, key))
|
||||
for key in dir(self.object_type) if isinstance(getattr(self.object_type, key), InstrumentedAttribute)
|
||||
and "id" not in key and key != manager}
|
||||
# NOTE: Move 'name' to the front
|
||||
try:
|
||||
fields = {'name': fields.pop('name'), **fields}
|
||||
except KeyError:
|
||||
pass
|
||||
logger.debug(pformat(fields, indent=4))
|
||||
height_counter = 0
|
||||
for key, field in fields.items():
|
||||
logger.debug(f"")
|
||||
try:
|
||||
widget = EditProperty(self, key=key, column_type=field.property.expression.type,
|
||||
value=getattr(self.instance, key))
|
||||
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:
|
||||
logger.error(f"Problem setting widget {key}: {e}")
|
||||
continue
|
||||
self.layout.addWidget(widget, self.layout.rowCount(), 0)
|
||||
if widget:
|
||||
self.layout.addWidget(widget, self.layout.rowCount(), 0)
|
||||
height_counter += 1
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
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)
|
||||
|
||||
def parse_form(self):
|
||||
results = {result[0]:result[1] for result in [item.parse_form() for item in self.findChildren(EditProperty)]}
|
||||
# logger.debug(results)
|
||||
def parse_form(self) -> Tuple[BaseModel, Report]:
|
||||
report = Report()
|
||||
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 = model(**results)
|
||||
try:
|
||||
extras = list(model.model_extra.keys())
|
||||
except AttributeError:
|
||||
extras = []
|
||||
fields = list(model.model_fields.keys()) + extras
|
||||
for field in fields:
|
||||
# logger.debug(result)
|
||||
self.instance.__setattr__(field, model.__getattribute__(field))
|
||||
return self.instance
|
||||
# NOTE: Hand-off to pydantic model for validation.
|
||||
# NOTE: Also, why am I not just using the toSQL method here. I could write one for contacts.
|
||||
model = model(**parsed)
|
||||
# output, result = model.toSQL()
|
||||
# report.add_result(result)
|
||||
# if len(report.results) < 1:
|
||||
# report.add_result(Result(msg="Added new regeant.", icon="Information", owner=__name__))
|
||||
return model, report
|
||||
|
||||
|
||||
class EditProperty(QWidget):
|
||||
@@ -64,7 +83,36 @@ class EditProperty(QWidget):
|
||||
self.layout = QGridLayout()
|
||||
self.layout.addWidget(self.label, 0, 0, 1, 1)
|
||||
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():
|
||||
if not value:
|
||||
value = ""
|
||||
@@ -76,22 +124,25 @@ class EditProperty(QWidget):
|
||||
value = date.today()
|
||||
self.widget.setDate(value)
|
||||
case _:
|
||||
logger.error(f"{column_type} not a supported type.")
|
||||
logger.error(f"{column_property} not a supported property.")
|
||||
self.widget = None
|
||||
return
|
||||
self.layout.addWidget(self.widget, 0, 1, 1, 3)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def parse_form(self):
|
||||
try:
|
||||
check = self.widget
|
||||
except AttributeError:
|
||||
return None, None
|
||||
match self.widget:
|
||||
case QLineEdit():
|
||||
value = self.widget.text()
|
||||
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 _:
|
||||
value = None
|
||||
return self.objectName(), value
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class ManagerWindow(QDialog):
|
||||
return self.instance
|
||||
|
||||
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():
|
||||
new_instance = dlg.parse_form()
|
||||
# logger.debug(new_instance.__dict__)
|
||||
@@ -182,7 +182,7 @@ class EditRelationship(QWidget):
|
||||
def add_new(self, instance: Any = None):
|
||||
if not instance:
|
||||
instance = self.entity()
|
||||
dlg = AddEdit(self, instance=instance)
|
||||
dlg = AddEdit(self, instance=instance, manager=self.parent().object_type.__name__.lower())
|
||||
if dlg.exec():
|
||||
new_instance = dlg.parse_form()
|
||||
# logger.debug(new_instance.__dict__)
|
||||
@@ -190,16 +190,15 @@ class EditRelationship(QWidget):
|
||||
if isinstance(addition, InstrumentedList):
|
||||
addition.append(new_instance)
|
||||
self.parent().instance.save()
|
||||
|
||||
self.parent().update_data()
|
||||
|
||||
def add_existing(self):
|
||||
dlg = SearchBox(self, object_type=self.entity, returnable=True, extras=[])
|
||||
if dlg.exec():
|
||||
rows = dlg.return_selected_rows()
|
||||
# print(f"Rows selected: {[row for row in rows]}")
|
||||
for row in rows:
|
||||
instance = self.entity.query(**row)
|
||||
# logger.debug(instance)
|
||||
addition = getattr(self.parent().instance, self.objectName())
|
||||
if isinstance(addition, InstrumentedList):
|
||||
addition.append(instance)
|
||||
|
||||
@@ -140,14 +140,22 @@ class SubmissionFormContainer(QWidget):
|
||||
self.layout().addWidget(self.form)
|
||||
return report
|
||||
|
||||
|
||||
def new_add_reagent(self):
|
||||
instance = Reagent()
|
||||
@report_result
|
||||
def new_add_reagent(self, instance: Reagent | None = None):
|
||||
report = Report()
|
||||
if not instance:
|
||||
instance = Reagent()
|
||||
dlg = AddEdit(parent=self, instance=instance)
|
||||
if dlg.exec():
|
||||
obj = dlg.parse_form()
|
||||
print(obj)
|
||||
|
||||
reagent, result = dlg.parse_form()
|
||||
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
|
||||
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):
|
||||
|
||||
update_reagent_fields = ['extraction_kit']
|
||||
|
||||
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):
|
||||
reagent.flip_check(self.disabler.checkbox.isChecked())
|
||||
|
||||
|
||||
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,
|
||||
disable: bool = False) -> "self.InfoItem":
|
||||
@@ -294,8 +300,10 @@ class SubmissionFormWidget(QWidget):
|
||||
if isinstance(reagent, self.ReagentFormWidget) or isinstance(reagent, QPushButton):
|
||||
reagent.setParent(None)
|
||||
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)
|
||||
for reagent in reagents:
|
||||
|
||||
add_widget = self.ReagentFormWidget(parent=self, reagent=reagent, extraction_kit=self.extraction_kit)
|
||||
self.layout.addWidget(add_widget)
|
||||
report.add_result(integrity_report)
|
||||
@@ -432,7 +440,7 @@ class SubmissionFormWidget(QWidget):
|
||||
for widget in self.findChildren(QWidget):
|
||||
match widget:
|
||||
case self.ReagentFormWidget():
|
||||
reagent, _ = widget.parse_form()
|
||||
reagent = widget.parse_form()
|
||||
if reagent is not None:
|
||||
reagents.append(reagent)
|
||||
case self.InfoItem():
|
||||
@@ -440,6 +448,7 @@ class SubmissionFormWidget(QWidget):
|
||||
if field is not None:
|
||||
info[field] = value
|
||||
self.pyd.reagents = reagents
|
||||
logger.debug(f"Reagents from form: {reagents}")
|
||||
for item in self.recover:
|
||||
if hasattr(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
|
||||
self.lot.currentTextChanged.connect(self.updated)
|
||||
|
||||
def flip_check(self, checked:bool):
|
||||
def flip_check(self, checked: bool):
|
||||
with QSignalBlocker(self.check) as b:
|
||||
self.check.setChecked(checked)
|
||||
self.lot.setEnabled(checked)
|
||||
@@ -675,6 +684,7 @@ class SubmissionFormWidget(QWidget):
|
||||
else:
|
||||
self.parent().disabler.checkbox.setChecked(True)
|
||||
|
||||
@report_result
|
||||
def parse_form(self) -> Tuple[PydReagent | None, Report]:
|
||||
"""
|
||||
Pulls form info into PydReagent
|
||||
@@ -686,18 +696,23 @@ class SubmissionFormWidget(QWidget):
|
||||
if not self.lot.isEnabled():
|
||||
return None, report
|
||||
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)
|
||||
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}?",
|
||||
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,
|
||||
name=self.reagent.name,
|
||||
kit=self.extraction_kit
|
||||
)
|
||||
# wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot,
|
||||
# reagent_role=self.reagent.role,
|
||||
# expiry=self.reagent.expiry,
|
||||
# name=self.reagent.name,
|
||||
# kit=self.extraction_kit
|
||||
# )
|
||||
wanted_reagent = self.parent().parent().new_add_reagent(instance=wanted_reagent)
|
||||
|
||||
return wanted_reagent, report
|
||||
else:
|
||||
# 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
|
||||
# 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)
|
||||
logger.debug(f"Reagent role: {rt}")
|
||||
if rt is None:
|
||||
rt = ReagentRole.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
||||
return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||
expiry=wanted_reagent.expiry, missing=False), report
|
||||
final = PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||
expiry=wanted_reagent.expiry.date(), missing=False)
|
||||
logger.debug(f"Final Reagent: {final}")
|
||||
return final, report
|
||||
|
||||
def updated(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user