Updates to details interface.
This commit is contained in:
@@ -342,6 +342,18 @@ class BaseClass(Base):
|
|||||||
Add the object to the database and commit
|
Add the object to the database and commit
|
||||||
"""
|
"""
|
||||||
report = Report()
|
report = Report()
|
||||||
|
del_keys = []
|
||||||
|
try:
|
||||||
|
items = self._misc_info.items()
|
||||||
|
except AttributeError:
|
||||||
|
items = []
|
||||||
|
for key, value in items:
|
||||||
|
try:
|
||||||
|
json.dumps(value)
|
||||||
|
except TypeError as e:
|
||||||
|
del_keys.append(key)
|
||||||
|
for dk in del_keys:
|
||||||
|
del self._misc_info[dk]
|
||||||
try:
|
try:
|
||||||
self.__database_session__.add(self)
|
self.__database_session__.add(self)
|
||||||
self.__database_session__.commit()
|
self.__database_session__.commit()
|
||||||
@@ -566,7 +578,7 @@ class BaseClass(Base):
|
|||||||
else:
|
else:
|
||||||
return super().__setattr__(key, value)
|
return super().__setattr__(key, value)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self, **kwargs):
|
||||||
logger.error(f"Delete has not been implemented for {self.__class__.__name__}")
|
logger.error(f"Delete has not been implemented for {self.__class__.__name__}")
|
||||||
|
|
||||||
def rectify_query_date(input_date: datetime, eod: bool = False) -> str:
|
def rectify_query_date(input_date: datetime, eod: bool = False) -> str:
|
||||||
@@ -600,6 +612,7 @@ class BaseClass(Base):
|
|||||||
relevant = {k: v for k, v in self.__class__.__dict__.items() if
|
relevant = {k: v for k, v in self.__class__.__dict__.items() if
|
||||||
isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)}
|
isinstance(v, InstrumentedAttribute) or isinstance(v, AssociationProxy)}
|
||||||
output = {}
|
output = {}
|
||||||
|
output['excluded'] = ["excluded", "misc_info", "_misc_info", "id"]
|
||||||
for k, v in relevant.items():
|
for k, v in relevant.items():
|
||||||
try:
|
try:
|
||||||
check = v.foreign_keys
|
check = v.foreign_keys
|
||||||
@@ -611,14 +624,55 @@ class BaseClass(Base):
|
|||||||
value = getattr(self, k)
|
value = getattr(self, k)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
|
# match value:
|
||||||
|
# case datetime():
|
||||||
|
# value = value.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
# case bytes():
|
||||||
|
# continue
|
||||||
|
# case dict():
|
||||||
|
# try:
|
||||||
|
# value = value['name']
|
||||||
|
# except KeyError:
|
||||||
|
# if k == "_misc_info":
|
||||||
|
# value = value
|
||||||
|
# else:
|
||||||
|
# continue
|
||||||
|
# case _:
|
||||||
|
# pass
|
||||||
|
output[k.strip("_")] = value
|
||||||
|
# output = self.clean_details_dict(output)
|
||||||
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_details_dict(cls, dictionary):
|
||||||
|
output = {}
|
||||||
|
for k, value in dictionary.items():
|
||||||
match value:
|
match value:
|
||||||
case datetime():
|
case datetime() | date():
|
||||||
value = value.strftime("%Y-%m-%d %H:%M:%S")
|
value = value.strftime("%Y-%m-%d")
|
||||||
|
case bytes():
|
||||||
|
continue
|
||||||
|
case dict():
|
||||||
|
try:
|
||||||
|
value = value['name']
|
||||||
|
except KeyError:
|
||||||
|
if k == "_misc_info" or k == "misc_info":
|
||||||
|
value = value
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
case x if issubclass(value.__class__, cls):
|
||||||
|
try:
|
||||||
|
value = value.name
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
output[k.strip("_")] = value
|
output[k] = value
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def to_pydantic(self, pyd_model_name:str|None=None, **kwargs):
|
def to_pydantic(self, pyd_model_name:str|None=None, **kwargs):
|
||||||
from backend.validators import pydant
|
from backend.validators import pydant
|
||||||
if not pyd_model_name:
|
if not pyd_model_name:
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ from pandas import ExcelFile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from . import Base, BaseClass, ClientLab, LogMixin
|
from . import Base, BaseClass, ClientLab, LogMixin
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from sqlalchemy.exc import OperationalError as AlcOperationalError, IntegrityError as AlcIntegrityError, StatementError, \
|
||||||
|
ArgumentError
|
||||||
|
from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as SQLIntegrityError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from backend.db.models.submissions import Run, ProcedureSampleAssociation
|
from backend.db.models.submissions import Run, ProcedureSampleAssociation
|
||||||
@@ -804,6 +807,8 @@ class Reagent(BaseClass, LogMixin):
|
|||||||
output = super().details_dict()
|
output = super().details_dict()
|
||||||
if reagentrole:
|
if reagentrole:
|
||||||
output['reagentrole'] = reagentrole
|
output['reagentrole'] = reagentrole
|
||||||
|
else:
|
||||||
|
output['reagentrole'] = self.reagentrole[0].name
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
@@ -1358,6 +1363,8 @@ class Procedure(BaseClass):
|
|||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
name = Column(String, unique=True)
|
name = Column(String, unique=True)
|
||||||
repeat = Column(INTEGER, nullable=False)
|
repeat = Column(INTEGER, nullable=False)
|
||||||
|
started_date = Column(TIMESTAMP)
|
||||||
|
completed_date = Column(TIMESTAMP)
|
||||||
|
|
||||||
technician = Column(String(64)) #: name of processing tech(s)
|
technician = Column(String(64)) #: name of processing tech(s)
|
||||||
results = relationship("Results", back_populates="procedure", uselist=True)
|
results = relationship("Results", back_populates="procedure", uselist=True)
|
||||||
@@ -1510,7 +1517,8 @@ class Procedure(BaseClass):
|
|||||||
def details_dict(self, **kwargs):
|
def details_dict(self, **kwargs):
|
||||||
output = super().details_dict()
|
output = super().details_dict()
|
||||||
output['kittype'] = output['kittype'].details_dict()
|
output['kittype'] = output['kittype'].details_dict()
|
||||||
output['proceduretype'] = output['proceduretype'].details_dict()
|
output['kit_type'] = self.kittype.name
|
||||||
|
output['proceduretype'] = output['proceduretype'].details_dict()['name']
|
||||||
output['results'] = [result.details_dict() for result in output['results']]
|
output['results'] = [result.details_dict() for result in output['results']]
|
||||||
run_samples = [sample for sample in self.run.sample]
|
run_samples = [sample for sample in self.run.sample]
|
||||||
active_samples = [sample.details_dict() for sample in output['proceduresampleassociation']
|
active_samples = [sample.details_dict() for sample in output['proceduresampleassociation']
|
||||||
@@ -1530,10 +1538,11 @@ class Procedure(BaseClass):
|
|||||||
output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']]
|
output['equipment'] = [equipment.details_dict() for equipment in output['procedureequipmentassociation']]
|
||||||
output['tips'] = [tips.details_dict() for tips in output['proceduretipsassociation']]
|
output['tips'] = [tips.details_dict() for tips in output['proceduretipsassociation']]
|
||||||
output['repeat'] = bool(output['repeat'])
|
output['repeat'] = bool(output['repeat'])
|
||||||
output['excluded'] = ['id', "results", "proceduresampleassociation", "sample", "procedurereagentassociation",
|
output['run'] = self.run.name
|
||||||
|
output['excluded'] += ['id', "results", "proceduresampleassociation", "sample", "procedurereagentassociation",
|
||||||
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
"procedureequipmentassociation", "proceduretipsassociation", "reagent", "equipment",
|
||||||
"tips",
|
"tips", "control", "kittype"]
|
||||||
"excluded"]
|
# output = self.clean_details_dict(output)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_pydantic(self, **kwargs):
|
def to_pydantic(self, **kwargs):
|
||||||
@@ -1546,7 +1555,7 @@ class Procedure(BaseClass):
|
|||||||
for reagent in output.reagent:
|
for reagent in output.reagent:
|
||||||
match reagent:
|
match reagent:
|
||||||
case dict():
|
case dict():
|
||||||
reagent['reagentrole'] = next((reagentrole.name for reagentrole in self.kittype.reagentrole if reagentrole in reagent['reagentrole']), None)
|
reagent['reagentrole'] = next((reagentrole.name for reagentrole in self.kittype.reagentrole if reagentrole == reagent['reagentrole']), None)
|
||||||
reagents.append(PydReagent(**reagent))
|
reagents.append(PydReagent(**reagent))
|
||||||
case PydReagent():
|
case PydReagent():
|
||||||
reagents.append(reagent)
|
reagents.append(reagent)
|
||||||
@@ -1942,6 +1951,7 @@ class ProcedureReagentAssociation(BaseClass):
|
|||||||
|
|
||||||
reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent
|
reagent_id = Column(INTEGER, ForeignKey("_reagent.id"), primary_key=True) #: id of associated reagent
|
||||||
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||||
|
reagentrole = Column(String(64))
|
||||||
comments = Column(String(1024)) #: Comments about reagents
|
comments = Column(String(1024)) #: Comments about reagents
|
||||||
|
|
||||||
procedure = relationship("Procedure",
|
procedure = relationship("Procedure",
|
||||||
@@ -1960,12 +1970,13 @@ class ProcedureReagentAssociation(BaseClass):
|
|||||||
logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!")
|
logger.error(f"Reagent {self.reagent.lot} procedure association {self.reagent_id} has no procedure!")
|
||||||
return f"<ProcedureReagentAssociation(Unknown Submission & {self.reagent.lot})>"
|
return f"<ProcedureReagentAssociation(Unknown Submission & {self.reagent.lot})>"
|
||||||
|
|
||||||
def __init__(self, reagent=None, procedure=None):
|
def __init__(self, reagent=None, procedure=None, reagentrole=""):
|
||||||
if isinstance(reagent, list):
|
if isinstance(reagent, list):
|
||||||
logger.warning(f"Got list for reagent. Likely no lot was provided. Using {reagent[0]}")
|
logger.warning(f"Got list for reagent. Likely no lot was provided. Using {reagent[0]}")
|
||||||
reagent = reagent[0]
|
reagent = reagent[0]
|
||||||
self.reagent = reagent
|
self.reagent = reagent
|
||||||
self.procedure = procedure
|
self.procedure = procedure
|
||||||
|
self.reagentrole = reagentrole
|
||||||
self.comments = ""
|
self.comments = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1973,6 +1984,7 @@ class ProcedureReagentAssociation(BaseClass):
|
|||||||
def query(cls,
|
def query(cls,
|
||||||
procedure: Procedure | str | int | None = None,
|
procedure: Procedure | str | int | None = None,
|
||||||
reagent: Reagent | str | None = None,
|
reagent: Reagent | str | None = None,
|
||||||
|
reagentrole: str | None = None,
|
||||||
limit: int = 0) -> ProcedureReagentAssociation | List[ProcedureReagentAssociation]:
|
limit: int = 0) -> ProcedureReagentAssociation | List[ProcedureReagentAssociation]:
|
||||||
"""
|
"""
|
||||||
Lookup SubmissionReagentAssociations of interest.
|
Lookup SubmissionReagentAssociations of interest.
|
||||||
@@ -2003,6 +2015,8 @@ class ProcedureReagentAssociation(BaseClass):
|
|||||||
query = query.join(Procedure).filter(Procedure.id == procedure)
|
query = query.join(Procedure).filter(Procedure.id == procedure)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
if reagentrole:
|
||||||
|
query = query.filter(cls.reagentrole==reagentrole)
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
def to_sub_dict(self, kittype) -> dict:
|
def to_sub_dict(self, kittype) -> dict:
|
||||||
@@ -2034,6 +2048,14 @@ class ProcedureReagentAssociation(BaseClass):
|
|||||||
# output['results'] = [result.details_dict() for result in output['results']]
|
# output['results'] = [result.details_dict() for result in output['results']]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def delete(self, **kwargs):
|
||||||
|
self.__database_session__.delete(self)
|
||||||
|
try:
|
||||||
|
self.__database_session__.commit()
|
||||||
|
except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e:
|
||||||
|
self.__database_session__.rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
class Equipment(BaseClass, LogMixin):
|
class Equipment(BaseClass, LogMixin):
|
||||||
"""
|
"""
|
||||||
@@ -2547,6 +2569,7 @@ class ProcedureEquipmentAssociation(BaseClass):
|
|||||||
misc = output['misc_info']
|
misc = output['misc_info']
|
||||||
output.update(relevant)
|
output.update(relevant)
|
||||||
output['misc_info'] = misc
|
output['misc_info'] = misc
|
||||||
|
output['equipment_role'] = self.equipmentrole
|
||||||
output['process'] = self.process.details_dict()
|
output['process'] = self.process.details_dict()
|
||||||
try:
|
try:
|
||||||
output['tips'] = self.tips.details_dict()
|
output['tips'] = self.tips.details_dict()
|
||||||
@@ -2752,7 +2775,7 @@ class Process(BaseClass):
|
|||||||
output = {}
|
output = {}
|
||||||
for k, v in self.details_dict().items():
|
for k, v in self.details_dict().items():
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
output[k] = [item.name for item in v]
|
output[k] = [item.name if issubclass(item.__class__, BaseClass) else item for item in v]
|
||||||
elif issubclass(v.__class__, BaseClass):
|
elif issubclass(v.__class__, BaseClass):
|
||||||
output[k] = v.name
|
output[k] = v.name
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
except AssertionError:
|
except AssertionError:
|
||||||
logger.warning(f"Converting {sample} to sql.")
|
logger.warning(f"Converting {sample} to sql.")
|
||||||
sample = sample.to_sql()
|
sample = sample.to_sql()
|
||||||
logger.debug(sample.__dict__)
|
# logger.debug(sample.__dict__)
|
||||||
try:
|
try:
|
||||||
row = sample._misc_info['row']
|
row = sample._misc_info['row']
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
@@ -297,8 +297,10 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
column = sample._misc_info['column']
|
column = sample._misc_info['column']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
column = 0
|
column = 0
|
||||||
logger.debug(f"Sample: {sample}")
|
# logger.debug(f"Sample: {sample}")
|
||||||
submission_rank = sample._misc_info['submission_rank']
|
submission_rank = sample._misc_info['submission_rank']
|
||||||
|
if sample in self.sample:
|
||||||
|
return
|
||||||
assoc = ClientSubmissionSampleAssociation(
|
assoc = ClientSubmissionSampleAssociation(
|
||||||
sample=sample,
|
sample=sample,
|
||||||
submission=self,
|
submission=self,
|
||||||
@@ -362,9 +364,11 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
output['name'] = self.name
|
output['name'] = self.name
|
||||||
output['client_lab'] = output['clientlab']
|
output['client_lab'] = output['clientlab']
|
||||||
output['submission_type'] = output['submissiontype']
|
output['submission_type'] = output['submissiontype']
|
||||||
output['excluded'] = ['run', "sample", "clientsubmissionsampleassociation", "excluded",
|
output['excluded'] += ['run', "sample", "clientsubmissionsampleassociation", "excluded",
|
||||||
"expanded", 'clientlab', 'submissiontype', 'id']
|
"expanded", 'clientlab', 'submissiontype', 'id']
|
||||||
output['expanded'] = ["clientlab", "contact", "submissiontype"]
|
output['expanded'] = ["clientlab", "contact", "submissiontype"]
|
||||||
|
# output = self.clean_details_dict(output)
|
||||||
|
logger.debug(f"{self.__class__.__name__}\n\n{pformat(output)}")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_pydantic(self, filepath: Path | str | None = None, **kwargs):
|
def to_pydantic(self, filepath: Path | str | None = None, **kwargs):
|
||||||
@@ -383,14 +387,14 @@ class Run(BaseClass, LogMixin):
|
|||||||
clientsubmission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL",
|
clientsubmission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL",
|
||||||
name="fk_BS_clientsub_id")) #: client lab id from _organizations)
|
name="fk_BS_clientsub_id")) #: client lab id from _organizations)
|
||||||
clientsubmission = relationship("ClientSubmission", back_populates="run")
|
clientsubmission = relationship("ClientSubmission", back_populates="run")
|
||||||
started_date = Column(TIMESTAMP) #: Date this procedure was started.
|
_started_date = Column(TIMESTAMP) #: Date this procedure was started.
|
||||||
run_cost = Column(
|
run_cost = Column(
|
||||||
FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kittype costs at time of creation.
|
FLOAT(2)) #: total cost of running the plate. Set from constant and mutable kittype costs at time of creation.
|
||||||
signed_by = Column(String(32)) #: user name of person who submitted the procedure to the database.
|
signed_by = Column(String(32)) #: user name of person who submitted the procedure to the database.
|
||||||
comment = Column(JSON) #: user notes
|
comment = Column(JSON) #: user notes
|
||||||
custom = Column(JSON)
|
custom = Column(JSON)
|
||||||
|
|
||||||
completed_date = Column(TIMESTAMP)
|
_completed_date = Column(TIMESTAMP)
|
||||||
|
|
||||||
procedure = relationship("Procedure", back_populates="run", uselist=True)
|
procedure = relationship("Procedure", back_populates="run", uselist=True)
|
||||||
|
|
||||||
@@ -429,6 +433,38 @@ class Run(BaseClass, LogMixin):
|
|||||||
def plate_number(self):
|
def plate_number(self):
|
||||||
return self.rsl_plate_number
|
return self.rsl_plate_number
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def started_date(self):
|
||||||
|
if self._started_date:
|
||||||
|
return self._started_date
|
||||||
|
else:
|
||||||
|
value = min([proc.started_date for proc in self.procedure])
|
||||||
|
return value
|
||||||
|
|
||||||
|
@started_date.setter
|
||||||
|
def started_date(self, value):
|
||||||
|
if value:
|
||||||
|
self._started_date = value
|
||||||
|
else:
|
||||||
|
self._started_date = min([proc.started_date for proc in self.procedure])
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def completed_date(self):
|
||||||
|
if not self.signed_by:
|
||||||
|
return None
|
||||||
|
if self._completed_date:
|
||||||
|
return self._completed_date
|
||||||
|
else:
|
||||||
|
value = max([proc.completed_date for proc in self.procedure])
|
||||||
|
return value
|
||||||
|
|
||||||
|
@completed_date.setter
|
||||||
|
def completed_date(self, value):
|
||||||
|
if value:
|
||||||
|
self._completed_date = value
|
||||||
|
else:
|
||||||
|
self._completed_date = min([proc.started_date for proc in self.procedure])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_info(cls, *args, submissiontype: SubmissionType | None = None) -> dict:
|
def get_default_info(cls, *args, submissiontype: SubmissionType | None = None) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -635,6 +671,8 @@ class Run(BaseClass, LogMixin):
|
|||||||
def sample_count(self):
|
def sample_count(self):
|
||||||
return len(self.sample)
|
return len(self.sample)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def details_dict(self, **kwargs):
|
def details_dict(self, **kwargs):
|
||||||
output = super().details_dict()
|
output = super().details_dict()
|
||||||
output['plate_number'] = self.plate_number
|
output['plate_number'] = self.plate_number
|
||||||
@@ -654,9 +692,12 @@ class Run(BaseClass, LogMixin):
|
|||||||
output['sample'] = active_samples + inactive_samples
|
output['sample'] = active_samples + inactive_samples
|
||||||
output['procedure'] = [procedure.details_dict() for procedure in output['procedure']]
|
output['procedure'] = [procedure.details_dict() for procedure in output['procedure']]
|
||||||
output['permission'] = is_power_user()
|
output['permission'] = is_power_user()
|
||||||
output['excluded'] = ['procedure', "runsampleassociation", 'excluded', 'expanded', 'sample', 'id', 'custom',
|
output['excluded'] += ['procedure', "runsampleassociation", 'excluded', 'expanded', 'sample', 'id', 'custom',
|
||||||
'permission']
|
'permission', "clientsubmission"]
|
||||||
output['sample_count'] = self.sample_count
|
output['sample_count'] = self.sample_count
|
||||||
|
output['client_submission'] = self.clientsubmission.name
|
||||||
|
output['started_date'] = self.started_date
|
||||||
|
output['completed_date'] = self.completed_date
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1715,6 +1756,8 @@ class ClientSubmissionSampleAssociation(BaseClass):
|
|||||||
output['misc_info'] = misc
|
output['misc_info'] = misc
|
||||||
# output['sample'] = temp
|
# output['sample'] = temp
|
||||||
# output.update(output['sample'].details_dict())
|
# output.update(output['sample'].details_dict())
|
||||||
|
|
||||||
|
# sys.exit()
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def to_pydantic(self) -> "PydSample":
|
def to_pydantic(self) -> "PydSample":
|
||||||
@@ -2132,6 +2175,7 @@ class RunSampleAssociation(BaseClass):
|
|||||||
# logger.debug(f"Output from sample: {pformat(output)}")
|
# logger.debug(f"Output from sample: {pformat(output)}")
|
||||||
output.update(relevant)
|
output.update(relevant)
|
||||||
output['misc_info'] = misc
|
output['misc_info'] = misc
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1500,7 +1500,7 @@ class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True):
|
|||||||
|
|
||||||
class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
||||||
proceduretype: ProcedureType | None = Field(default=None)
|
proceduretype: ProcedureType | None = Field(default=None)
|
||||||
run: Run | None = Field(default=None)
|
run: Run | str | None = Field(default=None)
|
||||||
name: dict = Field(default=dict(value="NA", missing=True), validate_default=True)
|
name: dict = Field(default=dict(value="NA", missing=True), validate_default=True)
|
||||||
technician: dict = Field(default=dict(value="NA", missing=True))
|
technician: dict = Field(default=dict(value="NA", missing=True))
|
||||||
repeat: bool = Field(default=False)
|
repeat: bool = Field(default=False)
|
||||||
@@ -1590,6 +1590,13 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
value = {item.name: item.reagent for item in kittype.reagentrole}
|
value = {item.name: item.reagent for item in kittype.reagentrole}
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@field_validator("run")
|
||||||
|
@classmethod
|
||||||
|
def lookup_run(cls, value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = Run.query(name=value)
|
||||||
|
return value
|
||||||
|
|
||||||
def update_kittype_reagentroles(self, kittype: str | KitType):
|
def update_kittype_reagentroles(self, kittype: str | KitType):
|
||||||
if kittype == self.__class__.model_fields['kittype'].default['value']:
|
if kittype == self.__class__.model_fields['kittype'].default['value']:
|
||||||
return
|
return
|
||||||
@@ -1701,13 +1708,24 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
# NOTE: reset reagent associations.
|
# NOTE: reset reagent associations.
|
||||||
sql.procedurereagentassociation = []
|
sql.procedurereagentassociation = []
|
||||||
for reagent in self.reagent:
|
for reagent in self.reagent:
|
||||||
logger.debug(reagent)
|
|
||||||
if isinstance(reagent, dict):
|
if isinstance(reagent, dict):
|
||||||
reagent = PydReagent(**reagent)
|
reagent = PydReagent(**reagent)
|
||||||
|
logger.debug(reagent)
|
||||||
|
reagentrole = reagent.reagentrole
|
||||||
reagent = reagent.to_sql()
|
reagent = reagent.to_sql()
|
||||||
|
logger.debug(reagentrole)
|
||||||
if reagent not in sql.reagent:
|
if reagent not in sql.reagent:
|
||||||
|
# NOTE: Remove any previous association for this role.
|
||||||
|
removable = ProcedureReagentAssociation.query(procedure=sql, reagentrole=reagentrole)
|
||||||
|
logger.debug(f"Removable: {removable}")
|
||||||
|
if removable:
|
||||||
|
if isinstance(removable, list):
|
||||||
|
for r in removable:
|
||||||
|
r.delete()
|
||||||
|
else:
|
||||||
|
removable.delete()
|
||||||
logger.debug(f"Adding {reagent} to {sql}")
|
logger.debug(f"Adding {reagent} to {sql}")
|
||||||
reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql)
|
reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql, reagentrole=reagentrole)
|
||||||
try:
|
try:
|
||||||
start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1
|
start_index = max([item.id for item in ProcedureSampleAssociation.query()]) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -1733,7 +1751,11 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
kittype = KitType.query(name=self.kittype['value'], limit=1)
|
kittype = KitType.query(name=self.kittype['value'], limit=1)
|
||||||
if kittype:
|
if kittype:
|
||||||
sql.kittype = kittype
|
sql.kittype = kittype
|
||||||
logger.debug(self.reagent)
|
# logger.debug(self.reagent)
|
||||||
|
# for reagent in self.reagent:
|
||||||
|
# reagent = reagent.to_sql()
|
||||||
|
# if reagent not in sql.reagent:
|
||||||
|
# reagent_assoc = ProcedureReagentAssociation(reagent=reagent, procedure=sql)
|
||||||
for equipment in self.equipment:
|
for equipment in self.equipment:
|
||||||
equip = Equipment.query(name=equipment.name)
|
equip = Equipment.query(name=equipment.name)
|
||||||
if equip not in sql.equipment:
|
if equip not in sql.equipment:
|
||||||
@@ -1742,6 +1764,7 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
process = equipment.process.to_sql()
|
process = equipment.process.to_sql()
|
||||||
equip_assoc.process = process
|
equip_assoc.process = process
|
||||||
# logger.debug(f"Output sql: {[pformat(item.__dict__) for item in sql.procedureequipmentassociation]}")
|
# logger.debug(f"Output sql: {[pformat(item.__dict__) for item in sql.procedureequipmentassociation]}")
|
||||||
|
logger.debug(pformat(sql.__dict__))
|
||||||
return sql, None
|
return sql, None
|
||||||
|
|
||||||
|
|
||||||
@@ -1854,20 +1877,29 @@ class PydClientSubmission(PydBaseClass):
|
|||||||
"""
|
"""
|
||||||
from frontend.widgets.submission_widget import ClientSubmissionFormWidget
|
from frontend.widgets.submission_widget import ClientSubmissionFormWidget
|
||||||
if not samples:
|
if not samples:
|
||||||
samples = self.samples
|
samples = self.sample
|
||||||
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
|
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
|
||||||
|
|
||||||
def to_sql(self):
|
def to_sql(self):
|
||||||
sql = super().to_sql()
|
sql = super().to_sql()
|
||||||
|
assert not any([isinstance(item, PydSample) for item in sql.sample])
|
||||||
|
sql.sample = []
|
||||||
if "info_placement" not in sql._misc_info:
|
if "info_placement" not in sql._misc_info:
|
||||||
sql._misc_info['info_placement'] = []
|
sql._misc_info['info_placement'] = []
|
||||||
info_placement = []
|
info_placement = []
|
||||||
for k in list(self.model_fields.keys()) + list(self.model_extra.keys()):
|
for k in list(self.model_fields.keys()) + list(self.model_extra.keys()):
|
||||||
|
logger.debug(f"Running {k}")
|
||||||
attribute = getattr(self, k)
|
attribute = getattr(self, k)
|
||||||
match k:
|
match k:
|
||||||
case "filepath":
|
case "filepath":
|
||||||
sql._misc_info[k] = attribute.__str__()
|
sql._misc_info[k] = attribute.__str__()
|
||||||
continue
|
continue
|
||||||
|
# case "sample":
|
||||||
|
# sample = [item.to_sql() for item in self.sample]
|
||||||
|
# logger.debug(sample)
|
||||||
|
# for s in sample:
|
||||||
|
# logger.debug(f"adding {s}")
|
||||||
|
# sql.add_sample(sample=s)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
logger.debug(f"Setting {k} to {attribute}")
|
logger.debug(f"Setting {k} to {attribute}")
|
||||||
@@ -1876,7 +1908,7 @@ class PydClientSubmission(PydBaseClass):
|
|||||||
info_placement.append(dict(name=k, location=attribute['location']))
|
info_placement.append(dict(name=k, location=attribute['location']))
|
||||||
else:
|
else:
|
||||||
info_placement.append(dict(name=k, location=None))
|
info_placement.append(dict(name=k, location=None))
|
||||||
max_row = max([value['location']['row'] for value in info_placement if value])
|
# max_row = max([value['location']['row'] for value in info_placement if value])
|
||||||
sql._misc_info['info_placement'] = info_placement
|
sql._misc_info['info_placement'] = info_placement
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class ProcedureCreation(QDialog):
|
|||||||
equipmentrole['equipment'].insert(0, equipmentrole['equipment'].pop(
|
equipmentrole['equipment'].insert(0, equipmentrole['equipment'].pop(
|
||||||
equipmentrole['equipment'].index(item_in_er_list)))
|
equipmentrole['equipment'].index(item_in_er_list)))
|
||||||
proceduretype_dict['equipment_section'] = EquipmentUsage.construct_html(procedure=self.procedure, child=True)
|
proceduretype_dict['equipment_section'] = EquipmentUsage.construct_html(procedure=self.procedure, child=True)
|
||||||
|
self.update_equipment = EquipmentUsage.update_equipment
|
||||||
html = render_details_template(
|
html = render_details_template(
|
||||||
template_name="procedure_creation",
|
template_name="procedure_creation",
|
||||||
# css_in=['new_context_menu'],
|
# css_in=['new_context_menu'],
|
||||||
@@ -94,6 +95,30 @@ class ProcedureCreation(QDialog):
|
|||||||
f.write(html)
|
f.write(html)
|
||||||
self.webview.setHtml(html)
|
self.webview.setHtml(html)
|
||||||
|
|
||||||
|
@pyqtSlot(str, str, str, str)
|
||||||
|
def update_equipment(self, equipmentrole: str, equipment: str, process: str, tips: str):
|
||||||
|
from backend.db.models import Equipment
|
||||||
|
logger.debug("Updating equipment")
|
||||||
|
try:
|
||||||
|
equipment_of_interest = next(
|
||||||
|
(item for item in self.procedure.equipment if item.equipmentrole == equipmentrole))
|
||||||
|
except StopIteration:
|
||||||
|
equipment_of_interest = None
|
||||||
|
equipment = Equipment.query(name=equipment)
|
||||||
|
if equipment_of_interest:
|
||||||
|
eoi = self.procedure.equipment.pop(self.procedure.equipment.index(equipment_of_interest))
|
||||||
|
else:
|
||||||
|
eoi = equipment.to_pydantic(proceduretype=self.procedure.proceduretype)
|
||||||
|
eoi.name = equipment.name
|
||||||
|
eoi.asset_number = equipment.asset_number
|
||||||
|
eoi.nickname = equipment.nickname
|
||||||
|
process = next((prcss for prcss in equipment.process if prcss.name == process))
|
||||||
|
eoi.process = process.to_pydantic()
|
||||||
|
tips = next((tps for tps in equipment.tips if tps.name == tips))
|
||||||
|
eoi.tips = tips.to_pydantic()
|
||||||
|
self.procedure.equipment.append(eoi)
|
||||||
|
logger.debug(f"Updated equipment: {self.procedure.equipment}")
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def text_changed(self, key: str, new_value: str):
|
def text_changed(self, key: str, new_value: str):
|
||||||
logger.debug(f"New value for {key}: {new_value}")
|
logger.debug(f"New value for {key}: {new_value}")
|
||||||
|
|||||||
@@ -63,17 +63,19 @@ class SubmissionDetails(QDialog):
|
|||||||
self.webview.page().setWebChannel(self.channel)
|
self.webview.page().setWebChannel(self.channel)
|
||||||
|
|
||||||
def object_details(self, object):
|
def object_details(self, object):
|
||||||
details = object.details_dict()
|
details = object.clean_details_dict(object.details_dict())
|
||||||
template = object.details_template
|
template = object.details_template
|
||||||
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
|
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
|
||||||
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
with open(template_path.joinpath("css", "styles.css"), "r") as f:
|
||||||
css = f.read()
|
css = f.read()
|
||||||
key = object.__class__.__name__.lower()
|
key = object.__class__.__name__.lower()
|
||||||
d = {key: details}
|
d = {key: details}
|
||||||
logger.debug(f"Using details: {d}")
|
logger.debug(f"Using details: {pformat(d)}")
|
||||||
html = template.render(**d, css=css)
|
html = template.render(**d, css=[css])
|
||||||
self.webview.setHtml(html)
|
self.webview.setHtml(html)
|
||||||
self.setWindowTitle(f"{object.__class__.__name__} Details - {object.name}")
|
self.setWindowTitle(f"{object.__class__.__name__} Details - {object.name}")
|
||||||
|
with open(f"{object.__class__.__name__}_details_rendered.html", "w") as f:
|
||||||
|
f.write(html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -226,7 +228,7 @@ class SubmissionDetails(QDialog):
|
|||||||
Args:
|
Args:
|
||||||
run (str | BasicRun): Submission of interest.
|
run (str | BasicRun): Submission of interest.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Submission details.")
|
logger.debug(f"Run details.")
|
||||||
if isinstance(run, str):
|
if isinstance(run, str):
|
||||||
run = Run.query(name=run)
|
run = Run.query(name=run)
|
||||||
self.rsl_plate_number = run.rsl_plate_number
|
self.rsl_plate_number = run.rsl_plate_number
|
||||||
@@ -242,6 +244,7 @@ class SubmissionDetails(QDialog):
|
|||||||
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
|
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
|
||||||
self.webview.setHtml(self.html)
|
self.webview.setHtml(self.html)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def sign_off(self, run: str | Run) -> None:
|
def sign_off(self, run: str | Run) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Contains all procedure related frontend functions
|
Contains all procedure related frontend functions
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QPushButton, QVBoxLayout,
|
QWidget, QPushButton, QVBoxLayout,
|
||||||
QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout
|
QComboBox, QDateEdit, QLineEdit, QLabel, QCheckBox, QHBoxLayout, QGridLayout
|
||||||
@@ -142,9 +144,9 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# self.pydclientsubmission = self.clientsubmissionparser.to_pydantic()
|
# self.pydclientsubmission = self.clientsubmissionparser.to_pydantic()
|
||||||
# self.pydsamples = self.sampleparser.to_pydantic()
|
# self.pydsamples = self.sampleparser.to_pydantic()
|
||||||
# logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}")
|
# logger.debug(f"Samples: {pformat(self.pydclientsubmission.sample)}")
|
||||||
self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, fname=fname)
|
self.clientsubmission_manager = DefaultClientSubmissionManager(parent=self, input_object=fname)
|
||||||
self.pydclientsubmission = self.clientsubmission_manager.parse()
|
self.pydclientsubmission = self.clientsubmission_manager.parse()
|
||||||
checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.samples)
|
checker = SampleChecker(self, "Sample Checker", self.pydclientsubmission.sample)
|
||||||
if checker.exec():
|
if checker.exec():
|
||||||
# logger.debug(pformat(self.pydclientsubmission.sample))
|
# logger.debug(pformat(self.pydclientsubmission.sample))
|
||||||
try:
|
try:
|
||||||
@@ -189,7 +191,7 @@ class SubmissionFormContainer(QWidget):
|
|||||||
class SubmissionFormWidget(QWidget):
|
class SubmissionFormWidget(QWidget):
|
||||||
update_reagent_fields = ['kittype']
|
update_reagent_fields = ['kittype']
|
||||||
|
|
||||||
def __init__(self, parent: QWidget, pyd: PydRun, disable: list | None = None) -> None:
|
def __init__(self, parent: QWidget, pyd: PydClientSubmission, disable: list | None = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
if disable is None:
|
if disable is None:
|
||||||
disable = []
|
disable = []
|
||||||
@@ -816,8 +818,8 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# save_btn = QPushButton("Save")
|
# save_btn = QPushButton("Save")
|
||||||
self.samples = samples
|
self.sample = samples
|
||||||
logger.debug(f"Samples: {self.samples}")
|
logger.debug(f"Samples: {self.sample}")
|
||||||
start_run_btn = QPushButton("Save")
|
start_run_btn = QPushButton("Save")
|
||||||
# self.layout.addWidget(save_btn)
|
# self.layout.addWidget(save_btn)
|
||||||
self.layout.addWidget(start_run_btn)
|
self.layout.addWidget(start_run_btn)
|
||||||
@@ -867,12 +869,19 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
|
|||||||
@report_result
|
@report_result
|
||||||
def create_new_submission(self, *args) -> Report:
|
def create_new_submission(self, *args) -> Report:
|
||||||
pyd = self.to_pydantic()
|
pyd = self.to_pydantic()
|
||||||
|
logger.debug(f"Pydantic: {pyd}")
|
||||||
sql = pyd.to_sql()
|
sql = pyd.to_sql()
|
||||||
for sample in self.samples:
|
for sample in pyd.sample:
|
||||||
if isinstance(sample, PydSample):
|
if isinstance(sample, PydSample):
|
||||||
sample = sample.to_sql()
|
sample = sample.to_sql()
|
||||||
|
assert not isinstance(sample, PydSample)
|
||||||
|
# if sample not in sql.sample:
|
||||||
sql.add_sample(sample=sample)
|
sql.add_sample(sample=sample)
|
||||||
logger.debug(sql.__dict__)
|
logger.debug(pformat(sql.__dict__))
|
||||||
|
try:
|
||||||
|
del sql._misc_info['sample']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
sql.save()
|
sql.save()
|
||||||
self.app.table_widget.sub_wid.set_data()
|
self.app.table_widget.sub_wid.set_data()
|
||||||
self.setParent(None)
|
self.setParent(None)
|
||||||
|
|||||||
@@ -16,22 +16,22 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if clientsubmission['sample'] %}
|
{% if clientsubmission['sample'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Client Submitted Samples:</u></h3></button>
|
<button type="button"><h3><u>Client Submitted Samples:</u></h3></button>
|
||||||
<div class="nested">
|
<!-- <div class="nested">-->
|
||||||
<p>{% for sample in clientsubmission['sample'] %}
|
<p>{% for sample in clientsubmission['sample'] %}
|
||||||
<a class="data-link sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id'] }}</a><br>
|
<a class="data-link sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id'] }}</a><br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
</div>
|
<!-- </div>-->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if clientsubmission['run'] %}
|
{% if clientsubmission['run'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Runs:</u></h3></button>
|
<button type="button"><h3><u>Runs:</u></h3></button>
|
||||||
<div class="nested">
|
<!-- <div class="nested">-->
|
||||||
{% for run in clientsubmission['run'] %}
|
{% for run in clientsubmission['run'] %}
|
||||||
{% with run=run, child=True %}
|
{% with run=run, child=True %}
|
||||||
{% include "run_details.html" %}
|
{% include "run_details.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
<!-- </div>-->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -24,23 +24,7 @@
|
|||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{% if not child %}
|
{% if not child %}
|
||||||
<!--<script>-->
|
|
||||||
<!--var coll = document.getElementsByClassName("collapsible");-->
|
|
||||||
<!--var i;-->
|
|
||||||
|
|
||||||
<!--for (i = 0; i < coll.length; i++) {-->
|
|
||||||
<!-- coll[i].addEventListener("click", function() {-->
|
|
||||||
<!-- this.classList.toggle("active");-->
|
|
||||||
<!-- var content = this.nextElementSibling;-->
|
|
||||||
<!-- if (content.style.display === "block") {-->
|
|
||||||
<!-- content.style.display = "none";-->
|
|
||||||
<!-- } else {-->
|
|
||||||
<!-- content.style.display = "block";-->
|
|
||||||
<!-- }-->
|
|
||||||
<!-- });-->
|
|
||||||
<!--}-->
|
|
||||||
<!--</script>-->
|
|
||||||
<!--{% endif %}-->
|
|
||||||
{% for j in js%}
|
{% for j in js%}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -48,6 +32,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% if not child %}
|
{% if not child %}
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,23 +15,51 @@
|
|||||||
<p>{% for key, value in procedure.items() if key not in procedure['excluded'] %}
|
<p>{% for key, value in procedure.items() if key not in procedure['excluded'] %}
|
||||||
<b>{{ key | replace("_", " ") | title | replace("Pcr", "PCR") }}: {{ value }}</b><br>
|
<b>{{ key | replace("_", " ") | title | replace("Pcr", "PCR") }}: {{ value }}</b><br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
|
{% if procedure['reagent'] %}
|
||||||
|
<button type="button"><h3><u>Reagents:</u></h3></button>
|
||||||
|
<table style="border: 1px solid black; width: 100%; text-align: center">
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid black;">Reagent Role</th>
|
||||||
|
<th style="border: 1px solid black;">Reagent Name</th>
|
||||||
|
<th style="border: 1px solid black;">Lot</th>
|
||||||
|
<th style="border: 1px solid black;">Expiry</th>
|
||||||
|
</tr>
|
||||||
|
{% for reg in procedure['reagent'] %}
|
||||||
|
{% with reagent=reg, child=True %}
|
||||||
|
{% include "support/reagent_list.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}</p>
|
||||||
|
</table><br/>
|
||||||
|
{% endif %}
|
||||||
|
{% if procedure['equipment'] %}
|
||||||
|
<button type="button"><h3><u>Equipment:</u></h3></button>
|
||||||
|
<table style="border: 1px solid black; width: 100%; text-align: center">
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid black;">Equipment Role</th>
|
||||||
|
<th style="border: 1px solid black;">Equipment Name</th>
|
||||||
|
<th style="border: 1px solid black;">Process</th>
|
||||||
|
<th style="border: 1px solid black;">Tips</th>
|
||||||
|
</tr>
|
||||||
|
{% for eq in procedure['equipment'] %}
|
||||||
|
{% with equipment=eq, child=True %}
|
||||||
|
{% include "support/equipment_list.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
</table><br/>
|
||||||
|
{% endif %}
|
||||||
{% if procedure['results'] %}
|
{% if procedure['results'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Results:</u></h3></button>
|
<button type="button"><h3><u>Results:</u></h3></button>
|
||||||
<div class="nested">
|
|
||||||
{% for result in procedure['results'] %}
|
{% for result in procedure['results'] %}
|
||||||
<p>{% for k, v in result['result'].items() %}
|
<p>{% for k, v in result['result'].items() %}
|
||||||
<b>{{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}:</b> {{ value }}<br>
|
<b>{{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}:</b> {{ value }}<br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if procedure['sample'] %}
|
{% if procedure['sample'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Procedure Samples:</u></h3></button>
|
<button type="button"><h3><u>Procedure Samples:</u></h3></button>
|
||||||
<div class="nested">
|
|
||||||
<p>{% for sample in procedure['sample'] %}
|
<p>{% for sample in procedure['sample'] %}
|
||||||
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -15,20 +15,18 @@
|
|||||||
<b>{{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}:</b> {{ value }}<br>
|
<b>{{ key | replace("_", " ") | title | replace("Rsl", "RSL") }}:</b> {{ value }}<br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% if run['sample'] %}
|
{% if run['sample'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Run Samples:</u></h3></button>
|
<button type="button"><h3><u>Run Samples:</u></h3></button>
|
||||||
<p>{% for sample in run['sample'] %}
|
<p>{% for sample in run['sample'] %}
|
||||||
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
<a class="{% if sample['active'] %}data-link {% else %}unused {% endif %}sample" id="{{ sample['sample_id'] }}">{{ sample['sample_id']}}</a><br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if run['procedure'] %}
|
{% if run['procedure'] %}
|
||||||
<button type="button" class="collapsible"><h3><u>Procedures:</u></h3></button>
|
<button type="button"><h3><u>Procedures:</u></h3></button>
|
||||||
<div class="nested">
|
|
||||||
{% for procedure in run['procedure'] %}
|
{% for procedure in run['procedure'] %}
|
||||||
{% with procedure=procedure, child=True %}
|
{% with procedure=procedure, child=True %}
|
||||||
{% include "procedure_details.html" %}
|
{% include "procedure_details.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block signing_button %}
|
{% block signing_button %}
|
||||||
@@ -55,5 +53,3 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
src/submissions/templates/support/reagent_list.html
Normal file
6
src/submissions/templates/support/reagent_list.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid black;">{{ reagent['reagentrole'] }}</td>
|
||||||
|
<td style="border: 1px solid black;">{{ reagent['name'] }}</td>
|
||||||
|
<td style="border: 1px solid black;">{{ reagent['lot'] }}</td>
|
||||||
|
<td style="border: 1px solid black;">{{ reagent['expiry'].strftime('%Y-%m-%d') }}</td>
|
||||||
|
</tr>
|
||||||
Reference in New Issue
Block a user