ReagentLot add/edit updated.

This commit is contained in:
lwark
2025-09-10 12:39:02 -05:00
parent c9396d6c41
commit ba4912cab7
18 changed files with 1862 additions and 1734 deletions

View File

@@ -2,11 +2,12 @@
Contains all models for sqlalchemy
"""
from __future__ import annotations
import sys, logging, json
import sys, logging, json, inspect
from dateutil.parser import parse
from jinja2 import TemplateNotFound
from pandas import DataFrame
from pydantic import BaseModel
from sqlalchemy import Column, INTEGER, String, JSON
from sqlalchemy import Column, INTEGER, String, JSON, DATETIME
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute, ColumnProperty
from sqlalchemy.ext.declarative import declared_attr
@@ -36,10 +37,6 @@ class BaseClass(Base):
__table_args__ = {'extend_existing': True} #: NOTE Will only add new columns
singles = ['id']
# omni_removes = ["id", 'run', "omnigui_class_dict", "omnigui_instance_dict"]
# omni_sort = ["name"]
# omni_inheritable = []
searchables = []
_misc_info = Column(JSON)
@@ -155,6 +152,33 @@ class BaseClass(Base):
except AttributeError:
return []
@classmethod
def get_omni_sort(cls):
output = [item[0] for item in inspect.getmembers(cls, lambda a: not (inspect.isroutine(a)))
if isinstance(item[1], InstrumentedAttribute)] # and not isinstance(item[1].property, _RelationshipDeclared)]
output = [item for item in output if item not in ['_misc_info']]
return output
@classmethod
def get_searchables(cls):
output = []
for item in inspect.getmembers(cls, lambda a: not (inspect.isroutine(a))):
if item[0] in ["_misc_info"]:
continue
if not isinstance(item[1], InstrumentedAttribute):
continue
if not isinstance(item[1].property, ColumnProperty):
continue
# if isinstance(item[1], _RelationshipDeclared):
# if "association" in item[0]:
# continue
if len(item[1].foreign_keys) > 0:
continue
if item[1].type.__class__.__name__ not in ["String"]:
continue
output.append(item[0])
return output
@classmethod
def get_default_info(cls, *args) -> dict | list | str:
"""
@@ -296,7 +320,6 @@ class BaseClass(Base):
except AttributeError:
check = False
if check:
logger.debug("Got uselist")
try:
query = query.filter(attr.contains(v))
except ArgumentError:
@@ -358,16 +381,14 @@ class BaseClass(Base):
dict: Dictionary of object minus _sa_instance_state with id at the front.
"""
dicto = {key: dict(class_attr=getattr(self.__class__, key), instance_attr=getattr(self, key))
for key in dir(self.__class__) if
isinstance(getattr(self.__class__, key), InstrumentedAttribute) and key not in self.omni_removes
}
for key in self.get_omni_sort()}
for k, v in dicto.items():
try:
v['instance_attr'] = v['instance_attr'].name
except AttributeError:
continue
try:
dicto = list_sort_dict(input_dict=dicto, sort_list=self.__class__.omni_sort)
dicto = list_sort_dict(input_dict=dicto, sort_list=self.__class__.get_omni_sort())
except TypeError as e:
logger.error(f"Could not sort {self.__class__.__name__} by list due to :{e}")
try:
@@ -418,7 +439,7 @@ class BaseClass(Base):
try:
template = env.get_template(temp_name)
except TemplateNotFound as e:
logger.error(f"Couldn't find template {e}")
# logger.error(f"Couldn't find template {e}")
template = env.get_template("details.html")
return template
@@ -505,7 +526,6 @@ class BaseClass(Base):
existing = self.__getattribute__(key)
# NOTE: This is causing problems with removal of items from lists. Have to overhaul it.
if existing is not None:
logger.debug(f"{key} Existing: {existing}, incoming: {value}")
if isinstance(value, list):
value = value
else:
@@ -626,7 +646,7 @@ class BaseClass(Base):
from backend.validators import pydant
if not pyd_model_name:
pyd_model_name = f"Pyd{self.__class__.__name__}"
logger.debug(f"Looking for pydant model {pyd_model_name}")
logger.info(f"Looking for pydant model {pyd_model_name}")
try:
pyd = getattr(pydant, pyd_model_name)
except AttributeError:

View File

@@ -194,6 +194,7 @@ class Reagent(BaseClass, LogMixin):
Concrete reagent instance
"""
skip_on_edit = False
id = Column(INTEGER, primary_key=True) #: primary key
reagentrole = relationship("ReagentRole", back_populates="reagent",
secondary=reagentrole_reagent) #: joined parent ReagentRole
@@ -319,15 +320,7 @@ class Reagent(BaseClass, LogMixin):
except AttributeError as e:
logger.error(f"Could not set {key} due to {e}")
@check_authorization
def edit_from_search(self, obj, **kwargs):
from frontend.widgets.omni_add_edit import AddEdit
dlg = AddEdit(parent=None, instance=self)
if dlg.exec():
pyd = dlg.parse_form()
for field in pyd.model_fields:
self.set_attribute(field, pyd.__getattribute__(field))
self.save()
@classproperty
def add_edit_tooltips(self):
@@ -418,6 +411,15 @@ class ReagentLot(BaseClass):
pass
setattr(self, key, value)
@check_authorization
def edit_from_search(self, obj, **kwargs):
from frontend.widgets.omni_add_edit import AddEdit
dlg = AddEdit(parent=None, instance=self, disabled=['reagent'])
if dlg.exec():
pyd = dlg.parse_form()
for field in pyd.model_fields:
self.set_attribute(field, pyd.__getattribute__(field))
self.save()
class Discount(BaseClass):
"""
@@ -952,6 +954,7 @@ class Procedure(BaseClass):
output['sample'] = active_samples + inactive_samples
output['reagent'] = [reagent.details_dict() for reagent in output['procedurereagentlotassociation']]
output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']]
logger.debug(f"equipment: {pformat([item for item in output['equipment']])}")
output['repeat'] = self.repeat
output['run'] = self.run.name
output['excluded'] += self.get_default_info("details_ignore")
@@ -963,14 +966,6 @@ class Procedure(BaseClass):
def to_pydantic(self, **kwargs):
from backend.validators.pydant import PydReagent
output = super().to_pydantic()
try:
output.kittype = dict(value=output.kittype['name'], missing=False)
except KeyError:
try:
output.kittype = dict(value=output.kittype['value'], missing=False)
except KeyError as e:
logger.error(f"Output.kittype: {output.kittype}")
raise e
output.sample = [item.to_pydantic() for item in output.proceduresampleassociation]
reagents = []
for reagent in output.reagent:
@@ -985,6 +980,7 @@ class Procedure(BaseClass):
output.result = [item.to_pydantic() for item in self.results]
output.sample_results = flatten_list(
[[result.to_pydantic() for result in item.results] for item in self.proceduresampleassociation])
return output
def create_proceduresampleassociations(self, sample):
@@ -1607,7 +1603,7 @@ class Equipment(BaseClass, LogMixin):
from backend.validators.pydant import PydEquipment
creation_dict = self.details_dict()
processes = self.get_processes(equipmentrole=equipmentrole)
creation_dict['process'] = processes
creation_dict['processes'] = processes
creation_dict['equipmentrole'] = equipmentrole or creation_dict['equipmentrole']
return PydEquipment(**creation_dict)
@@ -2187,6 +2183,7 @@ class ProcedureEquipmentAssociation(BaseClass):
output.update(relevant)
output['misc_info'] = misc
output['equipment_role'] = self.equipmentrole
output['processes'] = [item for item in self.equipment.get_processes(equipmentrole=output['equipment_role'])]
try:
output['processversion'] = self.processversion.details_dict()
except AttributeError:

View File

@@ -116,7 +116,6 @@ class ClientSubmission(BaseClass, LogMixin):
if start_date is not None:
start_date = cls.rectify_query_date(start_date)
end_date = cls.rectify_query_date(end_date, eod=True)
logger.debug(f"Start date: {start_date}, end date: {end_date}")
query = query.filter(cls.submitted_date.between(start_date, end_date))
# NOTE: by rsl number (returns only a single value)
match submitter_plate_id:
@@ -303,7 +302,6 @@ class ClientSubmission(BaseClass, LogMixin):
return {item: self.__getattribute__(item.lower().replace(" ", "_")) for item in names}
def add_run(self, obj):
logger.debug("Add Run")
from frontend.widgets.sample_checker import SampleChecker
samples = [sample.to_pydantic() for sample in self.clientsubmissionsampleassociation]
checker = SampleChecker(parent=None, title="Create Run", samples=samples, clientsubmission=self)
@@ -455,13 +453,14 @@ class Run(BaseClass, LogMixin):
output = {k: v for k, v in dicto.items() if k in args}
else:
output = {k: v for k, v in dicto.items()}
logger.debug(f"Submission type for get default info: {submissiontype}")
# logger.debug(f"Submission type for get default info: {submissiontype}")
if isinstance(submissiontype, SubmissionType):
st = submissiontype
else:
st = cls.get_submission_type(submissiontype)
if st is None:
logger.error("No default info for Run.")
# logger.error("No default info for Run.")
pass
else:
output['submissiontype'] = st.name
for k, v in st.defaults.items():