Middle of the road.
This commit is contained in:
@@ -8,7 +8,7 @@ from pydantic import BaseModel
|
||||
from sqlalchemy import Column, INTEGER, String, JSON
|
||||
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.exc import ArgumentError
|
||||
from sqlalchemy.exc import ArgumentError, InvalidRequestError
|
||||
from typing import Any, List
|
||||
from pathlib import Path
|
||||
from tools import report_result, list_sort_dict
|
||||
@@ -48,7 +48,7 @@ class BaseClass(Base):
|
||||
__table_args__ = {'extend_existing': True} #: Will only add new columns
|
||||
|
||||
singles = ['id']
|
||||
omni_removes = ['submissions']
|
||||
omni_removes = ['submissions', "omnigui_class_dict", "omnigui_instance_dict"]
|
||||
omni_sort = ["name"]
|
||||
|
||||
@classproperty
|
||||
@@ -60,7 +60,7 @@ class BaseClass(Base):
|
||||
|
||||
@classproperty
|
||||
def aliases(cls):
|
||||
return [cls.__name__.lower()]
|
||||
return [cls.query_alias]
|
||||
|
||||
@classproperty
|
||||
def level(cls):
|
||||
@@ -198,7 +198,7 @@ class BaseClass(Base):
|
||||
try:
|
||||
records = [obj.to_sub_dict(**kwargs) for obj in objects]
|
||||
except AttributeError:
|
||||
records = [{k:v['instance_attr'] for k, v in obj.to_omnigui_dict(**kwargs).items()} for obj in objects]
|
||||
records = [{k:v['instance_attr'] for k, v in obj.omnigui_instance_dict.items()} for obj in objects]
|
||||
return DataFrame.from_records(records)
|
||||
|
||||
@classmethod
|
||||
@@ -233,6 +233,10 @@ class BaseClass(Base):
|
||||
logger.info(f"Using key: {k} with value: {v}")
|
||||
try:
|
||||
attr = getattr(model, k)
|
||||
# NOTE: account for attrs that use list.
|
||||
if attr.property.uselist:
|
||||
query = query.filter(attr.contains(v))
|
||||
else:
|
||||
query = query.filter(attr == v)
|
||||
except (ArgumentError, AttributeError) as e:
|
||||
logger.error(f"Attribute {k} unavailable due to:\n\t{e}\nSkipping.")
|
||||
@@ -265,14 +269,13 @@ class BaseClass(Base):
|
||||
return report
|
||||
|
||||
@property
|
||||
def omnigui_dict(self) -> dict:
|
||||
def omnigui_instance_dict(self) -> dict:
|
||||
"""
|
||||
For getting any object in an omni-thing friendly output.
|
||||
|
||||
Returns:
|
||||
dict: Dictionary of object minus _sa_instance_state with id at the front.
|
||||
"""
|
||||
# dicto = {k: v for k, v in self.__dict__.items() if k not in ["_sa_instance_state"]}
|
||||
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
|
||||
@@ -306,7 +309,7 @@ class BaseClass(Base):
|
||||
model = getattr(pydant, f"Pyd{cls.__name__}")
|
||||
except AttributeError:
|
||||
logger.warning(f"Couldn't get {cls.__name__} pydantic model.")
|
||||
return None
|
||||
return pydant.PydElastic
|
||||
return model
|
||||
|
||||
@classproperty
|
||||
@@ -324,6 +327,32 @@ class BaseClass(Base):
|
||||
query_kwargs = {relationship_instance.query_alias:relationship_instance}
|
||||
return cls.query(**query_kwargs)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
try:
|
||||
field_type = getattr(self.__class__, key)
|
||||
except AttributeError:
|
||||
return super().__setattr__(key, value)
|
||||
try:
|
||||
check = field_type.property.uselist
|
||||
except AttributeError:
|
||||
check = False
|
||||
if check:
|
||||
logger.debug(f"Setting with uselist")
|
||||
if self.__getattribute__(key) is not None:
|
||||
if isinstance(value, list):
|
||||
value = self.__getattribute__(key) + value
|
||||
else:
|
||||
value = self.__getattribute__(key) + [value]
|
||||
else:
|
||||
value = [value]
|
||||
else:
|
||||
if isinstance(value, list):
|
||||
if len(value) == 1:
|
||||
value = value[0]
|
||||
else:
|
||||
raise ValueError("Object is too long to parse a single value.")
|
||||
return super().__setattr__(key, value)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ class Control(BaseClass):
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
submission_type: str | None = None,
|
||||
submissiontype: str | None = None,
|
||||
subtype: str | None = None,
|
||||
start_date: date | str | int | None = None,
|
||||
end_date: date | str | int | None = None,
|
||||
@@ -169,13 +169,13 @@ class Control(BaseClass):
|
||||
"""
|
||||
from backend.db import SubmissionType
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match submission_type:
|
||||
match submissiontype:
|
||||
case str():
|
||||
from backend import BasicSubmission, SubmissionType
|
||||
query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == submission_type)
|
||||
query = query.join(BasicSubmission).join(SubmissionType).filter(SubmissionType.name == submissiontype)
|
||||
case SubmissionType():
|
||||
from backend import BasicSubmission
|
||||
query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name == submission_type.name)
|
||||
query = query.join(BasicSubmission).filter(BasicSubmission.submission_type_name == submissiontype.name)
|
||||
case _:
|
||||
pass
|
||||
# NOTE: by control type
|
||||
@@ -347,7 +347,7 @@ class PCRControl(Control):
|
||||
parent.mode_typer.clear()
|
||||
parent.mode_typer.setEnabled(False)
|
||||
report = Report()
|
||||
controls = cls.query(submission_type=chart_settings['sub_type'], start_date=chart_settings['start_date'],
|
||||
controls = cls.query(submissiontype=chart_settings['sub_type'], start_date=chart_settings['start_date'],
|
||||
end_date=chart_settings['end_date'])
|
||||
data = [control.to_sub_dict() for control in controls]
|
||||
df = DataFrame.from_records(data)
|
||||
|
||||
@@ -96,7 +96,8 @@ class KitType(BaseClass):
|
||||
Base of kits used in submission processing
|
||||
"""
|
||||
|
||||
query_alias = "kit_type"
|
||||
# query_alias = "kit_type"
|
||||
omni_sort = BaseClass.omni_sort + ["kit_submissiontype_associations", "kit_reagentrole_associations", "processes"]
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
name = Column(String(64), unique=True) #: name of kit
|
||||
@@ -134,7 +135,12 @@ class KitType(BaseClass):
|
||||
|
||||
@classproperty
|
||||
def aliases(cls):
|
||||
return super().aliases + [cls.query_alias, "kit_types"]
|
||||
return super().aliases + [cls.query_alias, "kit_types", "kit_type"]
|
||||
|
||||
@hybrid_property
|
||||
def submissiontype(self):
|
||||
"""Alias used_for field to allow query with SubmissionType query alias"""
|
||||
return self.used_for
|
||||
|
||||
def get_reagents(self,
|
||||
required: bool = False,
|
||||
@@ -227,7 +233,8 @@ class KitType(BaseClass):
|
||||
name: str = None,
|
||||
used_for: str | SubmissionType | None = None,
|
||||
id: int | None = None,
|
||||
limit: int = 0
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> KitType | List[KitType]:
|
||||
"""
|
||||
Lookup a list of or single KitType.
|
||||
@@ -264,7 +271,7 @@ class KitType(BaseClass):
|
||||
limit = 1
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
@check_authorization
|
||||
def save(self):
|
||||
@@ -407,10 +414,11 @@ class ReagentRole(BaseClass):
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
name: str | None = None,
|
||||
kit_type: KitType | str | None = None,
|
||||
kittype: KitType | str | None = None,
|
||||
reagent: Reagent | str | None = None,
|
||||
id: int | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> ReagentRole | List[ReagentRole]:
|
||||
"""
|
||||
Lookup reagent types in the database.
|
||||
@@ -429,14 +437,14 @@ class ReagentRole(BaseClass):
|
||||
ReagentRole|List[ReagentRole]: ReagentRole or list of ReagentRoles matching filter.
|
||||
"""
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
if (kit_type is not None and reagent is None) or (reagent is not None and kit_type is None):
|
||||
if (kittype is not None and reagent is None) or (reagent is not None and kittype is None):
|
||||
raise ValueError("Cannot filter without both reagent and kit type.")
|
||||
elif kit_type is None and reagent is None:
|
||||
elif kittype is None and reagent is None:
|
||||
pass
|
||||
else:
|
||||
match kit_type:
|
||||
match kittype:
|
||||
case str():
|
||||
kit_type = KitType.query(name=kit_type)
|
||||
kittype = KitType.query(name=kittype)
|
||||
case _:
|
||||
pass
|
||||
match reagent:
|
||||
@@ -446,7 +454,7 @@ class ReagentRole(BaseClass):
|
||||
pass
|
||||
assert reagent.role
|
||||
# NOTE: Get all roles common to the reagent and the kit.
|
||||
result = set(kit_type.reagent_roles).intersection(reagent.role)
|
||||
result = set(kittype.reagent_roles).intersection(reagent.role)
|
||||
return next((item for item in result), None)
|
||||
match name:
|
||||
case str():
|
||||
@@ -491,8 +499,6 @@ class Reagent(BaseClass, LogMixin):
|
||||
Concrete reagent instance
|
||||
"""
|
||||
|
||||
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: primary key
|
||||
role = relationship("ReagentRole", back_populates="instances",
|
||||
secondary=reagentroles_reagents) #: joined parent reagent type
|
||||
@@ -523,6 +529,11 @@ class Reagent(BaseClass, LogMixin):
|
||||
def searchables(cls):
|
||||
return [dict(label="Lot", field="lot")]
|
||||
|
||||
@hybrid_property
|
||||
def reagentrole(self):
|
||||
"""Alias role field to allow query with ReagentRole query alias"""
|
||||
return self.role
|
||||
|
||||
def to_sub_dict(self, extraction_kit: KitType = None, full_data: bool = False, **kwargs) -> dict:
|
||||
"""
|
||||
dictionary containing values necessary for gui
|
||||
@@ -583,9 +594,9 @@ class Reagent(BaseClass, LogMixin):
|
||||
Report: Result of operation
|
||||
"""
|
||||
report = Report()
|
||||
rt = ReagentRole.query(kit_type=kit, reagent=self, limit=1)
|
||||
rt = ReagentRole.query(kittype=kit, reagent=self, limit=1)
|
||||
if rt is not None:
|
||||
assoc = KitTypeReagentRoleAssociation.query(kit_type=kit, reagent_role=rt)
|
||||
assoc = KitTypeReagentRoleAssociation.query(kittype=kit, reagentrole=rt)
|
||||
if assoc is not None:
|
||||
if assoc.last_used != self.lot:
|
||||
assoc.last_used = self.lot
|
||||
@@ -621,7 +632,8 @@ class Reagent(BaseClass, LogMixin):
|
||||
role: str | ReagentRole | None = None,
|
||||
lot: str | None = None,
|
||||
name: str | None = None,
|
||||
limit: int = 0
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> Reagent | List[Reagent]:
|
||||
"""
|
||||
Lookup a list of reagents from the database.
|
||||
@@ -643,6 +655,8 @@ class Reagent(BaseClass, LogMixin):
|
||||
limit = 1
|
||||
case _:
|
||||
pass
|
||||
# if not role and "reagentrole" in kwargs.keys():
|
||||
# role = kwargs['reagentrole']
|
||||
match role:
|
||||
case str():
|
||||
query = query.join(cls.role).filter(ReagentRole.name == role)
|
||||
@@ -663,7 +677,7 @@ class Reagent(BaseClass, LogMixin):
|
||||
limit = 1
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
return cls.execute_query(query=query, limit=limit, **kwargs)
|
||||
|
||||
def set_attribute(self, key, value):
|
||||
match key:
|
||||
@@ -741,7 +755,7 @@ class Discount(BaseClass):
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
organization: Organization | str | int | None = None,
|
||||
kit_type: KitType | str | int | None = None,
|
||||
kittype: KitType | str | int | None = None,
|
||||
) -> Discount | List[Discount]:
|
||||
"""
|
||||
Lookup discount objects (union of kit and organization)
|
||||
@@ -763,13 +777,13 @@ class Discount(BaseClass):
|
||||
query = query.join(Organization).filter(Organization.id == organization)
|
||||
case _:
|
||||
pass
|
||||
match kit_type:
|
||||
match kittype:
|
||||
case KitType():
|
||||
query = query.filter(cls.kit == kit_type)
|
||||
query = query.filter(cls.kit == kittype)
|
||||
case str():
|
||||
query = query.join(KitType).filter(KitType.name == kit_type)
|
||||
query = query.join(KitType).filter(KitType.name == kittype)
|
||||
case int():
|
||||
query = query.join(KitType).filter(KitType.id == kit_type)
|
||||
query = query.join(KitType).filter(KitType.id == kittype)
|
||||
case _:
|
||||
pass
|
||||
return cls.execute_query(query=query)
|
||||
@@ -833,6 +847,14 @@ class SubmissionType(BaseClass):
|
||||
"""
|
||||
return f"<SubmissionType({self.name})>"
|
||||
|
||||
@hybrid_property
|
||||
def kittype(self):
|
||||
return self.kit_types
|
||||
|
||||
@hybrid_property
|
||||
def process(self):
|
||||
return self.processes
|
||||
|
||||
@classproperty
|
||||
def aliases(cls):
|
||||
return super().aliases + ["submission_types", "submission_type"]
|
||||
@@ -990,7 +1012,8 @@ class SubmissionType(BaseClass):
|
||||
def query(cls,
|
||||
name: str | None = None,
|
||||
key: str | None = None,
|
||||
limit: int = 0
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> SubmissionType | List[SubmissionType]:
|
||||
"""
|
||||
Lookup submission type in the database by a number of parameters
|
||||
@@ -1124,7 +1147,23 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
||||
Returns:
|
||||
str: Representation of this object
|
||||
"""
|
||||
return f"<SubmissionTypeKitTypeAssociation({self.submission_type.name}&{self.kit_type.name})>"
|
||||
try:
|
||||
submission_type_name = self.submission_type.name
|
||||
except AttributeError:
|
||||
submission_type_name = "None"
|
||||
try:
|
||||
kit_type_name = self.kit_type.name
|
||||
except AttributeError:
|
||||
kit_type_name = "None"
|
||||
return f"<SubmissionTypeKitTypeAssociation({submission_type_name}&{kit_type_name})>"
|
||||
|
||||
@hybrid_property
|
||||
def kittype(self):
|
||||
return self.kit_type
|
||||
|
||||
@hybrid_property
|
||||
def submissiontype(self):
|
||||
return self.submission_type
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -1133,8 +1172,8 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
submission_type: SubmissionType | str | int | None = None,
|
||||
kit_type: KitType | str | int | None = None,
|
||||
submissiontype: SubmissionType | str | int | None = None,
|
||||
kittype: KitType | str | int | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> SubmissionTypeKitTypeAssociation | List[SubmissionTypeKitTypeAssociation]:
|
||||
@@ -1150,21 +1189,21 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
||||
SubmissionTypeKitTypeAssociation|List[SubmissionTypeKitTypeAssociation]: SubmissionTypeKitTypeAssociation(s) of interest
|
||||
"""
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match submission_type:
|
||||
match submissiontype:
|
||||
case SubmissionType():
|
||||
query = query.filter(cls.submission_type == submission_type)
|
||||
query = query.filter(cls.submission_type == submissiontype)
|
||||
case str():
|
||||
query = query.join(SubmissionType).filter(SubmissionType.name == submission_type)
|
||||
query = query.join(SubmissionType).filter(SubmissionType.name == submissiontype)
|
||||
case int():
|
||||
query = query.join(SubmissionType).filter(SubmissionType.id == submission_type)
|
||||
match kit_type:
|
||||
query = query.join(SubmissionType).filter(SubmissionType.id == submissiontype)
|
||||
match kittype:
|
||||
case KitType():
|
||||
query = query.filter(cls.kit_type == kit_type)
|
||||
query = query.filter(cls.kit_type == kittype)
|
||||
case str():
|
||||
query = query.join(KitType).filter(KitType.name == kit_type)
|
||||
query = query.join(KitType).filter(KitType.name == kittype)
|
||||
case int():
|
||||
query = query.join(KitType).filter(KitType.id == kit_type)
|
||||
if kit_type is not None and submission_type is not None:
|
||||
query = query.join(KitType).filter(KitType.id == kittype)
|
||||
if kittype is not None and submissiontype is not None:
|
||||
limit = 1
|
||||
# limit = query.count()
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
@@ -1221,7 +1260,10 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
try:
|
||||
return f"{self.kit_type.name} -> {self.reagent_role.name}"
|
||||
except AttributeError:
|
||||
return "Blank KitTypeReagentRole"
|
||||
|
||||
@validates('required')
|
||||
def validate_required(self, key, value):
|
||||
@@ -1238,6 +1280,8 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
||||
Returns:
|
||||
_type_: value
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
value = int(value)
|
||||
if not 0 <= value < 2:
|
||||
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
||||
return value
|
||||
@@ -1264,8 +1308,8 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
kit_type: KitType | str | None = None,
|
||||
reagent_role: ReagentRole | str | None = None,
|
||||
kittype: KitType | str | None = None,
|
||||
reagentrole: ReagentRole | str | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs
|
||||
) -> KitTypeReagentRoleAssociation | List[KitTypeReagentRoleAssociation]:
|
||||
@@ -1281,21 +1325,21 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
||||
models.KitTypeReagentTypeAssociation|List[models.KitTypeReagentTypeAssociation]: Junction of interest.
|
||||
"""
|
||||
query: Query = cls.__database_session__.query(cls)
|
||||
match kit_type:
|
||||
match kittype:
|
||||
case KitType():
|
||||
query = query.filter(cls.kit_type == kit_type)
|
||||
query = query.filter(cls.kit_type == kittype)
|
||||
case str():
|
||||
query = query.join(KitType).filter(KitType.name == kit_type)
|
||||
query = query.join(KitType).filter(KitType.name == kittype)
|
||||
case _:
|
||||
pass
|
||||
match reagent_role:
|
||||
match reagentrole:
|
||||
case ReagentRole():
|
||||
query = query.filter(cls.reagent_role == reagent_role)
|
||||
query = query.filter(cls.reagent_role == reagentrole)
|
||||
case str():
|
||||
query = query.join(ReagentRole).filter(ReagentRole.name == reagent_role)
|
||||
query = query.join(ReagentRole).filter(ReagentRole.name == reagentrole)
|
||||
case _:
|
||||
pass
|
||||
if kit_type is not None and reagent_role is not None:
|
||||
if kittype is not None and reagentrole is not None:
|
||||
limit = 1
|
||||
return cls.execute_query(query=query, limit=limit)
|
||||
|
||||
@@ -1329,8 +1373,8 @@ class KitTypeReagentRoleAssociation(BaseClass):
|
||||
yield rel_reagent
|
||||
|
||||
@property
|
||||
def omnigui_dict(self) -> dict:
|
||||
dicto = super().omnigui_dict
|
||||
def omnigui_instance_dict(self) -> dict:
|
||||
dicto = super().omnigui_instance_dict
|
||||
dicto['required']['instance_attr'] = bool(dicto['required']['instance_attr'])
|
||||
return dicto
|
||||
|
||||
@@ -1801,6 +1845,14 @@ class SubmissionTypeEquipmentRoleAssociation(BaseClass):
|
||||
equipment_role = relationship(EquipmentRole,
|
||||
back_populates="equipmentrole_submissiontype_associations") #: associated equipment
|
||||
|
||||
@hybrid_property
|
||||
def submissiontype(self):
|
||||
return self.submission_type
|
||||
|
||||
@hybrid_property
|
||||
def equipmentrole(self):
|
||||
return self.equipment_role
|
||||
|
||||
@validates('static')
|
||||
def validate_static(self, key, value):
|
||||
"""
|
||||
@@ -1847,6 +1899,8 @@ class Process(BaseClass):
|
||||
A Process is a method used by a piece of equipment.
|
||||
"""
|
||||
|
||||
level = 2
|
||||
|
||||
id = Column(INTEGER, primary_key=True) #: Process id, primary key
|
||||
name = Column(String(64), unique=True) #: Process name
|
||||
submission_types = relationship("SubmissionType", back_populates='processes',
|
||||
@@ -1869,14 +1923,24 @@ class Process(BaseClass):
|
||||
"""
|
||||
return f"<Process({self.name})>"
|
||||
|
||||
def set_attribute(self, key, value):
|
||||
match key:
|
||||
case "name":
|
||||
self.name = value
|
||||
case _:
|
||||
field = getattr(self, key)
|
||||
if value not in field:
|
||||
field.append(value)
|
||||
|
||||
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
name: str | None = None,
|
||||
id: int | None = None,
|
||||
submission_type: str | SubmissionType | None = None,
|
||||
extraction_kit: str | KitType | None = None,
|
||||
equipment_role: str | KitType | None = None,
|
||||
submissiontype: str | SubmissionType | None = None,
|
||||
kittype: str | KitType | None = None,
|
||||
equipmentrole: str | KitType | None = None,
|
||||
limit: int = 0,
|
||||
**kwargs) -> Process | List[Process]:
|
||||
"""
|
||||
@@ -1891,28 +1955,28 @@ class Process(BaseClass):
|
||||
Process|List[Process]: Process(es) matching criteria
|
||||
"""
|
||||
query = cls.__database_session__.query(cls)
|
||||
match submission_type:
|
||||
match submissiontype:
|
||||
case str():
|
||||
submission_type = SubmissionType.query(name=submission_type)
|
||||
query = query.filter(cls.submission_types.contains(submission_type))
|
||||
submissiontype = SubmissionType.query(name=submissiontype)
|
||||
query = query.filter(cls.submission_types.contains(submissiontype))
|
||||
case SubmissionType():
|
||||
query = query.filter(cls.submission_types.contains(submission_type))
|
||||
query = query.filter(cls.submission_types.contains(submissiontype))
|
||||
case _:
|
||||
pass
|
||||
match extraction_kit:
|
||||
match kittype:
|
||||
case str():
|
||||
extraction_kit = KitType.query(name=extraction_kit)
|
||||
query = query.filter(cls.kit_types.contains(extraction_kit))
|
||||
kittype = KitType.query(name=kittype)
|
||||
query = query.filter(cls.kit_types.contains(kittype))
|
||||
case KitType():
|
||||
query = query.filter(cls.kit_types.contains(extraction_kit))
|
||||
query = query.filter(cls.kit_types.contains(kittype))
|
||||
case _:
|
||||
pass
|
||||
match equipment_role:
|
||||
match equipmentrole:
|
||||
case str():
|
||||
equipment_role = EquipmentRole.query(name=equipment_role)
|
||||
query = query.filter(cls.equipment_roles.contains(equipment_role))
|
||||
equipmentrole = EquipmentRole.query(name=equipmentrole)
|
||||
query = query.filter(cls.equipment_roles.contains(equipmentrole))
|
||||
case EquipmentRole():
|
||||
query = query.filter(cls.equipment_roles.contains(equipment_role))
|
||||
query = query.filter(cls.equipment_roles.contains(equipmentrole))
|
||||
case _:
|
||||
pass
|
||||
match name:
|
||||
@@ -1983,6 +2047,10 @@ class Tips(BaseClass, LogMixin):
|
||||
|
||||
submissions = association_proxy("tips_submission_associations", 'submission')
|
||||
|
||||
@hybrid_property
|
||||
def tiprole(self):
|
||||
return self.role
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Tips({self.name})>"
|
||||
|
||||
@@ -2033,6 +2101,14 @@ class SubmissionTypeTipRoleAssociation(BaseClass):
|
||||
tip_role = relationship(TipRole,
|
||||
back_populates="tiprole_submissiontype_associations") #: associated equipment
|
||||
|
||||
@hybrid_property
|
||||
def submissiontype(self):
|
||||
return self.submission_type
|
||||
|
||||
@hybrid_property
|
||||
def tiprole(self):
|
||||
return self.tip_role
|
||||
|
||||
@check_authorization
|
||||
def save(self):
|
||||
super().save()
|
||||
|
||||
@@ -6,6 +6,7 @@ import json, yaml, logging
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
from sqlalchemy import Column, String, INTEGER, ForeignKey, Table
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import relationship, Query
|
||||
from . import Base, BaseClass
|
||||
from tools import check_authorization, setup_lookup, yaml_regex_creator
|
||||
@@ -38,6 +39,10 @@ class Organization(BaseClass):
|
||||
contacts = relationship("Contact", back_populates="organization",
|
||||
secondary=orgs_contacts) #: contacts involved with this org
|
||||
|
||||
@hybrid_property
|
||||
def contact(self):
|
||||
return self.contacts
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Organization({self.name})>"
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ from zipfile import ZipFile, BadZipfile
|
||||
from tempfile import TemporaryDirectory, TemporaryFile
|
||||
from operator import itemgetter
|
||||
from pprint import pformat
|
||||
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
from . import BaseClass, Reagent, SubmissionType, KitType, Organization, Contact, LogMixin, SubmissionReagentAssociation
|
||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func
|
||||
from sqlalchemy.orm import relationship, validates, Query
|
||||
@@ -126,6 +129,14 @@ class BasicSubmission(BaseClass, LogMixin):
|
||||
def __repr__(self) -> str:
|
||||
return f"<Submission({self.rsl_plate_num})>"
|
||||
|
||||
@hybrid_property
|
||||
def kittype(self):
|
||||
return self.extraction_kit
|
||||
|
||||
@hybrid_property
|
||||
def organization(self):
|
||||
return self.submitting_lab
|
||||
|
||||
@classproperty
|
||||
def jsons(cls) -> List[str]:
|
||||
"""
|
||||
@@ -488,7 +499,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
||||
"""
|
||||
# NOTE: use lookup function to create list of dicts
|
||||
subs = [item.to_dict() for item in
|
||||
cls.query(submission_type=submission_type, limit=limit, chronologic=chronologic, page=page,
|
||||
cls.query(submissiontype=submission_type, limit=limit, chronologic=chronologic, page=page,
|
||||
page_size=page_size)]
|
||||
df = pd.DataFrame.from_records(subs)
|
||||
# NOTE: Exclude sub information
|
||||
@@ -1056,7 +1067,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
||||
@classmethod
|
||||
@setup_lookup
|
||||
def query(cls,
|
||||
submission_type: str | SubmissionType | None = None,
|
||||
submissiontype: str | SubmissionType | None = None,
|
||||
submission_type_name: str | None = None,
|
||||
id: int | str | None = None,
|
||||
rsl_plate_num: str | None = None,
|
||||
@@ -1087,8 +1098,8 @@ class BasicSubmission(BaseClass, LogMixin):
|
||||
"""
|
||||
from ... import SubmissionReagentAssociation
|
||||
# NOTE: if you go back to using 'model' change the appropriate cls to model in the query filters
|
||||
if submission_type is not None:
|
||||
model = cls.find_polymorphic_subclass(polymorphic_identity=submission_type)
|
||||
if submissiontype is not None:
|
||||
model = cls.find_polymorphic_subclass(polymorphic_identity=submissiontype)
|
||||
elif len(kwargs) > 0:
|
||||
# NOTE: find the subclass containing the relevant attributes
|
||||
model = cls.find_polymorphic_subclass(attrs=kwargs)
|
||||
@@ -1196,7 +1207,7 @@ class BasicSubmission(BaseClass, LogMixin):
|
||||
if kwargs == {}:
|
||||
raise ValueError("Need to narrow down query or the first available instance will be returned.")
|
||||
sanitized_kwargs = {k: v for k, v in kwargs.items() if k not in disallowed}
|
||||
instance = cls.query(submission_type=submission_type, limit=1, **sanitized_kwargs)
|
||||
instance = cls.query(submissiontype=submission_type, limit=1, **sanitized_kwargs)
|
||||
if instance is None:
|
||||
used_class = cls.find_polymorphic_subclass(attrs=kwargs, polymorphic_identity=submission_type)
|
||||
instance = used_class(**sanitized_kwargs)
|
||||
|
||||
@@ -157,7 +157,7 @@ class RSLNamer(object):
|
||||
if "rsl_plate_num" in data.keys():
|
||||
plate_number = data['rsl_plate_num'].split("-")[-1][0]
|
||||
else:
|
||||
previous = BasicSubmission.query(start_date=today, end_date=today, submission_type=data['submission_type'])
|
||||
previous = BasicSubmission.query(start_date=today, end_date=today, submissiontype=data['submission_type'])
|
||||
plate_number = len(previous) + 1
|
||||
return f"RSL-{data['abbreviation']}-{today.year}{str(today.month).zfill(2)}{str(today.day).zfill(2)}-{plate_number}"
|
||||
|
||||
@@ -191,5 +191,5 @@ class RSLNamer(object):
|
||||
return ""
|
||||
|
||||
|
||||
from .pydant import PydSubmission, PydKit, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
|
||||
PydEquipment, PydEquipmentRole, PydTips, PydPCRControl, PydIridaControl
|
||||
from .pydant import PydSubmission, PydKitType, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
|
||||
PydEquipment, PydEquipmentRole, PydTips, PydPCRControl, PydIridaControl, PydProcess, PydElastic
|
||||
|
||||
@@ -14,6 +14,8 @@ from pathlib import Path
|
||||
from tools import check_not_nan, convert_nans_to_nones, Report, Result, timezone
|
||||
from backend.db.models import *
|
||||
from sqlalchemy.exc import StatementError, IntegrityError
|
||||
from sqlalchemy.orm.properties import ColumnProperty
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -203,6 +205,7 @@ class PydSample(BaseModel, extra='allow'):
|
||||
fields = list(self.model_fields.keys()) + list(self.model_extra.keys())
|
||||
return {k: getattr(self, k) for k in fields}
|
||||
|
||||
@report_result
|
||||
def to_sql(self, submission: BasicSubmission | str = None) -> Tuple[
|
||||
BasicSample, List[SubmissionSampleAssociation], Result | None]:
|
||||
"""
|
||||
@@ -270,6 +273,7 @@ class PydTips(BaseModel):
|
||||
value = value.name
|
||||
return value
|
||||
|
||||
@report_result
|
||||
def to_sql(self, submission: BasicSubmission) -> SubmissionTipsAssociation:
|
||||
"""
|
||||
Convert this object to the SQL version for database storage.
|
||||
@@ -280,12 +284,13 @@ class PydTips(BaseModel):
|
||||
Returns:
|
||||
SubmissionTipsAssociation: Association between queried tips and submission
|
||||
"""
|
||||
report = Report()
|
||||
tips = Tips.query(name=self.name, limit=1)
|
||||
# logger.debug(f"Tips query has yielded: {tips}")
|
||||
assoc = SubmissionTipsAssociation.query_or_create(tips=tips, submission=submission, role=self.role, limit=1)
|
||||
# if assoc is None:
|
||||
# assoc = SubmissionTipsAssociation(submission=submission, tips=tips, role_name=self.role)
|
||||
return assoc
|
||||
return assoc, report
|
||||
|
||||
|
||||
class PydEquipment(BaseModel, extra='ignore'):
|
||||
@@ -317,6 +322,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
||||
pass
|
||||
return value
|
||||
|
||||
@report_result
|
||||
def to_sql(self, submission: BasicSubmission | str = None, extraction_kit: KitType | str = None) -> Tuple[Equipment, SubmissionEquipmentAssociation]:
|
||||
"""
|
||||
Creates Equipment and SubmssionEquipmentAssociations for this PydEquipment
|
||||
@@ -327,6 +333,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
||||
Returns:
|
||||
Tuple[Equipment, SubmissionEquipmentAssociation]: SQL objects
|
||||
"""
|
||||
report = Report()
|
||||
if isinstance(submission, str):
|
||||
submission = BasicSubmission.query(rsl_plate_num=submission)
|
||||
if isinstance(extraction_kit, str):
|
||||
@@ -349,7 +356,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
||||
# NOTE: It looks like the way fetching the processes is done in the SQL model, this shouldn't be a problem, but I'll include a failsafe.
|
||||
# NOTE: I need to find a way to filter this by the kit involved.
|
||||
if len(self.processes) > 1:
|
||||
process = Process.query(submission_type=submission.get_submission_type(), extraction_kit=extraction_kit, equipment_role=self.role)
|
||||
process = Process.query(submissiontype=submission.get_submission_type(), kittype=extraction_kit, equipmentrole=self.role)
|
||||
else:
|
||||
process = Process.query(name=self.processes[0])
|
||||
if process is None:
|
||||
@@ -362,7 +369,7 @@ class PydEquipment(BaseModel, extra='ignore'):
|
||||
else:
|
||||
logger.warning(f"No submission found")
|
||||
assoc = None
|
||||
return equipment, assoc
|
||||
return equipment, assoc, report
|
||||
|
||||
def improved_dict(self) -> dict:
|
||||
"""
|
||||
@@ -762,6 +769,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
||||
missing_reagents = [reagent for reagent in self.reagents if reagent.missing]
|
||||
return missing_info, missing_reagents
|
||||
|
||||
@report_result
|
||||
def to_sql(self) -> Tuple[BasicSubmission, Report]:
|
||||
"""
|
||||
Converts this instance into a backend.db.models.submissions.BasicSubmission instance
|
||||
@@ -867,7 +875,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
||||
# NOTE: Apply any discounts that are applicable for client and kit.
|
||||
try:
|
||||
discounts = [item.amount for item in
|
||||
Discount.query(kit_type=instance.extraction_kit, organization=instance.submitting_lab)]
|
||||
Discount.query(kittype=instance.extraction_kit, organization=instance.submitting_lab)]
|
||||
if len(discounts) > 0:
|
||||
instance.run_cost = instance.run_cost - sum(discounts)
|
||||
except Exception as e:
|
||||
@@ -1047,7 +1055,7 @@ class PydOrganization(BaseModel):
|
||||
return None
|
||||
return value
|
||||
|
||||
|
||||
@report_result
|
||||
def to_sql(self) -> Organization:
|
||||
"""
|
||||
Converts this instance into a backend.db.models.organization.Organization instance.
|
||||
@@ -1055,6 +1063,7 @@ class PydOrganization(BaseModel):
|
||||
Returns:
|
||||
Organization: Organization instance
|
||||
"""
|
||||
report = Report()
|
||||
instance = Organization()
|
||||
for field in self.model_fields:
|
||||
match field:
|
||||
@@ -1067,7 +1076,7 @@ class PydOrganization(BaseModel):
|
||||
logger.debug(f"Setting {field} to {value}")
|
||||
if value:
|
||||
setattr(instance, field, value)
|
||||
return instance
|
||||
return instance, report
|
||||
|
||||
|
||||
class PydReagentRole(BaseModel):
|
||||
@@ -1083,6 +1092,7 @@ class PydReagentRole(BaseModel):
|
||||
return timedelta(days=value)
|
||||
return value
|
||||
|
||||
@report_result
|
||||
def to_sql(self, kit: KitType) -> ReagentRole:
|
||||
"""
|
||||
Converts this instance into a backend.db.models.ReagentType instance
|
||||
@@ -1093,23 +1103,25 @@ class PydReagentRole(BaseModel):
|
||||
Returns:
|
||||
ReagentRole: ReagentType instance
|
||||
"""
|
||||
report = Report()
|
||||
instance: ReagentRole = ReagentRole.query(name=self.name)
|
||||
if instance is None:
|
||||
instance = ReagentRole(name=self.name, eol_ext=self.eol_ext)
|
||||
try:
|
||||
assoc = KitTypeReagentRoleAssociation.query(reagent_role=instance, kit_type=kit)
|
||||
assoc = KitTypeReagentRoleAssociation.query(reagentrole=instance, kittype=kit)
|
||||
except StatementError:
|
||||
assoc = None
|
||||
if assoc is None:
|
||||
assoc = KitTypeReagentRoleAssociation(kit_type=kit, reagent_role=instance, uses=self.uses,
|
||||
required=self.required)
|
||||
return instance
|
||||
return instance, report
|
||||
|
||||
|
||||
class PydKit(BaseModel):
|
||||
class PydKitType(BaseModel):
|
||||
name: str
|
||||
reagent_roles: List[PydReagentRole] = []
|
||||
|
||||
@report_result
|
||||
def to_sql(self) -> Tuple[KitType, Report]:
|
||||
"""
|
||||
Converts this instance into a backend.db.models.kits.KitType instance
|
||||
@@ -1163,7 +1175,9 @@ class PydPCRControl(BaseModel):
|
||||
submission_id: int
|
||||
controltype_name: str
|
||||
|
||||
@report_result
|
||||
def to_sql(self):
|
||||
report = Report
|
||||
instance = PCRControl.query(name=self.name)
|
||||
if not instance:
|
||||
instance = PCRControl()
|
||||
@@ -1171,7 +1185,7 @@ class PydPCRControl(BaseModel):
|
||||
field_value = self.__getattribute__(key)
|
||||
if instance.__getattribute__(key) != field_value:
|
||||
instance.__setattr__(key, field_value)
|
||||
return instance
|
||||
return instance, report
|
||||
|
||||
|
||||
class PydIridaControl(BaseModel, extra='ignore'):
|
||||
@@ -1196,7 +1210,9 @@ class PydIridaControl(BaseModel, extra='ignore'):
|
||||
value = ""
|
||||
return value
|
||||
|
||||
@report_result
|
||||
def to_sql(self):
|
||||
report = Report()
|
||||
instance = IridaControl.query(name=self.name)
|
||||
if not instance:
|
||||
instance = IridaControl()
|
||||
@@ -1204,7 +1220,7 @@ class PydIridaControl(BaseModel, extra='ignore'):
|
||||
field_value = self.__getattribute__(key)
|
||||
if instance.__getattribute__(key) != field_value:
|
||||
instance.__setattr__(key, field_value)
|
||||
return instance
|
||||
return instance, report
|
||||
|
||||
|
||||
class PydProcess(BaseModel, extra="allow"):
|
||||
@@ -1222,16 +1238,62 @@ class PydProcess(BaseModel, extra="allow"):
|
||||
return [value]
|
||||
return value
|
||||
|
||||
@report_result
|
||||
def to_sql(self):
|
||||
report = Report()
|
||||
instance = Process.query(name=self.name)
|
||||
if not instance:
|
||||
instance = Process()
|
||||
dicto = instance.omnigui_dict
|
||||
for key in self.model_fields:
|
||||
# field_value = self.__getattribute__(key)
|
||||
# if instance.__getattribute__(key) != field_value:
|
||||
# instance.__setattr__(key, field_value)
|
||||
test = dicto[key]
|
||||
print(f"Attribute: {test['class_attr'].property}")
|
||||
# dicto = instance.omnigui_instance_dict
|
||||
fields = [item for item in self.model_fields]
|
||||
for field in fields:
|
||||
logger.debug(f"Field: {field}")
|
||||
try:
|
||||
field_type = getattr(instance.__class__, field).property
|
||||
except AttributeError:
|
||||
logger.error(f"No attribute: {field} in {instance.__class__}")
|
||||
continue
|
||||
match field_type:
|
||||
case _RelationshipDeclared():
|
||||
logger.debug(f"{field} is a relationship with {field_type.entity.class_}")
|
||||
query_str = getattr(self, field)
|
||||
if isinstance(query_str, list):
|
||||
query_str = query_str[0]
|
||||
if query_str in ["", " ", None]:
|
||||
continue
|
||||
logger.debug(f"Querying {field_type.entity.class_} with name {query_str}")
|
||||
field_value = field_type.entity.class_.query(name=query_str)
|
||||
logger.debug(f"{field} query result: {field_value}")
|
||||
case ColumnProperty():
|
||||
logger.debug(f"{field} is a property.")
|
||||
field_value = getattr(self, field)
|
||||
instance.set_attribute(key=field, value=field_value)
|
||||
return instance, report
|
||||
|
||||
|
||||
class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True):
|
||||
"""Allows for creation of arbitrary pydantic models"""
|
||||
instance: BaseClass
|
||||
|
||||
@report_result
|
||||
def to_sql(self):
|
||||
print(self.instance)
|
||||
fields = [item for item in self.model_extra]
|
||||
for field in fields:
|
||||
try:
|
||||
field_type = getattr(self.instance.__class__, field).property
|
||||
except AttributeError:
|
||||
logger.error(f"No attribute: {field} in {self.instance.__class__}")
|
||||
continue
|
||||
match field_type:
|
||||
case _RelationshipDeclared():
|
||||
logger.debug(f"{field} is a relationship with {field_type.entity.class_}")
|
||||
field_value = field_type.entity.class_.argument.query(name=getattr(self, field))
|
||||
logger.debug(f"{field} query result: {field_value}")
|
||||
case ColumnProperty():
|
||||
logger.debug(f"{field} is a property.")
|
||||
field_value = getattr(self, field)
|
||||
self.instance.__setattr__(field, field_value)
|
||||
return self.instance
|
||||
|
||||
|
||||
return instance
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
A widget to handle adding/updating any database object.
|
||||
"""
|
||||
from copy import deepcopy
|
||||
from datetime import date
|
||||
from pprint import pformat
|
||||
from typing import Any, Tuple
|
||||
@@ -14,7 +15,6 @@ from sqlalchemy.orm import InstrumentedAttribute, ColumnProperty
|
||||
import logging
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
from tools import Report, report_result
|
||||
from backend import db
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -26,9 +26,11 @@ class AddEdit(QDialog):
|
||||
logger.debug(f"Managers: {managers}")
|
||||
self.instance = instance
|
||||
self.object_type = instance.__class__
|
||||
# self.managers = deepcopy(managers)
|
||||
self.managers = managers
|
||||
if instance.level < 2:
|
||||
try:
|
||||
logger.debug(f"Parent instance: {self.parent().instance}")
|
||||
self.managers.add(self.parent().instance)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -38,8 +40,8 @@ class AddEdit(QDialog):
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
logger.debug(f"Fields: {pformat(self.instance.omnigui_dict)}")
|
||||
fields = {k: v for k, v in self.instance.omnigui_dict.items() if "id" not in k}
|
||||
# logger.debug(f"Fields: {pformat(self.instance.omnigui_instance_dict)}")
|
||||
fields = {k: v for k, v in self.instance.omnigui_instance_dict.items() if "id" not in k}
|
||||
# NOTE: Move 'name' to the front
|
||||
try:
|
||||
fields = {'name': fields.pop('name'), **fields}
|
||||
@@ -67,12 +69,15 @@ class AddEdit(QDialog):
|
||||
|
||||
@report_result
|
||||
def parse_form(self) -> Tuple[BaseModel, Report]:
|
||||
from backend.validators import pydant
|
||||
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(f"Parsed form: {parsed}")
|
||||
model = self.object_type.pydantic_model
|
||||
logger.debug(f"Model type: {model.__name__}")
|
||||
if model.__name__ == "PydElastic":
|
||||
logger.debug(f"We have an elastic model.")
|
||||
parsed['instance'] = 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)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Provides a screen for managing all attributes of a database object.
|
||||
"""
|
||||
from copy import deepcopy
|
||||
from pprint import pformat
|
||||
from typing import Any, List
|
||||
from PyQt6.QtCore import QSortFilterProxyModel, Qt
|
||||
from PyQt6.QtGui import QAction, QCursor
|
||||
@@ -17,6 +19,8 @@ from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
from pandas import DataFrame
|
||||
from backend import db
|
||||
import logging
|
||||
|
||||
from tools import check_object_in_managers
|
||||
from .omni_add_edit import AddEdit
|
||||
from .omni_search import SearchBox
|
||||
from frontend.widgets.submission_table import pandasModel
|
||||
@@ -33,6 +37,7 @@ class ManagerWindow(QDialog):
|
||||
super().__init__(parent)
|
||||
self.object_type = self.original_type = object_type
|
||||
self.instance = None
|
||||
# self.managers = deepcopy(managers)
|
||||
self.managers = managers
|
||||
try:
|
||||
self.managers.add(self.parent().instance)
|
||||
@@ -68,28 +73,35 @@ class ManagerWindow(QDialog):
|
||||
"""
|
||||
Changes form inputs based on sample type
|
||||
"""
|
||||
# logger.debug(f"Instance: {self.instance}")
|
||||
if self.sub_class:
|
||||
self.object_type = getattr(db, self.sub_class.currentText())
|
||||
logger.debug(f"From update options, managers: {self.managers}")
|
||||
# logger.debug(f"From update options, managers: {self.managers}")
|
||||
try:
|
||||
query_kwargs = {self.parent().instance.query_alias: self.parent().instance}
|
||||
except AttributeError as e:
|
||||
logger.debug(f"Couldn't set query kwargs due to: {e}")
|
||||
# logger.debug(f"Couldn't set query kwargs due to: {e}")
|
||||
query_kwargs = {}
|
||||
logger.debug(f"Query kwargs: {query_kwargs}")
|
||||
# logger.debug(f"Query kwargs: {query_kwargs}")
|
||||
options = [item.name for item in self.object_type.query(**query_kwargs)]
|
||||
logger.debug(f"self.object_type: {self.object_type}")
|
||||
# logger.debug(f"self.object_type: {self.object_type}")
|
||||
if self.instance:
|
||||
options.insert(0, options.pop(options.index(self.instance.name)))
|
||||
try:
|
||||
inserter = options.pop(options.index(self.instance.name))
|
||||
except ValueError:
|
||||
inserter = self.instance.name
|
||||
options.insert(0, inserter)
|
||||
self.options.clear()
|
||||
self.options.addItems(options)
|
||||
self.options.setEditable(False)
|
||||
self.options.setMinimumWidth(self.minimumWidth())
|
||||
self.layout.addWidget(self.options, 1, 0, 1, 1)
|
||||
if len(options) > 0:
|
||||
self.add_button = QPushButton("Add New")
|
||||
self.layout.addWidget(self.add_button, 1, 1, 1, 1)
|
||||
self.options.currentTextChanged.connect(self.update_data)
|
||||
self.add_button.clicked.connect(self.add_new)
|
||||
self.options.currentTextChanged.connect(self.update_data)
|
||||
# logger.debug(f"Instance: {self.instance}")
|
||||
self.update_data()
|
||||
|
||||
def update_data(self) -> None:
|
||||
@@ -105,26 +117,34 @@ class ManagerWindow(QDialog):
|
||||
[item for item in self.findChildren(QDialogButtonBox)]
|
||||
for item in deletes:
|
||||
item.setParent(None)
|
||||
# logger.debug(f"Current options text lower: {self.options.currentText().lower()}")
|
||||
# NOTE: Find the instance this manager will update
|
||||
logger.debug(f"Querying with {self.options.currentText()}")
|
||||
if "blank" not in self.options.currentText().lower() and self.options.currentText() != "":
|
||||
# logger.debug(f"Querying with {self.options.currentText()}")
|
||||
self.instance = self.object_type.query(name=self.options.currentText(), limit=1)
|
||||
logger.debug(f"Instance: {self.instance}")
|
||||
fields = {k: v for k, v in self.instance.omnigui_dict.items() if
|
||||
# logger.debug(f"Instance: {self.instance}")
|
||||
if not self.instance:
|
||||
self.instance = self.object_type()
|
||||
# logger.debug(f"self.instance: {self.instance}")
|
||||
fields = {k: v for k, v in self.instance.omnigui_instance_dict.items() if
|
||||
isinstance(v['class_attr'], InstrumentedAttribute) and k != "id"}
|
||||
# logger.debug(f"Instance fields: {fields}")
|
||||
for key, field in fields.items():
|
||||
try:
|
||||
value = getattr(self.instance, key)
|
||||
except AttributeError:
|
||||
value = None
|
||||
match field['class_attr'].property:
|
||||
# NOTE: ColumnProperties will be directly edited.
|
||||
case ColumnProperty():
|
||||
# NOTE: field.property.expression.type gives db column type eg. STRING or TIMESTAMP
|
||||
widget = EditProperty(self, key=key, column_type=field,
|
||||
value=getattr(self.instance, key))
|
||||
value=value)
|
||||
# NOTE: RelationshipDeclareds will be given a list of existing related objects.
|
||||
case _RelationshipDeclared():
|
||||
if key != "submissions":
|
||||
# NOTE: field.comparator.entity.class_ gives the relationship class
|
||||
widget = EditRelationship(self, key=key, entity=field['class_attr'].comparator.entity.class_,
|
||||
value=getattr(self.instance, key))
|
||||
value=value)
|
||||
else:
|
||||
continue
|
||||
case _:
|
||||
@@ -144,16 +164,26 @@ class ManagerWindow(QDialog):
|
||||
# TODO: Need Relationship property here too?
|
||||
results = [item.parse_form() for item in self.findChildren(EditProperty)]
|
||||
for result in results:
|
||||
# logger.debug(result)
|
||||
self.instance.__setattr__(result[0], result[1])
|
||||
logger.debug(f"Incoming result: {result}")
|
||||
setattr(self.instance, result['field'], result['value'])
|
||||
logger.debug(f"Set result: {getattr(self.instance, result['field'])}")
|
||||
results = [item.parse_form() for item in self.findChildren(EditRelationship)]
|
||||
for result in results:
|
||||
logger.debug(f"Incoming result: {result}")
|
||||
setattr(self.instance, result['field'], result['value'])
|
||||
logger.debug(f"Set result: {getattr(self.instance, result['field'])}")
|
||||
return self.instance
|
||||
|
||||
def add_new(self):
|
||||
dlg = AddEdit(parent=self, instance=self.object_type(), managers=self.managers)
|
||||
if dlg.exec():
|
||||
new_pyd = dlg.parse_form()
|
||||
new_instance = new_pyd.to_sql()
|
||||
new_instance.save()
|
||||
# dlg = AddEdit(parent=self, instance=self.object_type(), managers=self.managers)
|
||||
# if dlg.exec():
|
||||
# new_pyd = dlg.parse_form()
|
||||
# new_instance = new_pyd.to_sql()
|
||||
# # new_instance.save()
|
||||
# logger.debug(f"New instance: {new_instance}")
|
||||
# self.instance = new_instance
|
||||
# self.update_options()
|
||||
new_instance = self.object_type()
|
||||
self.instance = new_instance
|
||||
self.update_options()
|
||||
|
||||
@@ -165,7 +195,8 @@ class EditProperty(QWidget):
|
||||
self.label = QLabel(key.title().replace("_", " "))
|
||||
self.layout = QGridLayout()
|
||||
self.layout.addWidget(self.label, 0, 0, 1, 1)
|
||||
logger.debug(f"Column type: {column_type}")
|
||||
self.setObjectName(key)
|
||||
# logger.debug(f"Column type: {column_type}")
|
||||
match column_type['class_attr'].property.expression.type:
|
||||
case String():
|
||||
self.widget = QLineEdit(self)
|
||||
@@ -203,9 +234,13 @@ class EditProperty(QWidget):
|
||||
value = self.widget.text()
|
||||
case QDateEdit():
|
||||
value = self.widget.date()
|
||||
case QSpinBox() | QDoubleSpinBox():
|
||||
value = self.widget.value()
|
||||
case QCheckBox():
|
||||
value = self.widget.isChecked()
|
||||
case _:
|
||||
value = None
|
||||
return self.objectName(), value
|
||||
return dict(field=self.objectName(), value=value)
|
||||
|
||||
|
||||
class EditRelationship(QWidget):
|
||||
@@ -213,25 +248,37 @@ class EditRelationship(QWidget):
|
||||
def __init__(self, parent, key: str, entity: Any, value):
|
||||
super().__init__(parent)
|
||||
self.entity = entity #: The class of interest
|
||||
self.data = value
|
||||
self.setParent(parent)
|
||||
# logger.debug(f"Edit relationship entity: {self.entity}")
|
||||
self.label = QLabel(key.title().replace("_", " "))
|
||||
self.setObjectName(key)
|
||||
self.table = QTableView()
|
||||
self.setObjectName(key) #: key is the name of the relationship this represents
|
||||
self.relationship = getattr(self.parent().instance.__class__, key)
|
||||
# logger.debug(f"self.relationship: {self.relationship}")
|
||||
# logger.debug(f"Relationship uses list: {self.relationship.property.uselist}")
|
||||
self.data = value
|
||||
# logger.debug(f"Data for edit relationship: {self.data}")
|
||||
self.widget = QTableView()
|
||||
self.set_data()
|
||||
self.add_button = QPushButton("Add New")
|
||||
self.add_button.clicked.connect(self.add_new)
|
||||
self.existing_button = QPushButton("Add Existing")
|
||||
self.existing_button.clicked.connect(self.add_existing)
|
||||
self.existing_button.setEnabled(self.entity.level == 1)
|
||||
# self.existing_button.setEnabled(self.entity.level == 1)
|
||||
if not self.relationship.property.uselist and len(self.data) >= 1:
|
||||
self.add_button.setEnabled(False)
|
||||
self.existing_button.setEnabled(False)
|
||||
logger.debug(f"Checked manager for check: {check_object_in_managers(self.parent().managers, self.objectName())}")
|
||||
if check_object_in_managers(self.parent().managers, self.objectName()):
|
||||
self.widget.setEnabled(False)
|
||||
self.add_button.setEnabled(False)
|
||||
self.existing_button.setEnabled(False)
|
||||
self.layout = QGridLayout()
|
||||
self.layout.addWidget(self.label, 0, 0, 1, 5)
|
||||
self.layout.addWidget(self.table, 1, 0, 1, 8)
|
||||
self.layout.addWidget(self.widget, 1, 0, 1, 8)
|
||||
self.layout.addWidget(self.add_button, 0, 6, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
|
||||
self.layout.addWidget(self.existing_button, 0, 7, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
|
||||
self.setLayout(self.layout)
|
||||
self.set_data()
|
||||
self.table.resizeColumnsToContents()
|
||||
self.table.resizeRowsToContents()
|
||||
self.table.setSortingEnabled(True)
|
||||
# self.set_data()
|
||||
|
||||
def parse_row(self, x):
|
||||
context = {item: x.sibling(x.row(), self.data.columns.get_loc(item)).data() for item in self.data.columns}
|
||||
@@ -239,34 +286,42 @@ class EditRelationship(QWidget):
|
||||
object = self.entity.query(**context)
|
||||
except KeyError:
|
||||
object = None
|
||||
self.table.doubleClicked.disconnect()
|
||||
self.widget.doubleClicked.disconnect()
|
||||
self.add_edit(instance=object)
|
||||
|
||||
def add_new(self, instance: Any = None):
|
||||
# NOTE: if an existing instance is not being edited, create a new instance
|
||||
if not instance:
|
||||
instance = self.entity()
|
||||
# if self.parent().object_type.level == 2:
|
||||
managers = self.parent().managers
|
||||
# else:
|
||||
# managers = self.parent().managers + [self.parent().instance]
|
||||
match instance.level:
|
||||
case 1:
|
||||
dlg = AddEdit(self.parent(), instance=instance, managers=managers)
|
||||
case 2:
|
||||
logger.debug(f"Managers going into add new: {managers}")
|
||||
# match instance.level:
|
||||
# case 1:
|
||||
# dlg = AddEdit(self.parent(), instance=instance, managers=managers)
|
||||
# case 2:
|
||||
# dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], managers=managers)
|
||||
# case _:
|
||||
# return
|
||||
dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], managers=managers)
|
||||
case _:
|
||||
return
|
||||
if dlg.exec():
|
||||
new_instance = dlg.parse_form()
|
||||
logger.debug(f"New instance before transformation attempt: {new_instance}")
|
||||
try:
|
||||
new_instance = new_instance.to_sql()
|
||||
logger.debug(f"New instance: {new_instance}")
|
||||
addition = getattr(self.parent().instance, self.objectName())
|
||||
logger.debug(f"Addition: {addition}")
|
||||
except AttributeError as e:
|
||||
logger.error(f"Couldn't convert {new_instance} to sql due to {e}")
|
||||
logger.debug(f"New instance after transformation attempt: {new_instance.__dict__}")
|
||||
# addition = getattr(self.parent().instance, self.objectName())
|
||||
# logger.debug(f"Addition: {addition}")
|
||||
# if self.relationship.property.uselist:
|
||||
# addition.append(instance)
|
||||
# else:
|
||||
# addition = instance
|
||||
# setattr(self.parent().instance, self.objectName(), new_instance)
|
||||
logger.debug(f"Parent instance after insert: {getattr(self.parent().instance, self.objectName())}")
|
||||
# NOTE: Saving currently disabled
|
||||
# if isinstance(addition, InstrumentedList):
|
||||
# addition.append(new_instance)
|
||||
# self.parent().instance.save()
|
||||
return new_instance
|
||||
self.parent().update_data()
|
||||
|
||||
def add_existing(self):
|
||||
@@ -274,16 +329,24 @@ class EditRelationship(QWidget):
|
||||
if dlg.exec():
|
||||
rows = dlg.return_selected_rows()
|
||||
for row in rows:
|
||||
logger.debug(f"Querying with {row}")
|
||||
# logger.debug(f"Querying with {row}")
|
||||
instance = self.entity.query(**row)
|
||||
logger.debug(f"Queried instance: {instance}")
|
||||
addition = getattr(self.parent().instance, self.objectName())
|
||||
logger.debug(f"Addition: {addition}")
|
||||
# logger.debug(f"Queried instance: {instance}")
|
||||
# logger.debug(f"Checking field type: {self.objectName()}")
|
||||
# addition = getattr(self.parent().instance, self.objectName())
|
||||
# logger.debug(f"Instance object: {addition}")
|
||||
# NOTE: Saving currently disabled
|
||||
# if isinstance(addition, InstrumentedList):
|
||||
# if self.relationship.property.uselist:
|
||||
# addition.append(instance)
|
||||
# else:
|
||||
# addition = instance
|
||||
setattr(self.parent().instance, self.objectName(), instance)
|
||||
# self.parent().instance.save()
|
||||
self.parent().update_data()
|
||||
# self.parent().update_data()
|
||||
# yield instance
|
||||
|
||||
def set_choices(self) -> None:
|
||||
pass
|
||||
|
||||
def set_data(self) -> None:
|
||||
"""
|
||||
@@ -291,26 +354,38 @@ class EditRelationship(QWidget):
|
||||
"""
|
||||
# logger.debug(self.data)
|
||||
if not isinstance(self.data, list):
|
||||
if self.data is not None:
|
||||
self.data = [self.data]
|
||||
records = [{k: v['instance_attr'] for k, v in item.omnigui_dict.items()} for item in self.data]
|
||||
# logger.debug(f"Records: {records}")
|
||||
self.data = DataFrame.from_records(records)
|
||||
else:
|
||||
self.data = []
|
||||
checked_manager = check_object_in_managers(self.parent().managers, self.objectName())
|
||||
logger.debug(f"Returned checked_manager: {checked_manager}")
|
||||
if checked_manager is not None:
|
||||
if not self.data:
|
||||
self.data = [checked_manager]
|
||||
# logger.debug(f"Data: {self.data}")
|
||||
try:
|
||||
self.columns_of_interest = [dict(name=item, column=self.data.columns.get_loc(item)) for item in self.extras]
|
||||
records = [{k: v['instance_attr'] for k, v in item.omnigui_instance_dict.items()} for item in self.data]
|
||||
except AttributeError:
|
||||
records = []
|
||||
# logger.debug(f"Records: {records}")
|
||||
self.df = DataFrame.from_records(records)
|
||||
try:
|
||||
self.columns_of_interest = [dict(name=item, column=self.df.columns.get_loc(item)) for item in self.extras]
|
||||
except (KeyError, AttributeError):
|
||||
self.columns_of_interest = []
|
||||
try:
|
||||
self.data['id'] = self.data['id'].apply(str)
|
||||
self.data['id'] = self.data['id'].str.zfill(4)
|
||||
self.df['id'] = self.df['id'].apply(str)
|
||||
self.df['id'] = self.df['id'].str.zfill(4)
|
||||
except KeyError as e:
|
||||
logger.error(f"Could not alter id to string due to KeyError: {e}")
|
||||
proxy_model = QSortFilterProxyModel()
|
||||
proxy_model.setSourceModel(pandasModel(self.data))
|
||||
self.table.setModel(proxy_model)
|
||||
self.table.resizeColumnsToContents()
|
||||
self.table.resizeRowsToContents()
|
||||
self.table.setSortingEnabled(True)
|
||||
self.table.doubleClicked.connect(self.parse_row)
|
||||
proxy_model.setSourceModel(pandasModel(self.df))
|
||||
self.widget.setModel(proxy_model)
|
||||
self.widget.resizeColumnsToContents()
|
||||
self.widget.resizeRowsToContents()
|
||||
self.widget.setSortingEnabled(True)
|
||||
self.widget.doubleClicked.connect(self.parse_row)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""
|
||||
@@ -319,16 +394,23 @@ class EditRelationship(QWidget):
|
||||
Args:
|
||||
event (_type_): the item of interest
|
||||
"""
|
||||
id = self.table.selectionModel().currentIndex()
|
||||
print(self.widget.isEnabled())
|
||||
if not self.widget.isEnabled():
|
||||
logger.warning(f"{self.objectName()} is disabled.")
|
||||
return
|
||||
id = self.widget.selectionModel().currentIndex()
|
||||
# NOTE: the overly complicated {column_name: row_value} dictionary construction
|
||||
row_data = {self.data.columns[column]: self.table.model().index(id.row(), column).data() for column in
|
||||
range(self.table.model().columnCount())}
|
||||
row_data = {self.df.columns[column]: self.widget.model().index(id.row(), column).data() for column in
|
||||
range(self.widget.model().columnCount())}
|
||||
object = self.entity.query(**row_data)
|
||||
if isinstance(object, list):
|
||||
object = object[0]
|
||||
logger.debug(object)
|
||||
# logger.debug(object)
|
||||
self.menu = QMenu(self)
|
||||
try:
|
||||
action = QAction(f"Remove {object.name}", self)
|
||||
except AttributeError:
|
||||
action = QAction(f"Remove object", self)
|
||||
action.triggered.connect(lambda: self.remove_item(object=object))
|
||||
self.menu.addAction(action)
|
||||
self.menu.popup(QCursor.pos())
|
||||
@@ -338,3 +420,6 @@ class EditRelationship(QWidget):
|
||||
editor.remove(object)
|
||||
self.parent().instance.save()
|
||||
self.parent().update_data()
|
||||
|
||||
def parse_form(self):
|
||||
return dict(field=self.objectName(), value=self.data)
|
||||
|
||||
@@ -341,7 +341,7 @@ class SubmissionFormWidget(QWidget):
|
||||
# result = self.pyd.check_reagent_expiries(exempt=exempt)
|
||||
if len(result.results) > 0:
|
||||
return report
|
||||
base_submission, result = self.pyd.to_sql()
|
||||
base_submission = self.pyd.to_sql()
|
||||
# NOTE: check output message for issues
|
||||
try:
|
||||
trigger = result.results[-1]
|
||||
@@ -693,7 +693,7 @@ 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)
|
||||
if rt is None:
|
||||
rt = ReagentRole.query(kit_type=self.extraction_kit, reagent=wanted_reagent)
|
||||
rt = ReagentRole.query(kittype=self.extraction_kit, reagent=wanted_reagent)
|
||||
final = PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||
expiry=wanted_reagent.expiry.date(), missing=False)
|
||||
return final, report
|
||||
@@ -733,8 +733,8 @@ class SubmissionFormWidget(QWidget):
|
||||
def __init__(self, scrollWidget, reagent, extraction_kit: str) -> None:
|
||||
super().__init__(scrollWidget=scrollWidget)
|
||||
self.setEditable(True)
|
||||
looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role,
|
||||
kit_type=extraction_kit)
|
||||
looked_up_rt = KitTypeReagentRoleAssociation.query(reagentrole=reagent.role,
|
||||
kittype=extraction_kit)
|
||||
relevant_reagents = [str(item.lot) for item in looked_up_rt.get_all_relevant_reagents()]
|
||||
# NOTE: if reagent in sheet is not found insert it into the front of relevant reagents so it shows
|
||||
if str(reagent.lot) not in relevant_reagents:
|
||||
|
||||
@@ -16,11 +16,14 @@ from dateutil.easter import easter
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from logging import handlers
|
||||
from pathlib import Path
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, InstrumentedAttribute
|
||||
from sqlalchemy import create_engine, text, MetaData
|
||||
from pydantic import field_validator, BaseModel, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from typing import Any, Tuple, Literal, List, Generator
|
||||
|
||||
from sqlalchemy.orm.relationships import _RelationshipDeclared
|
||||
|
||||
from __init__ import project_path
|
||||
from configparser import ConfigParser
|
||||
from tkinter import Tk # NOTE: This is for choosing database path before app is created.
|
||||
@@ -813,6 +816,37 @@ def setup_lookup(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_object_in_managers(managers: list, object_name: object):
|
||||
for manager in managers:
|
||||
logger.debug(f"Manager: {manager}, Key: {object_name}")
|
||||
if object_name in manager.aliases:
|
||||
return manager
|
||||
relationships = [getattr(manager.__class__, item) for item in dir(manager.__class__)
|
||||
if isinstance(getattr(manager.__class__, item), InstrumentedAttribute)]
|
||||
relationships = [item for item in relationships if isinstance(item.property, _RelationshipDeclared)]
|
||||
for relationship in relationships:
|
||||
if relationship.key == object_name:
|
||||
logger.debug(f"Checking {relationship.key}")
|
||||
try:
|
||||
rel_obj = getattr(manager, relationship.key)
|
||||
if rel_obj is not None:
|
||||
logger.debug(f"Returning {rel_obj}")
|
||||
return rel_obj
|
||||
except AttributeError:
|
||||
pass
|
||||
if "association" in relationship.key:
|
||||
try:
|
||||
logger.debug(f"Checking association {relationship.key}")
|
||||
rel_obj = next((getattr(item, object_name) for item in getattr(manager, relationship.key)
|
||||
if getattr(item, object_name) is not None), None)
|
||||
if rel_obj is not None:
|
||||
logger.debug(f"Returning {rel_obj}")
|
||||
return rel_obj
|
||||
except AttributeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def get_application_from_parent(widget):
|
||||
try:
|
||||
return widget.app
|
||||
|
||||
Reference in New Issue
Block a user