Renaming ReagentType to ReagentRole
This commit is contained in:
@@ -56,9 +56,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
|||||||
# output_encoding = utf-8
|
# output_encoding = utf-8
|
||||||
|
|
||||||
; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
; sqlalchemy.url = sqlite:///L:\Robotics Laboratory Support\Submissions\submissions.db
|
||||||
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-demo.db
|
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-demo.db
|
||||||
;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-prototypes.db
|
;sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\Archives\Submissions_app_backups\DB_backups\submissions-prototypes.db
|
||||||
sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\mytests\test_assets\submissions-test.db
|
; sqlalchemy.url = sqlite:///C:\Users\lwark\Documents\python\submissions\mytests\test_assets\submissions-test.db
|
||||||
|
|
||||||
|
|
||||||
[post_write_hooks]
|
[post_write_hooks]
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ from io import BytesIO
|
|||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
|
|
||||||
# logger.debug("Table for ReagentType/Reagent relations")
|
# logger.debug("Table for ReagentType/Reagent relations")
|
||||||
reagenttypes_reagents = Table(
|
reagentroles_reagents = Table(
|
||||||
"_reagenttypes_reagents",
|
"_reagentroles_reagents",
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("reagent_id", INTEGER, ForeignKey("_reagent.id")),
|
Column("reagent_id", INTEGER, ForeignKey("_reagent.id")),
|
||||||
Column("reagenttype_id", INTEGER, ForeignKey("_reagenttype.id")),
|
Column("reagentrole_id", INTEGER, ForeignKey("_reagentrole.id")),
|
||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,16 +84,16 @@ class KitType(BaseClass):
|
|||||||
processes = relationship("Process", back_populates="kit_types",
|
processes = relationship("Process", back_populates="kit_types",
|
||||||
secondary=kittypes_processes) #: equipment processes used by this kit
|
secondary=kittypes_processes) #: equipment processes used by this kit
|
||||||
|
|
||||||
kit_reagenttype_associations = relationship(
|
kit_reagentrole_associations = relationship(
|
||||||
"KitTypeReagentTypeAssociation",
|
"KitTypeReagentRoleAssociation",
|
||||||
back_populates="kit_type",
|
back_populates="kit_type",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
)
|
)
|
||||||
|
|
||||||
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
||||||
reagent_types = association_proxy("kit_reagenttype_associations", "reagent_type",
|
reagent_roles = association_proxy("kit_reagentrole_associations", "reagent_role",
|
||||||
creator=lambda RT: KitTypeReagentTypeAssociation(
|
creator=lambda RT: KitTypeReagentRoleAssociation(
|
||||||
reagent_type=RT)) #: Association proxy to KitTypeReagentTypeAssociation
|
reagent_role=RT)) #: Association proxy to KitTypeReagentRoleAssociation
|
||||||
|
|
||||||
kit_submissiontype_associations = relationship(
|
kit_submissiontype_associations = relationship(
|
||||||
"SubmissionTypeKitTypeAssociation",
|
"SubmissionTypeKitTypeAssociation",
|
||||||
@@ -111,7 +111,7 @@ class KitType(BaseClass):
|
|||||||
"""
|
"""
|
||||||
return f"<KitType({self.name})>"
|
return f"<KitType({self.name})>"
|
||||||
|
|
||||||
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[ReagentType]:
|
def get_reagents(self, required: bool = False, submission_type: str | SubmissionType | None = None) -> List[ReagentRole]:
|
||||||
"""
|
"""
|
||||||
Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation.
|
Return ReagentTypes linked to kit through KitTypeReagentTypeAssociation.
|
||||||
|
|
||||||
@@ -120,25 +120,25 @@ class KitType(BaseClass):
|
|||||||
submission_type (str | Submissiontype | None, optional): Submission type to narrow results. Defaults to None.
|
submission_type (str | Submissiontype | None, optional): Submission type to narrow results. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[ReagentType]: List of reagents linked to this kit.
|
List[ReagentRole]: List of reagents linked to this kit.
|
||||||
"""
|
"""
|
||||||
match submission_type:
|
match submission_type:
|
||||||
case SubmissionType():
|
case SubmissionType():
|
||||||
# logger.debug(f"Getting reagents by SubmissionType {submission_type}")
|
# logger.debug(f"Getting reagents by SubmissionType {submission_type}")
|
||||||
relevant_associations = [item for item in self.kit_reagenttype_associations if
|
relevant_associations = [item for item in self.kit_reagentrole_associations if
|
||||||
item.submission_type == submission_type]
|
item.submission_type == submission_type]
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Getting reagents by str {submission_type}")
|
# logger.debug(f"Getting reagents by str {submission_type}")
|
||||||
relevant_associations = [item for item in self.kit_reagenttype_associations if
|
relevant_associations = [item for item in self.kit_reagentrole_associations if
|
||||||
item.submission_type.name == submission_type]
|
item.submission_type.name == submission_type]
|
||||||
case _:
|
case _:
|
||||||
# logger.debug(f"Getting reagents")
|
# logger.debug(f"Getting reagents")
|
||||||
relevant_associations = [item for item in self.kit_reagenttype_associations]
|
relevant_associations = [item for item in self.kit_reagentrole_associations]
|
||||||
if required:
|
if required:
|
||||||
# logger.debug(f"Filtering by required.")
|
# logger.debug(f"Filtering by required.")
|
||||||
return [item.reagent_type for item in relevant_associations if item.required == 1]
|
return [item.reagent_role for item in relevant_associations if item.required == 1]
|
||||||
else:
|
else:
|
||||||
return [item.reagent_type for item in relevant_associations]
|
return [item.reagent_role for item in relevant_associations]
|
||||||
|
|
||||||
# TODO: Move to BasicSubmission?
|
# TODO: Move to BasicSubmission?
|
||||||
def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> dict:
|
def construct_xl_map_for_use(self, submission_type: str | SubmissionType) -> dict:
|
||||||
@@ -156,17 +156,17 @@ class KitType(BaseClass):
|
|||||||
match submission_type:
|
match submission_type:
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Constructing xl map with str {submission_type}")
|
# logger.debug(f"Constructing xl map with str {submission_type}")
|
||||||
assocs = [item for item in self.kit_reagenttype_associations if
|
assocs = [item for item in self.kit_reagentrole_associations if
|
||||||
item.submission_type.name == submission_type]
|
item.submission_type.name == submission_type]
|
||||||
case SubmissionType():
|
case SubmissionType():
|
||||||
# logger.debug(f"Constructing xl map with SubmissionType {submission_type}")
|
# logger.debug(f"Constructing xl map with SubmissionType {submission_type}")
|
||||||
assocs = [item for item in self.kit_reagenttype_associations if item.submission_type == submission_type]
|
assocs = [item for item in self.kit_reagentrole_associations if item.submission_type == submission_type]
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Wrong variable type: {type(submission_type)} used!")
|
raise ValueError(f"Wrong variable type: {type(submission_type)} used!")
|
||||||
# logger.debug("Get all KitTypeReagentTypeAssociation for SubmissionType")
|
# logger.debug("Get all KitTypeReagentTypeAssociation for SubmissionType")
|
||||||
for assoc in assocs:
|
for assoc in assocs:
|
||||||
try:
|
try:
|
||||||
info_map[assoc.reagent_type.name] = assoc.uses
|
info_map[assoc.reagent_role.name] = assoc.uses
|
||||||
except TypeError:
|
except TypeError:
|
||||||
continue
|
continue
|
||||||
return info_map
|
return info_map
|
||||||
@@ -226,34 +226,34 @@ class KitType(BaseClass):
|
|||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
|
|
||||||
class ReagentType(BaseClass):
|
class ReagentRole(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base of reagent type abstract
|
Base of reagent type abstract
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: name of reagent type
|
name = Column(String(64)) #: name of reagent type
|
||||||
instances = relationship("Reagent", back_populates="type",
|
instances = relationship("Reagent", back_populates="role",
|
||||||
secondary=reagenttypes_reagents) #: concrete instances of this reagent type
|
secondary=reagentroles_reagents) #: concrete instances of this reagent type
|
||||||
eol_ext = Column(Interval()) #: extension of life interval
|
eol_ext = Column(Interval()) #: extension of life interval
|
||||||
|
|
||||||
reagenttype_kit_associations = relationship(
|
reagentrole_kit_associations = relationship(
|
||||||
"KitTypeReagentTypeAssociation",
|
"KitTypeReagentRoleAssociation",
|
||||||
back_populates="reagent_type",
|
back_populates="reagent_role",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
) #: Relation to KitTypeReagentTypeAssociation
|
) #: Relation to KitTypeReagentTypeAssociation
|
||||||
|
|
||||||
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
# creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
||||||
kit_types = association_proxy("reagenttype_kit_associations", "kit_type",
|
kit_types = association_proxy("reagentrole_kit_associations", "kit_type",
|
||||||
creator=lambda kit: KitTypeReagentTypeAssociation(
|
creator=lambda kit: KitTypeReagentRoleAssociation(
|
||||||
kit_type=kit)) #: Association proxy to KitTypeReagentTypeAssociation
|
kit_type=kit)) #: Association proxy to KitTypeReagentRoleAssociation
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns:
|
Returns:
|
||||||
str: Representation of object
|
str: Representation of object
|
||||||
"""
|
"""
|
||||||
return f"<ReagentType({self.name})>"
|
return f"<ReagentRole({self.name})>"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
@@ -262,7 +262,7 @@ class ReagentType(BaseClass):
|
|||||||
kit_type: KitType | str | None = None,
|
kit_type: KitType | str | None = None,
|
||||||
reagent: Reagent | str | None = None,
|
reagent: Reagent | str | None = None,
|
||||||
limit: int = 0,
|
limit: int = 0,
|
||||||
) -> ReagentType | List[ReagentType]:
|
) -> ReagentRole | List[ReagentRole]:
|
||||||
"""
|
"""
|
||||||
Lookup reagent types in the database.
|
Lookup reagent types in the database.
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ class ReagentType(BaseClass):
|
|||||||
ValueError: Raised if only kit_type or reagent, not both, given.
|
ValueError: Raised if only kit_type or reagent, not both, given.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ReagentType|List[ReagentType]: ReagentType or list of ReagentTypes matching filter.
|
ReagentRole|List[ReagentRole]: ReagentRole or list of ReagentRoles matching filter.
|
||||||
"""
|
"""
|
||||||
query: Query = cls.__database_session__.query(cls)
|
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 (kit_type is not None and reagent is None) or (reagent is not None and kit_type is None):
|
||||||
@@ -296,10 +296,10 @@ class ReagentType(BaseClass):
|
|||||||
reagent = Reagent.query(lot_number=reagent)
|
reagent = Reagent.query(lot_number=reagent)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
assert reagent.type
|
assert reagent.role
|
||||||
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
# logger.debug(f"Looking up reagent type for {type(kit_type)} {kit_type} and {type(reagent)} {reagent}")
|
||||||
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
# logger.debug(f"Kit reagent types: {kit_type.reagent_types}")
|
||||||
result = list(set(kit_type.reagent_types).intersection(reagent.type))
|
result = list(set(kit_type.reagent_roles).intersection(reagent.role))
|
||||||
# logger.debug(f"Result: {result}")
|
# logger.debug(f"Result: {result}")
|
||||||
try:
|
try:
|
||||||
return result[0]
|
return result[0]
|
||||||
@@ -322,7 +322,7 @@ class ReagentType(BaseClass):
|
|||||||
PydReagent: PydReagent representation of this object.
|
PydReagent: PydReagent representation of this object.
|
||||||
"""
|
"""
|
||||||
from backend.validators.pydant import PydReagent
|
from backend.validators.pydant import PydReagent
|
||||||
return PydReagent(lot=None, type=self.name, name=self.name, expiry=date.today())
|
return PydReagent(lot=None, role=self.name, name=self.name, expiry=date.today())
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -335,10 +335,10 @@ class Reagent(BaseClass):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
type = relationship("ReagentType", back_populates="instances",
|
role = relationship("ReagentRole", back_populates="instances",
|
||||||
secondary=reagenttypes_reagents) #: joined parent reagent type
|
secondary=reagentroles_reagents) #: joined parent reagent type
|
||||||
type_id = Column(INTEGER, ForeignKey("_reagenttype.id", ondelete='SET NULL',
|
role_id = Column(INTEGER, ForeignKey("_reagentrole.id", ondelete='SET NULL',
|
||||||
name="fk_reagent_type_id")) #: id of parent reagent type
|
name="fk_reagent_role_id")) #: id of parent reagent type
|
||||||
name = Column(String(64)) #: reagent name
|
name = Column(String(64)) #: reagent name
|
||||||
lot = Column(String(64)) #: lot number of reagent
|
lot = Column(String(64)) #: lot number of reagent
|
||||||
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
||||||
@@ -356,7 +356,7 @@ class Reagent(BaseClass):
|
|||||||
if self.name is not None:
|
if self.name is not None:
|
||||||
return f"<Reagent({self.name}-{self.lot})>"
|
return f"<Reagent({self.name}-{self.lot})>"
|
||||||
else:
|
else:
|
||||||
return f"<Reagent({self.type.name}-{self.lot})>"
|
return f"<Reagent({self.role.name}-{self.lot})>"
|
||||||
|
|
||||||
def to_sub_dict(self, extraction_kit: KitType = None) -> dict:
|
def to_sub_dict(self, extraction_kit: KitType = None) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -371,12 +371,12 @@ class Reagent(BaseClass):
|
|||||||
if extraction_kit is not None:
|
if extraction_kit is not None:
|
||||||
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
# NOTE: Get the intersection of this reagent's ReagentType and all ReagentTypes in KitType
|
||||||
try:
|
try:
|
||||||
reagent_role = list(set(self.type).intersection(extraction_kit.reagent_types))[0]
|
reagent_role = list(set(self.role).intersection(extraction_kit.reagent_roles))[0]
|
||||||
# NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
|
# NOTE: Most will be able to fall back to first ReagentType in itself because most will only have 1.
|
||||||
except:
|
except:
|
||||||
reagent_role = self.type[0]
|
reagent_role = self.role[0]
|
||||||
else:
|
else:
|
||||||
reagent_role = self.type[0]
|
reagent_role = self.role[0]
|
||||||
try:
|
try:
|
||||||
rtype = reagent_role.name.replace("_", " ")
|
rtype = reagent_role.name.replace("_", " ")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -393,7 +393,7 @@ class Reagent(BaseClass):
|
|||||||
place_holder = place_holder.strftime("%Y-%m-%d")
|
place_holder = place_holder.strftime("%Y-%m-%d")
|
||||||
return dict(
|
return dict(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
type=rtype,
|
role=rtype,
|
||||||
lot=self.lot,
|
lot=self.lot,
|
||||||
expiry=place_holder,
|
expiry=place_holder,
|
||||||
missing=False
|
missing=False
|
||||||
@@ -411,10 +411,10 @@ class Reagent(BaseClass):
|
|||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
# logger.debug(f"Attempting update of last used reagent type at intersection of ({self}), ({kit})")
|
# logger.debug(f"Attempting update of last used reagent type at intersection of ({self}), ({kit})")
|
||||||
rt = ReagentType.query(kit_type=kit, reagent=self, limit=1)
|
rt = ReagentRole.query(kit_type=kit, reagent=self, limit=1)
|
||||||
if rt is not None:
|
if rt is not None:
|
||||||
# logger.debug(f"got reagenttype {rt}")
|
# logger.debug(f"got reagenttype {rt}")
|
||||||
assoc = KitTypeReagentTypeAssociation.query(kit_type=kit, reagent_type=rt)
|
assoc = KitTypeReagentRoleAssociation.query(kit_type=kit, reagent_role=rt)
|
||||||
if assoc is not None:
|
if assoc is not None:
|
||||||
if assoc.last_used != self.lot:
|
if assoc.last_used != self.lot:
|
||||||
# logger.debug(f"Updating {assoc} last used to {self.lot}")
|
# logger.debug(f"Updating {assoc} last used to {self.lot}")
|
||||||
@@ -429,7 +429,7 @@ class Reagent(BaseClass):
|
|||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
id: int | None = None,
|
id: int | None = None,
|
||||||
reagent_type: str | ReagentType | None = None,
|
reagent_role: str | ReagentRole | None = None,
|
||||||
lot_number: str | None = None,
|
lot_number: str | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
limit: int = 0
|
limit: int = 0
|
||||||
@@ -438,7 +438,7 @@ class Reagent(BaseClass):
|
|||||||
Lookup a list of reagents from the database.
|
Lookup a list of reagents from the database.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reagent_type (str | models.ReagentType | None, optional): Reagent type. Defaults to None.
|
reagent_role (str | models.ReagentType | None, optional): Reagent type. Defaults to None.
|
||||||
lot_number (str | None, optional): Reagent lot number. Defaults to None.
|
lot_number (str | None, optional): Reagent lot number. Defaults to None.
|
||||||
name (str | None, optional): Reagent name. Defaults to None.
|
name (str | None, optional): Reagent name. Defaults to None.
|
||||||
limit (int, optional): limit of results returned. Defaults to 0.
|
limit (int, optional): limit of results returned. Defaults to 0.
|
||||||
@@ -453,13 +453,13 @@ class Reagent(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
match reagent_type:
|
match reagent_role:
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Looking up reagents by reagent type str: {reagent_type}")
|
# logger.debug(f"Looking up reagents by reagent type str: {reagent_type}")
|
||||||
query = query.join(cls.type).filter(ReagentType.name == reagent_type)
|
query = query.join(cls.role).filter(ReagentRole.name == reagent_role)
|
||||||
case ReagentType():
|
case ReagentRole():
|
||||||
# logger.debug(f"Looking up reagents by reagent type ReagentType: {reagent_type}")
|
# logger.debug(f"Looking up reagents by reagent type ReagentType: {reagent_type}")
|
||||||
query = query.filter(cls.type.contains(reagent_type))
|
query = query.filter(cls.role.contains(reagent_role))
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
match name:
|
match name:
|
||||||
@@ -582,7 +582,7 @@ class SubmissionType(BaseClass):
|
|||||||
"equipment_role") #: Proxy of equipmentrole associations
|
"equipment_role") #: Proxy of equipmentrole associations
|
||||||
|
|
||||||
submissiontype_kit_rt_associations = relationship(
|
submissiontype_kit_rt_associations = relationship(
|
||||||
"KitTypeReagentTypeAssociation",
|
"KitTypeReagentRoleAssociation",
|
||||||
back_populates="submission_type",
|
back_populates="submission_type",
|
||||||
cascade="all, delete-orphan"
|
cascade="all, delete-orphan"
|
||||||
) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
|
) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
|
||||||
@@ -617,7 +617,7 @@ class SubmissionType(BaseClass):
|
|||||||
if isinstance(filepath, str):
|
if isinstance(filepath, str):
|
||||||
filepath = Path(filepath)
|
filepath = Path(filepath)
|
||||||
try:
|
try:
|
||||||
xl = ExcelFile(filepath)
|
ExcelFile(filepath)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(f"File {filepath} is not of appropriate type.")
|
raise ValueError(f"File {filepath} is not of appropriate type.")
|
||||||
with open(filepath, "rb") as f:
|
with open(filepath, "rb") as f:
|
||||||
@@ -836,13 +836,13 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
|||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
|
|
||||||
class KitTypeReagentTypeAssociation(BaseClass):
|
class KitTypeReagentRoleAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
table containing reagenttype/kittype associations
|
table containing reagenttype/kittype associations
|
||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reagent_types_id = Column(INTEGER, ForeignKey("_reagenttype.id"),
|
reagent_roles_id = Column(INTEGER, ForeignKey("_reagentrole.id"),
|
||||||
primary_key=True) #: id of associated reagent type
|
primary_key=True) #: id of associated reagent type
|
||||||
kits_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of associated reagent type
|
kits_id = Column(INTEGER, ForeignKey("_kittype.id"), primary_key=True) #: id of associated reagent type
|
||||||
submission_type_id = Column(INTEGER, ForeignKey("_submissiontype.id"), primary_key=True)
|
submission_type_id = Column(INTEGER, ForeignKey("_submissiontype.id"), primary_key=True)
|
||||||
@@ -851,23 +851,23 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
last_used = Column(String(32)) #: last used lot number of this type of reagent
|
last_used = Column(String(32)) #: last used lot number of this type of reagent
|
||||||
|
|
||||||
kit_type = relationship(KitType,
|
kit_type = relationship(KitType,
|
||||||
back_populates="kit_reagenttype_associations") #: relationship to associated KitType
|
back_populates="kit_reagentrole_associations") #: relationship to associated KitType
|
||||||
|
|
||||||
# reference to the "ReagentType" object
|
# reference to the "ReagentType" object
|
||||||
reagent_type = relationship(ReagentType,
|
reagent_role = relationship(ReagentRole,
|
||||||
back_populates="reagenttype_kit_associations") #: relationship to associated ReagentType
|
back_populates="reagentrole_kit_associations") #: relationship to associated ReagentType
|
||||||
|
|
||||||
submission_type = relationship(SubmissionType,
|
submission_type = relationship(SubmissionType,
|
||||||
back_populates="submissiontype_kit_rt_associations") #: relationship to associated SubmissionType
|
back_populates="submissiontype_kit_rt_associations") #: relationship to associated SubmissionType
|
||||||
|
|
||||||
def __init__(self, kit_type=None, reagent_type=None, uses=None, required=1):
|
def __init__(self, kit_type=None, reagent_role=None, uses=None, required=1):
|
||||||
self.kit_type = kit_type
|
self.kit_type = kit_type
|
||||||
self.reagent_type = reagent_type
|
self.reagent_role = reagent_role
|
||||||
self.uses = uses
|
self.uses = uses
|
||||||
self.required = required
|
self.required = required
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<KitTypeReagentTypeAssociation({self.kit_type} & {self.reagent_type})>"
|
return f"<KitTypeReagentRoleAssociation({self.kit_type} & {self.reagent_role})>"
|
||||||
|
|
||||||
@validates('required')
|
@validates('required')
|
||||||
def validate_age(self, key, value):
|
def validate_age(self, key, value):
|
||||||
@@ -888,8 +888,8 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@validates('reagenttype')
|
@validates('reagentrole')
|
||||||
def validate_reagenttype(self, key, value):
|
def validate_reagentrole(self, key, value):
|
||||||
"""
|
"""
|
||||||
Ensures reagenttype is an actual ReagentType
|
Ensures reagenttype is an actual ReagentType
|
||||||
|
|
||||||
@@ -903,23 +903,23 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
_type_: ReagentType
|
_type_: ReagentType
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, ReagentType):
|
if not isinstance(value, ReagentRole):
|
||||||
raise ValueError(f'{value} is not a reagenttype')
|
raise ValueError(f'{value} is not a reagentrole')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
def query(cls,
|
def query(cls,
|
||||||
kit_type: KitType | str | None = None,
|
kit_type: KitType | str | None = None,
|
||||||
reagent_type: ReagentType | str | None = None,
|
reagent_role: ReagentRole | str | None = None,
|
||||||
limit: int = 0
|
limit: int = 0
|
||||||
) -> KitTypeReagentTypeAssociation | List[KitTypeReagentTypeAssociation]:
|
) -> KitTypeReagentRoleAssociation | List[KitTypeReagentRoleAssociation]:
|
||||||
"""
|
"""
|
||||||
Lookup junction of ReagentType and KitType
|
Lookup junction of ReagentType and KitType
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
kit_type (models.KitType | str | None): KitType of interest.
|
kit_type (models.KitType | str | None): KitType of interest.
|
||||||
reagent_type (models.ReagentType | str | None): ReagentType of interest.
|
reagent_role (models.ReagentType | str | None): ReagentType of interest.
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -935,16 +935,16 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
query = query.join(KitType).filter(KitType.name == kit_type)
|
query = query.join(KitType).filter(KitType.name == kit_type)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
match reagent_type:
|
match reagent_role:
|
||||||
case ReagentType():
|
case ReagentRole():
|
||||||
# logger.debug(f"Lookup KitTypeReagentTypeAssociation by reagent_type ReagentType {reagent_type}")
|
# logger.debug(f"Lookup KitTypeReagentTypeAssociation by reagent_type ReagentType {reagent_type}")
|
||||||
query = query.filter(cls.reagent_type == reagent_type)
|
query = query.filter(cls.reagent_role == reagent_role)
|
||||||
case str():
|
case str():
|
||||||
# logger.debug(f"Lookup KitTypeReagentTypeAssociation by reagent_type ReagentType {reagent_type}")
|
# logger.debug(f"Lookup KitTypeReagentTypeAssociation by reagent_type ReagentType {reagent_type}")
|
||||||
query = query.join(ReagentType).filter(ReagentType.name == reagent_type)
|
query = query.join(ReagentRole).filter(ReagentRole.name == reagent_role)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
if kit_type != None and reagent_type != None:
|
if kit_type != None and reagent_role != None:
|
||||||
limit = 1
|
limit = 1
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
@@ -1478,4 +1478,45 @@ class Process(BaseClass):
|
|||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
|
|
||||||
# class Tips(Reagent):
|
# class TipRole(BaseClass):
|
||||||
|
#
|
||||||
|
# id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
|
# name = Column(String(64)) #: name of reagent type
|
||||||
|
# instances = relationship("Tips", back_populates="role",
|
||||||
|
# secondary=reagenttypes_reagents) #: concrete instances of this reagent type
|
||||||
|
#
|
||||||
|
# tiprole_kit_associations = relationship(
|
||||||
|
# "KitTypeTipRoleAssociation",
|
||||||
|
# back_populates="tip_role",
|
||||||
|
# cascade="all, delete-orphan",
|
||||||
|
# ) #: Relation to KitTypeReagentTypeAssociation
|
||||||
|
#
|
||||||
|
# # creator function: https://stackoverflow.com/questions/11091491/keyerror-when-adding-objects-to-sqlalchemy-association-object/11116291#11116291
|
||||||
|
# kit_types = association_proxy("tiprole_kit_associations", "kit_type",
|
||||||
|
# creator=lambda kit: KitTypeReagentTypeAssociation(
|
||||||
|
# kit_type=kit)) #: Association proxy to KitTypeReagentTypeAssociation
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return f"<TipRole({self.name})>"
|
||||||
|
#
|
||||||
|
# class Tips(BaseClass):
|
||||||
|
#
|
||||||
|
# id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
|
# role = relationship("TipRole", back_populates="instances",
|
||||||
|
# secondary=reagenttypes_reagents) #: joined parent reagent type
|
||||||
|
# role_id = Column(INTEGER, ForeignKey("_tiprole.id", ondelete='SET NULL',
|
||||||
|
# name="fk_tip_role_id")) #: id of parent reagent type
|
||||||
|
# name = Column(String(64)) #: tip common name
|
||||||
|
# lot = Column(String(64)) #: lot number of tips
|
||||||
|
#
|
||||||
|
# tips_submission_associations = relationship(
|
||||||
|
# "SubmissionTipsAssociation",
|
||||||
|
# back_populates="tips",
|
||||||
|
# cascade="all, delete-orphan",
|
||||||
|
# ) #: Relation to SubmissionSampleAssociation
|
||||||
|
#
|
||||||
|
# submissions = association_proxy("tips_submission_associations",
|
||||||
|
# "submission") #: Association proxy to SubmissionSampleAssociation.samples
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return f"<Tips({self.name})>"
|
||||||
|
|||||||
@@ -266,9 +266,9 @@ class BasicSubmission(BaseClass):
|
|||||||
for k in self.extraction_kit.construct_xl_map_for_use(self.submission_type):
|
for k in self.extraction_kit.construct_xl_map_for_use(self.submission_type):
|
||||||
if k == 'info':
|
if k == 'info':
|
||||||
continue
|
continue
|
||||||
if not any([item['type'] == k for item in reagents]):
|
if not any([item['role'] == k for item in reagents]):
|
||||||
reagents.append(
|
reagents.append(
|
||||||
dict(type=k, name="Not Applicable", lot="NA", expiry=date(year=1970, month=1, day=1),
|
dict(role=k, name="Not Applicable", lot="NA", expiry=date(year=1970, month=1, day=1),
|
||||||
missing=True))
|
missing=True))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"We got an error retrieving reagents: {e}")
|
logger.error(f"We got an error retrieving reagents: {e}")
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ class SheetParser(object):
|
|||||||
# logger.debug(f"Submission dictionary coming into 'to_pydantic':\n{pformat(self.sub)}")
|
# logger.debug(f"Submission dictionary coming into 'to_pydantic':\n{pformat(self.sub)}")
|
||||||
pyd_dict = copy(self.sub)
|
pyd_dict = copy(self.sub)
|
||||||
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
pyd_dict['samples'] = [PydSample(**sample) for sample in self.sub['samples']]
|
||||||
|
logger.debug(f"Reagents: {pformat(self.sub['reagents'])}")
|
||||||
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
pyd_dict['reagents'] = [PydReagent(**reagent) for reagent in self.sub['reagents']]
|
||||||
# logger.debug(f"Equipment: {self.sub['equipment']}")
|
# logger.debug(f"Equipment: {self.sub['equipment']}")
|
||||||
try:
|
try:
|
||||||
@@ -307,7 +308,7 @@ class ReagentParser(object):
|
|||||||
comment = ""
|
comment = ""
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
listo.append(
|
listo.append(
|
||||||
PydReagent(type=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
|
PydReagent(role=item.strip(), lot=None, expiry=None, name=None, comment="", missing=True))
|
||||||
continue
|
continue
|
||||||
# NOTE: If the cell is blank tell the PydReagent
|
# NOTE: If the cell is blank tell the PydReagent
|
||||||
if check_not_nan(lot):
|
if check_not_nan(lot):
|
||||||
@@ -324,7 +325,7 @@ class ReagentParser(object):
|
|||||||
logger.warning(f"name is not a string.")
|
logger.warning(f"name is not a string.")
|
||||||
check = True
|
check = True
|
||||||
if check:
|
if check:
|
||||||
listo.append(dict(type=item.strip(), lot=lot, expiry=expiry, name=name, comment=comment,
|
listo.append(dict(role=item.strip(), lot=lot, expiry=expiry, name=name, comment=comment,
|
||||||
missing=missing))
|
missing=missing))
|
||||||
return listo
|
return listo
|
||||||
|
|
||||||
|
|||||||
@@ -178,5 +178,5 @@ class RSLNamer(object):
|
|||||||
return template.render(**kwargs)
|
return template.render(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
from .pydant import PydSubmission, PydKit, PydContact, PydOrganization, PydSample, PydReagent, PydReagentType, \
|
from .pydant import PydSubmission, PydKit, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
|
||||||
PydEquipment, PydEquipmentRole
|
PydEquipment, PydEquipmentRole
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class PydReagent(BaseModel):
|
class PydReagent(BaseModel):
|
||||||
lot: str | None
|
lot: str | None
|
||||||
type: str | None
|
role: str | None
|
||||||
expiry: date | Literal['NA'] | None
|
expiry: date | Literal['NA'] | None
|
||||||
name: str | None
|
name: str | None
|
||||||
missing: bool = Field(default=True)
|
missing: bool = Field(default=True)
|
||||||
@@ -38,7 +38,7 @@ class PydReagent(BaseModel):
|
|||||||
return ""
|
return ""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@field_validator("type", mode='before')
|
@field_validator("role", mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_undesired_types(cls, value):
|
def remove_undesired_types(cls, value):
|
||||||
match value:
|
match value:
|
||||||
@@ -47,7 +47,7 @@ class PydReagent(BaseModel):
|
|||||||
case _:
|
case _:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@field_validator("type")
|
@field_validator("role")
|
||||||
@classmethod
|
@classmethod
|
||||||
def rescue_type_with_lookup(cls, value, values):
|
def rescue_type_with_lookup(cls, value, values):
|
||||||
if value == None and values.data['lot'] != None:
|
if value == None and values.data['lot'] != None:
|
||||||
@@ -147,10 +147,10 @@ class PydReagent(BaseModel):
|
|||||||
match key:
|
match key:
|
||||||
case "lot":
|
case "lot":
|
||||||
reagent.lot = value.upper()
|
reagent.lot = value.upper()
|
||||||
case "type":
|
case "role":
|
||||||
reagent_type = ReagentType.query(name=value)
|
reagent_role = ReagentRole.query(name=value)
|
||||||
if reagent_type != None:
|
if reagent_role is not None:
|
||||||
reagent.type.append(reagent_type)
|
reagent.role.append(reagent_role)
|
||||||
case "comment":
|
case "comment":
|
||||||
continue
|
continue
|
||||||
case "expiry":
|
case "expiry":
|
||||||
@@ -792,11 +792,11 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
# logger.debug(f"Kit reagents: {ext_kit_rtypes}")
|
# logger.debug(f"Kit reagents: {ext_kit_rtypes}")
|
||||||
# logger.debug(f"Submission reagents: {self.reagents}")
|
# logger.debug(f"Submission reagents: {self.reagents}")
|
||||||
# Exclude any reagenttype found in this pyd not expected in kit.
|
# Exclude any reagenttype found in this pyd not expected in kit.
|
||||||
expected_check = [item.type for item in ext_kit_rtypes]
|
expected_check = [item.role for item in ext_kit_rtypes]
|
||||||
output_reagents = [rt for rt in self.reagents if rt.type in expected_check]
|
output_reagents = [rt for rt in self.reagents if rt.role in expected_check]
|
||||||
# logger.debug(f"Already have these reagent types: {output_reagents}")
|
# logger.debug(f"Already have these reagent types: {output_reagents}")
|
||||||
missing_check = [item.type 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.type not in missing_check]
|
missing_reagents = [rt for rt in ext_kit_rtypes if rt.role not in missing_check]
|
||||||
missing_reagents += [rt for rt in output_reagents if rt.missing]
|
missing_reagents += [rt for rt in output_reagents if rt.missing]
|
||||||
output_reagents += [rt for rt in missing_reagents if rt not in output_reagents]
|
output_reagents += [rt for rt in missing_reagents if rt not in output_reagents]
|
||||||
# logger.debug(f"Missing reagents types: {missing_reagents}")
|
# logger.debug(f"Missing reagents types: {missing_reagents}")
|
||||||
@@ -805,7 +805,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
result = None
|
result = None
|
||||||
else:
|
else:
|
||||||
result = Result(
|
result = Result(
|
||||||
msg=f"The excel sheet you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.type.upper() for item in missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!",
|
msg=f"The excel sheet you are importing is missing some reagents expected by the kit.\n\nIt looks like you are missing: {[item.role.upper() for item in missing_reagents]}\n\nAlternatively, you may have set the wrong extraction kit.\n\nThe program will populate lists using existing reagents.\n\nPlease make sure you check the lots carefully!",
|
||||||
status="Warning")
|
status="Warning")
|
||||||
report.add_result(result)
|
report.add_result(result)
|
||||||
return output_reagents, report
|
return output_reagents, report
|
||||||
@@ -850,7 +850,7 @@ class PydOrganization(BaseModel):
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class PydReagentType(BaseModel):
|
class PydReagentRole(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
eol_ext: timedelta | int | None
|
eol_ext: timedelta | int | None
|
||||||
uses: dict | None
|
uses: dict | None
|
||||||
@@ -863,33 +863,33 @@ class PydReagentType(BaseModel):
|
|||||||
return timedelta(days=value)
|
return timedelta(days=value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def toSQL(self, kit: KitType) -> ReagentType:
|
def toSQL(self, kit: KitType) -> ReagentRole:
|
||||||
"""
|
"""
|
||||||
Converts this instance into a backend.db.models.ReagentType instance
|
Converts this instance into a backend.db.models.ReagentType instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
kit (KitType): KitType joined to the reagenttype
|
kit (KitType): KitType joined to the reagentrole
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ReagentType: ReagentType instance
|
ReagentRole: ReagentType instance
|
||||||
"""
|
"""
|
||||||
instance: ReagentType = ReagentType.query(name=self.name)
|
instance: ReagentRole = ReagentRole.query(name=self.name)
|
||||||
if instance == None:
|
if instance == None:
|
||||||
instance = ReagentType(name=self.name, eol_ext=self.eol_ext)
|
instance = ReagentRole(name=self.name, eol_ext=self.eol_ext)
|
||||||
# logger.debug(f"This is the reagent type instance: {instance.__dict__}")
|
# logger.debug(f"This is the reagent type instance: {instance.__dict__}")
|
||||||
try:
|
try:
|
||||||
assoc = KitTypeReagentTypeAssociation.query(reagent_type=instance, kit_type=kit)
|
assoc = KitTypeReagentRoleAssociation.query(reagent_role=instance, kit_type=kit)
|
||||||
except StatementError:
|
except StatementError:
|
||||||
assoc = None
|
assoc = None
|
||||||
if assoc == None:
|
if assoc == None:
|
||||||
assoc = KitTypeReagentTypeAssociation(kit_type=kit, reagent_type=instance, uses=self.uses,
|
assoc = KitTypeReagentRoleAssociation(kit_type=kit, reagent_role=instance, uses=self.uses,
|
||||||
required=self.required)
|
required=self.required)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class PydKit(BaseModel):
|
class PydKit(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
reagent_types: List[PydReagentType] = []
|
reagent_roles: List[PydReagentRole] = []
|
||||||
|
|
||||||
def toSQL(self) -> Tuple[KitType, Report]:
|
def toSQL(self) -> Tuple[KitType, Report]:
|
||||||
"""
|
"""
|
||||||
@@ -902,7 +902,7 @@ class PydKit(BaseModel):
|
|||||||
instance = KitType.query(name=self.name)
|
instance = KitType.query(name=self.name)
|
||||||
if instance == None:
|
if instance == None:
|
||||||
instance = KitType(name=self.name)
|
instance = KitType(name=self.name)
|
||||||
[item.toSQL(instance) for item in self.reagent_types]
|
[item.toSQL(instance) for item in self.reagent_roles]
|
||||||
return instance, report
|
return instance, report
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from PyQt6.QtWidgets import (
|
|||||||
QSpinBox, QDateEdit
|
QSpinBox, QDateEdit
|
||||||
)
|
)
|
||||||
from sqlalchemy import FLOAT, INTEGER
|
from sqlalchemy import FLOAT, INTEGER
|
||||||
from backend.db import SubmissionTypeKitTypeAssociation, SubmissionType, ReagentType
|
from backend.db import SubmissionTypeKitTypeAssociation, SubmissionType, ReagentRole
|
||||||
from backend.validators import PydReagentType, PydKit
|
from backend.validators import PydReagentRole, PydKit
|
||||||
import logging
|
import logging
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from tools import Report
|
from tools import Report
|
||||||
@@ -81,7 +81,7 @@ class KitAdder(QWidget):
|
|||||||
"""
|
"""
|
||||||
# get bottommost row
|
# get bottommost row
|
||||||
maxrow = self.grid.rowCount()
|
maxrow = self.grid.rowCount()
|
||||||
reg_form = ReagentTypeForm(parent=self)
|
reg_form = ReagentRoleForm(parent=self)
|
||||||
reg_form.setObjectName(f"ReagentForm_{maxrow}")
|
reg_form.setObjectName(f"ReagentForm_{maxrow}")
|
||||||
self.grid.addWidget(reg_form, maxrow,0,1,4)
|
self.grid.addWidget(reg_form, maxrow,0,1,4)
|
||||||
|
|
||||||
@@ -95,11 +95,11 @@ class KitAdder(QWidget):
|
|||||||
info = {k:v for k,v in info.items() if k in [column.name for column in self.columns] + ['kit_name', 'used_for']}
|
info = {k:v for k,v in info.items() if k in [column.name for column in self.columns] + ['kit_name', 'used_for']}
|
||||||
# logger.debug(f"kit info: {pformat(info)}")
|
# logger.debug(f"kit info: {pformat(info)}")
|
||||||
# logger.debug(f"kit reagents: {pformat(reagents)}")
|
# logger.debug(f"kit reagents: {pformat(reagents)}")
|
||||||
info['reagent_types'] = reagents
|
info['reagent_roles'] = reagents
|
||||||
# logger.debug(pformat(info))
|
# logger.debug(pformat(info))
|
||||||
# send to kit constructor
|
# send to kit constructor
|
||||||
kit = PydKit(name=info['kit_name'])
|
kit = PydKit(name=info['kit_name'])
|
||||||
for reagent in info['reagent_types']:
|
for reagent in info['reagent_roles']:
|
||||||
uses = {
|
uses = {
|
||||||
info['used_for']:
|
info['used_for']:
|
||||||
{'sheet':reagent['sheet'],
|
{'sheet':reagent['sheet'],
|
||||||
@@ -107,7 +107,7 @@ class KitAdder(QWidget):
|
|||||||
'lot':reagent['lot'],
|
'lot':reagent['lot'],
|
||||||
'expiry':reagent['expiry']
|
'expiry':reagent['expiry']
|
||||||
}}
|
}}
|
||||||
kit.reagent_types.append(PydReagentType(name=reagent['rtname'], eol_ext=reagent['eol'], uses=uses))
|
kit.reagent_roles.append(PydReagentRole(name=reagent['rtname'], eol_ext=reagent['eol'], uses=uses))
|
||||||
# logger.debug(f"Output pyd object: {kit.__dict__}")
|
# logger.debug(f"Output pyd object: {kit.__dict__}")
|
||||||
sqlobj, result = kit.toSQL(self.ctx)
|
sqlobj, result = kit.toSQL(self.ctx)
|
||||||
report.add_result(result=result)
|
report.add_result(result=result)
|
||||||
@@ -125,11 +125,11 @@ class KitAdder(QWidget):
|
|||||||
# logger.debug(f"Hello from {self.__class__} parser!")
|
# logger.debug(f"Hello from {self.__class__} parser!")
|
||||||
info = {}
|
info = {}
|
||||||
reagents = []
|
reagents = []
|
||||||
widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore and not isinstance(widget.parent(), ReagentTypeForm)]
|
widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore and not isinstance(widget.parent(), ReagentRoleForm)]
|
||||||
for widget in widgets:
|
for widget in widgets:
|
||||||
# logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
|
# logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
|
||||||
match widget:
|
match widget:
|
||||||
case ReagentTypeForm():
|
case ReagentRoleForm():
|
||||||
reagents.append(widget.parse_form())
|
reagents.append(widget.parse_form())
|
||||||
case QLineEdit():
|
case QLineEdit():
|
||||||
info[widget.objectName()] = widget.text()
|
info[widget.objectName()] = widget.text()
|
||||||
@@ -139,7 +139,7 @@ class KitAdder(QWidget):
|
|||||||
info[widget.objectName()] = widget.date().toPyDate()
|
info[widget.objectName()] = widget.date().toPyDate()
|
||||||
return info, reagents
|
return info, reagents
|
||||||
|
|
||||||
class ReagentTypeForm(QWidget):
|
class ReagentRoleForm(QWidget):
|
||||||
"""
|
"""
|
||||||
custom widget to add information about a new reagenttype
|
custom widget to add information about a new reagenttype
|
||||||
"""
|
"""
|
||||||
@@ -152,13 +152,13 @@ class ReagentTypeForm(QWidget):
|
|||||||
self.reagent_getter = QComboBox()
|
self.reagent_getter = QComboBox()
|
||||||
self.reagent_getter.setObjectName("rtname")
|
self.reagent_getter.setObjectName("rtname")
|
||||||
# lookup all reagent type names from db
|
# lookup all reagent type names from db
|
||||||
lookup = ReagentType.query()
|
lookup = ReagentRole.query()
|
||||||
# logger.debug(f"Looked up ReagentType names: {lookup}")
|
# logger.debug(f"Looked up ReagentType names: {lookup}")
|
||||||
self.reagent_getter.addItems([item.name for item in lookup])
|
self.reagent_getter.addItems([item.name for item in lookup])
|
||||||
self.reagent_getter.setEditable(True)
|
self.reagent_getter.setEditable(True)
|
||||||
grid.addWidget(self.reagent_getter,0,1)
|
grid.addWidget(self.reagent_getter,0,1)
|
||||||
grid.addWidget(QLabel("Extension of Life (months):"),0,2)
|
grid.addWidget(QLabel("Extension of Life (months):"),0,2)
|
||||||
# widget to get extension of life
|
# NOTE: widget to get extension of life
|
||||||
self.eol = QSpinBox()
|
self.eol = QSpinBox()
|
||||||
self.eol.setObjectName('eol')
|
self.eol.setObjectName('eol')
|
||||||
self.eol.setMinimum(0)
|
self.eol.setMinimum(0)
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ class AddReagentForm(QDialog):
|
|||||||
"""
|
"""
|
||||||
dialog to add gather info about new reagent
|
dialog to add gather info about new reagent
|
||||||
"""
|
"""
|
||||||
def __init__(self, reagent_lot:str|None=None, reagent_type:str|None=None, expiry:date|None=None, reagent_name:str|None=None) -> None:
|
def __init__(self, reagent_lot:str|None=None, reagent_role: str | None=None, expiry: date | None=None, reagent_name: str | None=None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if reagent_lot == None:
|
if reagent_lot == None:
|
||||||
reagent_lot = reagent_type
|
reagent_lot = reagent_role
|
||||||
|
|
||||||
self.setWindowTitle("Add Reagent")
|
self.setWindowTitle("Add Reagent")
|
||||||
|
|
||||||
@@ -57,15 +57,15 @@ class AddReagentForm(QDialog):
|
|||||||
# widget to get reagent type info
|
# widget to get reagent type info
|
||||||
self.type_input = QComboBox()
|
self.type_input = QComboBox()
|
||||||
self.type_input.setObjectName('type')
|
self.type_input.setObjectName('type')
|
||||||
self.type_input.addItems([item.name for item in ReagentType.query()])
|
self.type_input.addItems([item.name for item in ReagentRole.query()])
|
||||||
# logger.debug(f"Trying to find index of {reagent_type}")
|
# logger.debug(f"Trying to find index of {reagent_type}")
|
||||||
# convert input to user friendly string?
|
# convert input to user friendly string?
|
||||||
try:
|
try:
|
||||||
reagent_type = reagent_type.replace("_", " ").title()
|
reagent_role = reagent_role.replace("_", " ").title()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
reagent_type = None
|
reagent_role = None
|
||||||
# set parsed reagent type to top of list
|
# set parsed reagent type to top of list
|
||||||
index = self.type_input.findText(reagent_type, Qt.MatchFlag.MatchEndsWith)
|
index = self.type_input.findText(reagent_role, Qt.MatchFlag.MatchEndsWith)
|
||||||
if index >= 0:
|
if index >= 0:
|
||||||
self.type_input.setCurrentIndex(index)
|
self.type_input.setCurrentIndex(index)
|
||||||
self.layout = QVBoxLayout()
|
self.layout = QVBoxLayout()
|
||||||
@@ -91,7 +91,7 @@ class AddReagentForm(QDialog):
|
|||||||
return dict(name=self.name_input.currentText().strip(),
|
return dict(name=self.name_input.currentText().strip(),
|
||||||
lot=self.lot_input.text().strip(),
|
lot=self.lot_input.text().strip(),
|
||||||
expiry=self.exp_input.date().toPyDate(),
|
expiry=self.exp_input.date().toPyDate(),
|
||||||
type=self.type_input.currentText().strip())
|
role=self.type_input.currentText().strip())
|
||||||
|
|
||||||
def update_names(self):
|
def update_names(self):
|
||||||
"""
|
"""
|
||||||
@@ -99,7 +99,7 @@ class AddReagentForm(QDialog):
|
|||||||
"""
|
"""
|
||||||
# logger.debug(self.type_input.currentText())
|
# logger.debug(self.type_input.currentText())
|
||||||
self.name_input.clear()
|
self.name_input.clear()
|
||||||
lookup = Reagent.query(reagent_type=self.type_input.currentText())
|
lookup = Reagent.query(reagent_role=self.type_input.currentText())
|
||||||
self.name_input.addItems(list(set([item.name for item in lookup])))
|
self.name_input.addItems(list(set([item.name for item in lookup])))
|
||||||
|
|
||||||
class ReportDatePicker(QDialog):
|
class ReportDatePicker(QDialog):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from backend.excel.parser import SheetParser
|
|||||||
from backend.validators import PydSubmission, PydReagent
|
from backend.validators import PydSubmission, PydReagent
|
||||||
from backend.db import (
|
from backend.db import (
|
||||||
KitType, Organization, SubmissionType, Reagent,
|
KitType, Organization, SubmissionType, Reagent,
|
||||||
ReagentType, KitTypeReagentTypeAssociation
|
ReagentRole, KitTypeReagentRoleAssociation
|
||||||
)
|
)
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from .pop_ups import QuestionAsker, AlertPop
|
from .pop_ups import QuestionAsker, AlertPop
|
||||||
@@ -112,14 +112,14 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# logger.debug(f"Outgoing report: {self.report.results}")
|
# logger.debug(f"Outgoing report: {self.report.results}")
|
||||||
# logger.debug(f"All attributes of submission container:\n{pformat(self.__dict__)}")
|
# logger.debug(f"All attributes of submission container:\n{pformat(self.__dict__)}")
|
||||||
|
|
||||||
def add_reagent(self, reagent_lot: str | None = None, reagent_type: 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,
|
||||||
name: str | None = None):
|
name: str | None = None):
|
||||||
"""
|
"""
|
||||||
Action to create new reagent in DB.
|
Action to create new reagent in DB.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reagent_lot (str | None, optional): Parsed reagent from import form. Defaults to None.
|
reagent_lot (str | None, optional): Parsed reagent from import form. Defaults to None.
|
||||||
reagent_type (str | None, optional): Parsed reagent type from import form. Defaults to None.
|
reagent_role (str | None, optional): Parsed reagent type from import form. Defaults to None.
|
||||||
expiry (date | None, optional): Parsed reagent expiry data. Defaults to None.
|
expiry (date | None, optional): Parsed reagent expiry data. Defaults to None.
|
||||||
name (str | None, optional): Parsed reagent name. Defaults to None.
|
name (str | None, optional): Parsed reagent name. Defaults to None.
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ class SubmissionFormContainer(QWidget):
|
|||||||
if isinstance(reagent_lot, bool):
|
if isinstance(reagent_lot, bool):
|
||||||
reagent_lot = ""
|
reagent_lot = ""
|
||||||
# NOTE: create form
|
# NOTE: create form
|
||||||
dlg = AddReagentForm(reagent_lot=reagent_lot, reagent_type=reagent_type, expiry=expiry, reagent_name=name)
|
dlg = AddReagentForm(reagent_lot=reagent_lot, reagent_role=reagent_role, expiry=expiry, reagent_name=name)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
# extract form info
|
# extract form info
|
||||||
info = dlg.parse_form()
|
info = dlg.parse_form()
|
||||||
@@ -581,13 +581,13 @@ class SubmissionFormWidget(QWidget):
|
|||||||
"""
|
"""
|
||||||
lot = self.lot.currentText()
|
lot = self.lot.currentText()
|
||||||
# logger.debug(f"Using this lot for the reagent {self.reagent}: {lot}")
|
# logger.debug(f"Using this lot for the reagent {self.reagent}: {lot}")
|
||||||
wanted_reagent = Reagent.query(lot_number=lot, reagent_type=self.reagent.type)
|
wanted_reagent = Reagent.query(lot_number=lot, reagent_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 == None:
|
if wanted_reagent == None:
|
||||||
dlg = QuestionAsker(title=f"Add {lot}?",
|
dlg = QuestionAsker(title=f"Add {lot}?",
|
||||||
message=f"Couldn't find reagent type {self.reagent.type}: {lot} in the database.\n\nWould you like to add it?")
|
message=f"Couldn't find reagent type {self.reagent.type}: {lot} in the database.\n\nWould you like to add it?")
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot, reagent_type=self.reagent.type,
|
wanted_reagent = self.parent().parent().add_reagent(reagent_lot=lot, reagent_role=self.reagent.role,
|
||||||
expiry=self.reagent.expiry,
|
expiry=self.reagent.expiry,
|
||||||
name=self.reagent.name)
|
name=self.reagent.name)
|
||||||
return wanted_reagent, None
|
return wanted_reagent, None
|
||||||
@@ -598,10 +598,10 @@ class SubmissionFormWidget(QWidget):
|
|||||||
else:
|
else:
|
||||||
# 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 = ReagentType.query(name=self.reagent.type)
|
rt = ReagentRole.query(name=self.reagent.role)
|
||||||
if rt == None:
|
if rt == None:
|
||||||
rt = ReagentType.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, type=rt.name,
|
return PydReagent(name=wanted_reagent.name, lot=wanted_reagent.lot, role=rt.name,
|
||||||
expiry=wanted_reagent.expiry, missing=False), None
|
expiry=wanted_reagent.expiry, missing=False), None
|
||||||
|
|
||||||
def updated(self):
|
def updated(self):
|
||||||
@@ -619,20 +619,20 @@ class SubmissionFormWidget(QWidget):
|
|||||||
check = not reagent.missing
|
check = not reagent.missing
|
||||||
except:
|
except:
|
||||||
check = False
|
check = False
|
||||||
self.setObjectName(f"{reagent.type}_label")
|
self.setObjectName(f"{reagent.role}_label")
|
||||||
if check:
|
if check:
|
||||||
self.setText(f"Parsed {reagent.type}")
|
self.setText(f"Parsed {reagent.role}")
|
||||||
else:
|
else:
|
||||||
self.setText(f"MISSING {reagent.type}")
|
self.setText(f"MISSING {reagent.role}")
|
||||||
|
|
||||||
def updated(self, reagent_type: str):
|
def updated(self, reagent_role: str):
|
||||||
"""
|
"""
|
||||||
Marks widget as updated
|
Marks widget as updated
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reagent_type (str): _description_
|
reagent_role (str): _description_
|
||||||
"""
|
"""
|
||||||
self.setText(f"UPDATED {reagent_type}")
|
self.setText(f"UPDATED {reagent_role}")
|
||||||
|
|
||||||
class ReagentLot(QComboBox):
|
class ReagentLot(QComboBox):
|
||||||
|
|
||||||
@@ -641,7 +641,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
self.setEditable(True)
|
self.setEditable(True)
|
||||||
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
|
# logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
|
||||||
# NOTE: below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
|
# NOTE: below was lookup_reagent_by_type_name_and_kit_name, but I couldn't get it to work.
|
||||||
lookup = Reagent.query(reagent_type=reagent.type)
|
lookup = Reagent.query(reagent_role=reagent.role)
|
||||||
relevant_reagents = [str(item.lot) for item in lookup]
|
relevant_reagents = [str(item.lot) for item in lookup]
|
||||||
output_reg = []
|
output_reg = []
|
||||||
for rel_reagent in relevant_reagents:
|
for rel_reagent in relevant_reagents:
|
||||||
@@ -658,7 +658,7 @@ class SubmissionFormWidget(QWidget):
|
|||||||
if check_not_nan(reagent.lot):
|
if check_not_nan(reagent.lot):
|
||||||
relevant_reagents.insert(0, str(reagent.lot))
|
relevant_reagents.insert(0, str(reagent.lot))
|
||||||
else:
|
else:
|
||||||
looked_up_rt = KitTypeReagentTypeAssociation.query(reagent_type=reagent.type,
|
looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role,
|
||||||
kit_type=extraction_kit)
|
kit_type=extraction_kit)
|
||||||
try:
|
try:
|
||||||
looked_up_reg = Reagent.query(lot_number=looked_up_rt.last_used)
|
looked_up_reg = Reagent.query(lot_number=looked_up_rt.last_used)
|
||||||
@@ -684,5 +684,5 @@ class SubmissionFormWidget(QWidget):
|
|||||||
# logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. But no need to move due to short list.")
|
# logger.debug(f"Found {reagent.lot} in relevant reagents: {relevant_reagents}. But no need to move due to short list.")
|
||||||
pass
|
pass
|
||||||
# logger.debug(f"New relevant reagents: {relevant_reagents}")
|
# logger.debug(f"New relevant reagents: {relevant_reagents}")
|
||||||
self.setObjectName(f"lot_{reagent.type}")
|
self.setObjectName(f"lot_{reagent.role}")
|
||||||
self.addItems(relevant_reagents)
|
self.addItems(relevant_reagents)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
<h3><u>Reagents:</u></h3>
|
<h3><u>Reagents:</u></h3>
|
||||||
<p>{% for item in sub['reagents'] %}
|
<p>{% for item in sub['reagents'] %}
|
||||||
<b>{{ item['type'] }}</b>: {{ item['lot'] }} (EXP: {{ item['expiry'] }})<br>
|
<b>{{ item['role'] }}</b>: {{ item['lot'] }} (EXP: {{ item['expiry'] }})<br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% if sub['equipment'] %}
|
{% if sub['equipment'] %}
|
||||||
<h3><u>Equipment:</u></h3>
|
<h3><u>Equipment:</u></h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user