Attempt to update treeview.
This commit is contained in:
@@ -22,7 +22,7 @@ from . import Base, BaseClass, ClientLab, LogMixin
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from backend.db.models.submissions import Run
|
from backend.db.models.submissions import Run, ProcedureSampleAssociation
|
||||||
|
|
||||||
logger = logging.getLogger(f'procedure.{__name__}')
|
logger = logging.getLogger(f'procedure.{__name__}')
|
||||||
|
|
||||||
@@ -1175,8 +1175,9 @@ class ProcedureType(BaseClass):
|
|||||||
proceduretype=self,
|
proceduretype=self,
|
||||||
#name=dict(value=self.name, missing=True),
|
#name=dict(value=self.name, missing=True),
|
||||||
#possible_kits=[kittype.name for kittype in self.kittype],
|
#possible_kits=[kittype.name for kittype in self.kittype],
|
||||||
repeat=False
|
repeat=False,
|
||||||
# plate_map=plate_map
|
# plate_map=plate_map
|
||||||
|
run=run
|
||||||
)
|
)
|
||||||
return PydProcedure(**output)
|
return PydProcedure(**output)
|
||||||
|
|
||||||
@@ -1222,7 +1223,7 @@ class ProcedureType(BaseClass):
|
|||||||
|
|
||||||
class Procedure(BaseClass):
|
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)
|
||||||
technician = Column(JSON) #: name of processing tech(s)
|
technician = Column(JSON) #: name of processing tech(s)
|
||||||
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
|
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
|
||||||
@@ -1236,13 +1237,23 @@ class Procedure(BaseClass):
|
|||||||
kittype = relationship("KitType", back_populates="procedure")
|
kittype = relationship("KitType", back_populates="procedure")
|
||||||
control = relationship("Control", back_populates="procedure", uselist=True) #: A control sample added to procedure
|
control = relationship("Control", back_populates="procedure", uselist=True) #: A control sample added to procedure
|
||||||
|
|
||||||
|
proceduresampleassociation = relationship(
|
||||||
|
"ProcedureSampleAssociation",
|
||||||
|
back_populates="procedure",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
)
|
||||||
|
|
||||||
|
sample = association_proxy("proceduresampleassociation",
|
||||||
|
"sample", creator=lambda sample: ProcedureSampleAssociation(sample=sample)
|
||||||
|
)
|
||||||
|
|
||||||
procedurereagentassociation = relationship(
|
procedurereagentassociation = relationship(
|
||||||
"ProcedureReagentAssociation",
|
"ProcedureReagentAssociation",
|
||||||
back_populates="procedure",
|
back_populates="procedure",
|
||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
) #: Relation to ProcedureReagentAssociation
|
) #: Relation to ProcedureReagentAssociation
|
||||||
|
|
||||||
reagents = association_proxy("procedurereagentassociation",
|
reagent = association_proxy("procedurereagentassociation",
|
||||||
"reagent", creator=lambda reg: ProcedureReagentAssociation(
|
"reagent", creator=lambda reg: ProcedureReagentAssociation(
|
||||||
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
|
reagent=reg)) #: Association proxy to RunReagentAssociation.reagent
|
||||||
|
|
||||||
@@ -1263,6 +1274,14 @@ class Procedure(BaseClass):
|
|||||||
tips = association_proxy("proceduretipsassociation",
|
tips = association_proxy("proceduretipsassociation",
|
||||||
"tips")
|
"tips")
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def name(self):
|
||||||
|
return f"{self.proceduretype.name}-{self.run.rsl_plate_num}"
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, value):
|
||||||
|
self._name = value
|
||||||
|
|
||||||
@validates('repeat')
|
@validates('repeat')
|
||||||
def validate_repeat(self, key, value):
|
def validate_repeat(self, key, value):
|
||||||
if value > 1:
|
if value > 1:
|
||||||
@@ -1290,6 +1309,12 @@ class Procedure(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return cls.execute_query(query=query, limit=limit)
|
return cls.execute_query(query=query, limit=limit)
|
||||||
|
|
||||||
|
def to_dict(self, full_data: bool=False):
|
||||||
|
output = dict()
|
||||||
|
output['name'] = self.name
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProcedureTypeKitTypeAssociation(BaseClass):
|
class ProcedureTypeKitTypeAssociation(BaseClass):
|
||||||
"""
|
"""
|
||||||
@@ -2608,171 +2633,4 @@ class ProcedureTipsAssociation(BaseClass):
|
|||||||
from backend.validators import PydTips
|
from backend.validators import PydTips
|
||||||
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
|
||||||
|
|
||||||
#
|
|
||||||
# class ProcedureType(BaseClass):
|
|
||||||
# id = Column(INTEGER, primary_key=True)
|
|
||||||
# name = Column(String(64))
|
|
||||||
# reagent_map = Column(JSON)
|
|
||||||
#
|
|
||||||
# procedure = relationship("Procedure",
|
|
||||||
# back_populates="proceduretype") #: Concrete control of this type.
|
|
||||||
#
|
|
||||||
# process = relationship("Process", back_populates="proceduretype",
|
|
||||||
# secondary=proceduretype_process) #: Relation to equipment process used for this type.
|
|
||||||
#
|
|
||||||
# proceduretypekittypeassociation = relationship(
|
|
||||||
# "ProcedureTypeKitTypeAssociation",
|
|
||||||
# back_populates="proceduretype",
|
|
||||||
# cascade="all, delete-orphan",
|
|
||||||
# ) #: Association of kittypes
|
|
||||||
#
|
|
||||||
# kittype = association_proxy("proceduretypekittypeassociation", "kittype",
|
|
||||||
# creator=lambda kit: ProcedureTypeKitTypeAssociation(
|
|
||||||
# kittype=kit)) #: Proxy of kittype association
|
|
||||||
#
|
|
||||||
# proceduretypeequipmentroleassociation = relationship(
|
|
||||||
# "ProcedureTypeEquipmentRoleAssociation",
|
|
||||||
# back_populates="proceduretype",
|
|
||||||
# cascade="all, delete-orphan"
|
|
||||||
# ) #: Association of equipmentroles
|
|
||||||
#
|
|
||||||
# equipment = association_proxy("proceduretypeequipmentroleassociation", "equipmentrole",
|
|
||||||
# creator=lambda eq: ProcedureTypeEquipmentRoleAssociation(
|
|
||||||
# equipment_role=eq)) #: Proxy of equipmentrole associations
|
|
||||||
#
|
|
||||||
# kittypereagentroleassociation = relationship(
|
|
||||||
# "KitTypeReagentRoleAssociation",
|
|
||||||
# back_populates="proceduretype",
|
|
||||||
# cascade="all, delete-orphan"
|
|
||||||
# ) #: triple association of KitTypes, ReagentTypes, SubmissionTypes
|
|
||||||
#
|
|
||||||
# proceduretypetiproleassociation = relationship(
|
|
||||||
# "ProcedureTypeTipRoleAssociation",
|
|
||||||
# back_populates="proceduretype",
|
|
||||||
# cascade="all, delete-orphan"
|
|
||||||
# ) #: Association of tiproles
|
|
||||||
#
|
|
||||||
# def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
|
|
||||||
# """
|
|
||||||
# Make a map of all locations for tips or equipment.
|
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# field (Literal['equipment', 'tip']): the field to construct a map for
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# Generator[(str, dict), None, None]: Generator composing key, locations for each item in the map
|
|
||||||
# """
|
|
||||||
# for item in self.__getattribute__(f"proceduretype{field}role_associations"):
|
|
||||||
# fmap = item.uses
|
|
||||||
# if fmap is None:
|
|
||||||
# fmap = {}
|
|
||||||
# yield getattr(item, f"{field}_role").name, fmap
|
|
||||||
#
|
|
||||||
# @property
|
|
||||||
# def default_kit(self) -> KitType | None:
|
|
||||||
# """
|
|
||||||
# If only one kits exists for this Submission Type, return it.
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# KitType | None:
|
|
||||||
# """
|
|
||||||
# if len(self.kittype) == 1:
|
|
||||||
# return self.kittype[0]
|
|
||||||
# else:
|
|
||||||
# return None
|
|
||||||
#
|
|
||||||
# def get_equipment(self, kittype: str | KitType | None = None) -> Generator['PydEquipmentRole', None, None]:
|
|
||||||
# """
|
|
||||||
# Returns PydEquipmentRole of all equipment associated with this SubmissionType
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# Generator['PydEquipmentRole', None, None]: List of equipment roles
|
|
||||||
# """
|
|
||||||
# return (item.to_pydantic(proceduretype=self, kittype=kittype) for item in self.equipment)
|
|
||||||
#
|
|
||||||
# def get_processes_for_role(self, equipmentrole: str | EquipmentRole, kittype: str | KitType | None = None) -> list:
|
|
||||||
# """
|
|
||||||
# Get process associated with this SubmissionType for an EquipmentRole
|
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# equipmentrole (str | EquipmentRole): EquipmentRole of interest
|
|
||||||
# kittype (str | KitType | None, optional): Kit of interest. Defaults to None.
|
|
||||||
#
|
|
||||||
# Raises:
|
|
||||||
# TypeError: Raised if wrong type given for equipmentrole
|
|
||||||
#
|
|
||||||
# Returns:
|
|
||||||
# list: list of associated process
|
|
||||||
# """
|
|
||||||
# match equipmentrole:
|
|
||||||
# case str():
|
|
||||||
# relevant = [item.get_all_processes(kittype) for item in self.proceduretypeequipmentroleassociation if
|
|
||||||
# item.equipmentrole.name == equipmentrole]
|
|
||||||
# case EquipmentRole():
|
|
||||||
# relevant = [item.get_all_processes(kittype) for item in self.proceduretypeequipmentroleassociation if
|
|
||||||
# item.equipmentrole == equipmentrole]
|
|
||||||
# case _:
|
|
||||||
# raise TypeError(f"Type {type(equipmentrole)} is not allowed")
|
|
||||||
# return list(set([item for items in relevant for item in items if item is not None]))
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class Procedure(BaseClass):
|
|
||||||
# id = Column(INTEGER, primary_key=True)
|
|
||||||
# name = Column(String, unique=True)
|
|
||||||
# technician = Column(JSON) #: name of processing tech(s)
|
|
||||||
# proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
|
|
||||||
# name="fk_PRO_proceduretype_id")) #: client lab id from _organizations))
|
|
||||||
# proceduretype = relationship("ProcedureType", back_populates="procedure")
|
|
||||||
# run_id = Column(INTEGER, ForeignKey("_run.id", ondelete="SET NULL",
|
|
||||||
# name="fk_PRO_basicrun_id")) #: client lab id from _organizations))
|
|
||||||
# run = relationship("Run", back_populates="procedure")
|
|
||||||
# kittype_id = Column(INTEGER, ForeignKey("_kittype.id", ondelete="SET NULL",
|
|
||||||
# name="fk_PRO_kittype_id")) #: client lab id from _organizations))
|
|
||||||
# kittype = relationship("KitType", back_populates="procedure")
|
|
||||||
#
|
|
||||||
# control = relationship("Control", back_populates="procedure",
|
|
||||||
# uselist=True) #: A control sample added to procedure
|
|
||||||
#
|
|
||||||
# procedurereagentassociations = relationship(
|
|
||||||
# "ProcedureReagentAssociation",
|
|
||||||
# back_populates="procedure",
|
|
||||||
# cascade="all, delete-orphan",
|
|
||||||
# ) #: Relation to ProcedureReagentAssociation
|
|
||||||
#
|
|
||||||
# reagents = association_proxy("procedurereagentassociations",
|
|
||||||
# "reagent") #: Association proxy to RunReagentAssociation.reagent
|
|
||||||
#
|
|
||||||
# procedureequipmentassociations = relationship(
|
|
||||||
# "ProcedureEquipmentAssociation",
|
|
||||||
# back_populates="procedure",
|
|
||||||
# cascade="all, delete-orphan"
|
|
||||||
# ) #: Relation to Equipment
|
|
||||||
#
|
|
||||||
# equipment = association_proxy("procedureequipmentassociations",
|
|
||||||
# "equipment") #: Association proxy to RunEquipmentAssociation.equipment
|
|
||||||
#
|
|
||||||
# proceduretipsassociations = relationship(
|
|
||||||
# "ProcedureTipsAssociation",
|
|
||||||
# back_populates="procedure",
|
|
||||||
# cascade="all, delete-orphan")
|
|
||||||
#
|
|
||||||
# tips = association_proxy("proceduretipsassociations",
|
|
||||||
# "tips")
|
|
||||||
#
|
|
||||||
# @classmethod
|
|
||||||
# @setup_lookup
|
|
||||||
# def query(cls, id: int|None = None, name: str | None = None, limit: int = 0, **kwargs) -> Procedure | List[Procedure]:
|
|
||||||
# query: Query = cls.__database_session__.query(cls)
|
|
||||||
# match id:
|
|
||||||
# case int():
|
|
||||||
# query = query.filter(cls.id == id)
|
|
||||||
# limit = 1
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# match name:
|
|
||||||
# case str():
|
|
||||||
# query = query.filter(cls.name == name)
|
|
||||||
# limit = 1
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# return cls.execute_query(query=query, limit=limit)
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from operator import itemgetter
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin
|
from . import Base, BaseClass, Reagent, SubmissionType, KitType, ClientLab, Contact, LogMixin, Procedure, kittype_procedure
|
||||||
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
|
from sqlalchemy import Column, String, TIMESTAMP, INTEGER, ForeignKey, JSON, FLOAT, case, func, Table
|
||||||
from sqlalchemy.orm import relationship, validates, Query
|
from sqlalchemy.orm import relationship, validates, Query
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
@@ -34,9 +34,8 @@ from jinja2.exceptions import TemplateNotFound
|
|||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from backend.db.models.kits import ProcedureType
|
from backend.db.models.kits import ProcedureType, Procedure
|
||||||
|
|
||||||
from . import kittype_procedure
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"procedure.{__name__}")
|
logger = logging.getLogger(f"procedure.{__name__}")
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
# dicto, _ = self.kittype.construct_xl_map_for_use(self.proceduretype)
|
# dicto, _ = self.kittype.construct_xl_map_for_use(self.proceduretype)
|
||||||
# sample = self.generate_associations(name="clientsubmissionsampleassociation")
|
# sample = self.generate_associations(name="clientsubmissionsampleassociation")
|
||||||
samples = None
|
samples = None
|
||||||
runs = [item.to_dict() for item in self.run]
|
runs = [item.to_dict(full_data=True) for item in self.run]
|
||||||
# custom = self.custom
|
# custom = self.custom
|
||||||
else:
|
else:
|
||||||
samples = None
|
samples = None
|
||||||
@@ -263,6 +262,7 @@ class ClientSubmission(BaseClass, LogMixin):
|
|||||||
output["contact_phone"] = contact_phone
|
output["contact_phone"] = contact_phone
|
||||||
# output["custom"] = custom
|
# output["custom"] = custom
|
||||||
output["run"] = runs
|
output["run"] = runs
|
||||||
|
output['name'] = self.name
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def add_sample(self, sample: Sample):
|
def add_sample(self, sample: Sample):
|
||||||
@@ -539,12 +539,14 @@ class Run(BaseClass, LogMixin):
|
|||||||
samples = self.generate_associations(name="clientsubmissionsampleassociation")
|
samples = self.generate_associations(name="clientsubmissionsampleassociation")
|
||||||
equipment = self.generate_associations(name="submission_equipment_associations")
|
equipment = self.generate_associations(name="submission_equipment_associations")
|
||||||
tips = self.generate_associations(name="submission_tips_associations")
|
tips = self.generate_associations(name="submission_tips_associations")
|
||||||
|
procedures = [item.to_dict(full_data=True) for item in self.procedure]
|
||||||
custom = self.custom
|
custom = self.custom
|
||||||
else:
|
else:
|
||||||
samples = None
|
samples = None
|
||||||
equipment = None
|
equipment = None
|
||||||
tips = None
|
tips = None
|
||||||
custom = None
|
custom = None
|
||||||
|
procedures = None
|
||||||
try:
|
try:
|
||||||
comments = self.comment
|
comments = self.comment
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -570,6 +572,8 @@ class Run(BaseClass, LogMixin):
|
|||||||
output["contact"] = contact
|
output["contact"] = contact
|
||||||
output["contact_phone"] = contact_phone
|
output["contact_phone"] = contact_phone
|
||||||
output["custom"] = custom
|
output["custom"] = custom
|
||||||
|
output['procedures'] = procedures
|
||||||
|
output['name'] = self.name
|
||||||
try:
|
try:
|
||||||
output["completed_date"] = self.completed_date.strftime("%Y-%m-%d")
|
output["completed_date"] = self.completed_date.strftime("%Y-%m-%d")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -1131,7 +1135,10 @@ class Run(BaseClass, LogMixin):
|
|||||||
logger.debug(f"Got ProcedureType: {procedure_type}")
|
logger.debug(f"Got ProcedureType: {procedure_type}")
|
||||||
dlg = ProcedureCreation(parent=obj, run=self, proceduretype=procedure_type)
|
dlg = ProcedureCreation(parent=obj, run=self, proceduretype=procedure_type)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
pass
|
sql, _ = dlg.return_sql()
|
||||||
|
logger.debug(f"Output run samples:\n{pformat(sql.run.sample)}")
|
||||||
|
sql.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def delete(self, obj=None):
|
def delete(self, obj=None):
|
||||||
@@ -1314,8 +1321,9 @@ class Run(BaseClass, LogMixin):
|
|||||||
background_color="#6ffe1d"))
|
background_color="#6ffe1d"))
|
||||||
padded_list = []
|
padded_list = []
|
||||||
for iii in range(1, proceduretype.total_wells+1):
|
for iii in range(1, proceduretype.total_wells+1):
|
||||||
|
row, column = proceduretype.ranked_plate[iii]
|
||||||
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
|
sample = next((item for item in ranked_samples if item['submission_rank']==iii),
|
||||||
dict(well_id=f"blank_{iii}", sample_id="", row=0, column=0, submission_rank=iii, background_color="#ffffff")
|
dict(well_id=f"blank_{iii}", sample_id="", row=row, column=column, submission_rank=iii, background_color="#ffffff")
|
||||||
)
|
)
|
||||||
padded_list.append(sample)
|
padded_list.append(sample)
|
||||||
# logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
|
# logger.debug(f"Final padded list:\n{pformat(list(sorted(padded_list, key=itemgetter('submission_rank'))))}")
|
||||||
@@ -1361,6 +1369,14 @@ class Sample(BaseClass, LogMixin):
|
|||||||
|
|
||||||
run = association_proxy("samplerunassociation", "run") #: proxy of associated procedure
|
run = association_proxy("samplerunassociation", "run") #: proxy of associated procedure
|
||||||
|
|
||||||
|
sampleprocedureassociation = relationship(
|
||||||
|
"ProcedureSampleAssociation",
|
||||||
|
back_populates="sample",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
)
|
||||||
|
|
||||||
|
procedure = association_proxy("sampleprocedureassociation", "procedure")
|
||||||
|
|
||||||
@hybrid_property
|
@hybrid_property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.sample_id
|
return self.sample_id
|
||||||
@@ -1806,10 +1822,10 @@ class RunSampleAssociation(BaseClass):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
|
# id = Column(INTEGER, unique=True, nullable=False) #: id to be used for inheriting purposes
|
||||||
sample_id = Column(INTEGER, ForeignKey("_sample.id"), nullable=False) #: id of associated sample
|
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated sample
|
||||||
run_id = Column(INTEGER, ForeignKey("_run.id"), primary_key=True) #: id of associated procedure
|
run_id = Column(INTEGER, ForeignKey("_run.id"), primary_key=True) #: id of associated procedure
|
||||||
row = Column(INTEGER, primary_key=True) #: row on the 96 well plate
|
row = Column(INTEGER) #: row on the 96 well plate
|
||||||
column = Column(INTEGER, primary_key=True) #: column on the 96 well plate
|
column = Column(INTEGER) #: column on the 96 well plate
|
||||||
# misc_info = Column(JSON)
|
# misc_info = Column(JSON)
|
||||||
|
|
||||||
# NOTE: reference to the Submission object
|
# NOTE: reference to the Submission object
|
||||||
@@ -2009,3 +2025,16 @@ class RunSampleAssociation(BaseClass):
|
|||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||||
|
|
||||||
|
|
||||||
|
class ProcedureSampleAssociation(BaseClass):
|
||||||
|
procedure_id = Column(INTEGER, ForeignKey("_procedure.id"), primary_key=True) #: id of associated procedure
|
||||||
|
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated equipment
|
||||||
|
row = Column(INTEGER)
|
||||||
|
column = Column(INTEGER)
|
||||||
|
|
||||||
|
procedure = relationship(Procedure,
|
||||||
|
back_populates="proceduresampleassociation") #: associated procedure
|
||||||
|
|
||||||
|
sample = relationship(Sample, back_populates="sampleprocedureassociation") #: associated equipment
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ class PydBaseClass(BaseModel, extra='allow', validate_assignment=True):
|
|||||||
dicto = self.improved_dict(dictionaries=False)
|
dicto = self.improved_dict(dictionaries=False)
|
||||||
logger.debug(f"Dicto: {dicto}")
|
logger.debug(f"Dicto: {dicto}")
|
||||||
sql, _ = self._sql_object().query_or_create(**dicto)
|
sql, _ = self._sql_object().query_or_create(**dicto)
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
|
||||||
@@ -1383,8 +1382,6 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
value = {item.name: item.reagents for item in kittype.reagentrole}
|
value = {item.name: item.reagents for item in kittype.reagentrole}
|
||||||
return 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
|
||||||
@@ -1395,20 +1392,60 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
kittype_obj.get_reagents(proceduretype=self.proceduretype)}
|
kittype_obj.get_reagents(proceduretype=self.proceduretype)}
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.reagentrole = {}
|
self.reagentrole = {}
|
||||||
|
self.kittype['value'] = kittype
|
||||||
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
self.possible_kits.insert(0, self.possible_kits.pop(self.possible_kits.index(kittype)))
|
||||||
|
|
||||||
def update_samples(self, sample_list: List[dict]):
|
def update_samples(self, sample_list: List[dict]):
|
||||||
logger.debug(f"Incoming sample_list:\n{pformat(sample_list)}")
|
logger.debug(f"Incoming sample_list:\n{pformat(sample_list)}")
|
||||||
for sample_dict in sample_list:
|
for sample_dict in sample_list:
|
||||||
try:
|
if sample_dict['sample_id'].startswith("blank_"):
|
||||||
sample = next((item for item in self.samples if item.sample_id.upper()==sample_dict['sample_id'].upper()))
|
|
||||||
except StopIteration:
|
|
||||||
continue
|
continue
|
||||||
row, column = self.proceduretype.ranked_plate[sample_dict['index']]
|
row, column = self.proceduretype.ranked_plate[sample_dict['index']]
|
||||||
|
logger.debug(f"Row: {row}, Column: {column}")
|
||||||
|
try:
|
||||||
|
sample = next(
|
||||||
|
(item for item in self.samples if item.sample_id.upper() == sample_dict['sample_id'].upper()))
|
||||||
|
except StopIteration:
|
||||||
|
# NOTE: Code to check for added controls.
|
||||||
|
logger.debug(f"Sample not found by name: {sample_dict['sample_id']}, checking row {row} column {column}")
|
||||||
|
try:
|
||||||
|
sample = next(
|
||||||
|
(item for item in self.samples if item.row == row and item.column == column))
|
||||||
|
except StopIteration:
|
||||||
|
logger.error(f"Couldn't find sample: {pformat(sample_dict)}")
|
||||||
|
continue
|
||||||
|
logger.debug(f"Sample of interest: {sample.improved_dict()}")
|
||||||
|
sample.sample_id = sample_dict['sample_id']
|
||||||
|
sample.well_id = sample_dict['sample_id']
|
||||||
sample.row = row
|
sample.row = row
|
||||||
sample.column = column
|
sample.column = column
|
||||||
logger.debug(f"Updated samples:\n{pformat(self.samples)}")
|
logger.debug(f"Updated samples:\n{pformat(self.samples)}")
|
||||||
|
|
||||||
|
def to_sql(self):
|
||||||
|
from backend.db.models import RunSampleAssociation, ProcedureSampleAssociation
|
||||||
|
sql = super().to_sql()
|
||||||
|
if self.run:
|
||||||
|
sql.run = self.run
|
||||||
|
if self.proceduretype:
|
||||||
|
sql.proceduretype = self.proceduretype
|
||||||
|
for sample in self.samples:
|
||||||
|
if sample.sample_id.startswith("blank_") or sample.sample_id == "":
|
||||||
|
continue
|
||||||
|
sample_sql = sample.to_sql()
|
||||||
|
if sql.run:
|
||||||
|
if sample_sql not in sql.run.sample:
|
||||||
|
logger.debug(f"sample {sample_sql} not found in {sql.run.sample}")
|
||||||
|
run_assoc = RunSampleAssociation(sample=sample_sql, run=self.run, row=sample.row, column=sample.column)
|
||||||
|
else:
|
||||||
|
logger.debug(f"sample {sample_sql} found in {sql.run.sample}")
|
||||||
|
proc_assoc = ProcedureSampleAssociation(procedure=sql, sample=sample_sql, row=sample.row, column=sample.column)
|
||||||
|
if self.kittype['value'] not in ["NA", None, ""]:
|
||||||
|
kittype = KitType.query(name=self.kittype['value'], limit=1)
|
||||||
|
if kittype:
|
||||||
|
sql.kittype = kittype
|
||||||
|
return sql, None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PydClientSubmission(PydBaseClass):
|
class PydClientSubmission(PydBaseClass):
|
||||||
# sql_object: ClassVar = ClientSubmission
|
# sql_object: ClassVar = ClientSubmission
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from PyQt6.QtCore import pyqtSlot, Qt
|
|||||||
from PyQt6.QtGui import QContextMenuEvent, QAction
|
from PyQt6.QtGui import QContextMenuEvent, QAction
|
||||||
from PyQt6.QtWebChannel import QWebChannel
|
from PyQt6.QtWebChannel import QWebChannel
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PyQt6.QtWidgets import QDialog, QGridLayout, QMenu
|
from PyQt6.QtWidgets import QDialog, QGridLayout, QMenu, QDialogButtonBox
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -54,6 +54,11 @@ class ProcedureCreation(QDialog):
|
|||||||
self.channel.registerObject('backend', self)
|
self.channel.registerObject('backend', self)
|
||||||
self.set_html()
|
self.set_html()
|
||||||
self.webview.page().setWebChannel(self.channel)
|
self.webview.page().setWebChannel(self.channel)
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
self.layout.addWidget(self.buttonBox, 11, 1, 1, 1)
|
||||||
|
|
||||||
def set_html(self):
|
def set_html(self):
|
||||||
html = render_details_template(
|
html = render_details_template(
|
||||||
@@ -71,9 +76,9 @@ class ProcedureCreation(QDialog):
|
|||||||
|
|
||||||
@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}")
|
||||||
attribute = getattr(self.created_procedure, key)
|
attribute = getattr(self.created_procedure, key)
|
||||||
attribute['value'] = new_value
|
attribute['value'] = new_value.strip('\"')
|
||||||
|
|
||||||
@pyqtSlot(str, bool)
|
@pyqtSlot(str, bool)
|
||||||
def check_toggle(self, key: str, ischecked: bool):
|
def check_toggle(self, key: str, ischecked: bool):
|
||||||
@@ -94,6 +99,9 @@ class ProcedureCreation(QDialog):
|
|||||||
def log(self, logtext: str):
|
def log(self, logtext: str):
|
||||||
logger.debug(logtext)
|
logger.debug(logtext)
|
||||||
|
|
||||||
|
def return_sql(self):
|
||||||
|
return self.created_procedure.to_sql()
|
||||||
|
|
||||||
|
|
||||||
# class ProcedureWebViewer(QWebEngineView):
|
# class ProcedureWebViewer(QWebEngineView):
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, Q
|
|||||||
QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator
|
QHeaderView, QAbstractItemView, QWidget, QTreeWidgetItemIterator
|
||||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex
|
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel, pyqtSlot, QModelIndex
|
||||||
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor, QContextMenuEvent
|
from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor, QContextMenuEvent
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from backend import Procedure
|
||||||
from backend.db.models import Run, ClientSubmission
|
from backend.db.models import Run, ClientSubmission
|
||||||
from tools import Report, Result, report_result
|
from tools import Report, Result, report_result
|
||||||
from .functions import select_open_file
|
from .functions import select_open_file
|
||||||
@@ -263,8 +265,8 @@ class SubmissionsTree(QTreeView):
|
|||||||
self.setIndentation(0)
|
self.setIndentation(0)
|
||||||
self.setExpandsOnDoubleClick(False)
|
self.setExpandsOnDoubleClick(False)
|
||||||
self.clicked.connect(self.on_clicked)
|
self.clicked.connect(self.on_clicked)
|
||||||
delegate = ClientSubmissionDelegate(self)
|
delegate1 = ClientSubmissionDelegate(self)
|
||||||
self.setItemDelegateForColumn(0, delegate)
|
self.setItemDelegateForColumn(0, delegate1)
|
||||||
self.model = model
|
self.model = model
|
||||||
self.setModel(self.model)
|
self.setModel(self.model)
|
||||||
# self.header().setSectionResizeMode(0, QHeaderView.sectionResizeMode(self,0).ResizeToContents)
|
# self.header().setSectionResizeMode(0, QHeaderView.sectionResizeMode(self,0).ResizeToContents)
|
||||||
@@ -329,11 +331,49 @@ class SubmissionsTree(QTreeView):
|
|||||||
ClientSubmission.query(chronologic=True, page=page, page_size=page_size)]
|
ClientSubmission.query(chronologic=True, page=page, page_size=page_size)]
|
||||||
logger.debug(f"setting data:\n {pformat(self.data)}")
|
logger.debug(f"setting data:\n {pformat(self.data)}")
|
||||||
# sys.exit()
|
# sys.exit()
|
||||||
|
root = self.model.invisibleRootItem()
|
||||||
for submission in self.data:
|
for submission in self.data:
|
||||||
group_str = f"{submission['submissiontype']}-{submission['submitter_plate_id']}-{submission['submitted_date']}"
|
group_str = f"{submission['submissiontype']}-{submission['submitter_plate_id']}-{submission['submitted_date']}"
|
||||||
group_item = self.model.add_group(group_str, query_str=submission['submitter_plate_id'])
|
submission_item = self.model.add_group(parent=root, item_data=dict(
|
||||||
|
name=group_str,
|
||||||
|
query_str=submission['submitter_plate_id'],
|
||||||
|
item_type=ClientSubmission
|
||||||
|
))
|
||||||
for run in submission['run']:
|
for run in submission['run']:
|
||||||
self.model.append_element_to_group(group_item=group_item, element=run)
|
# self.model.append_element_to_group(group_item=group_item, element=run)
|
||||||
|
run_item = self.model.add_group(parent=submission_item, item_data=dict(
|
||||||
|
name=run['plate_number'],
|
||||||
|
query_str=run['plate_number'],
|
||||||
|
item_type=Run
|
||||||
|
))
|
||||||
|
|
||||||
|
for procedure in run['procedures']:
|
||||||
|
self.model.add_group(parent=run_item, item_data=dict(
|
||||||
|
name=procedure['name'],
|
||||||
|
query_str=procedure['name'],
|
||||||
|
item_type=Procedure
|
||||||
|
))
|
||||||
|
# root = self.model.invisibleRootItem()
|
||||||
|
# for submission in self.data:
|
||||||
|
# submission_item = QStandardItem(submission['name'])
|
||||||
|
# root.appendRow(submission_item)
|
||||||
|
# for run in submission['run']:
|
||||||
|
# run_item = QStandardItem(run['name'])
|
||||||
|
# submission_item.appendRow(run_item)
|
||||||
|
# for procedure in run['procedures']:
|
||||||
|
# procedure_item = QStandardItem(procedure['name'])
|
||||||
|
# run_item.appendRow(procedure_item)
|
||||||
|
# self._populateTree(self.data, self.model.invisibleRootItem())
|
||||||
|
|
||||||
|
def _populateTree(self, children, parent):
|
||||||
|
for child in children:
|
||||||
|
logger.debug(child)
|
||||||
|
child_item = QStandardItem(child['name'])
|
||||||
|
parent.appendRow(child_item)
|
||||||
|
if isinstance(children, List):
|
||||||
|
self._populateTree(child, child_item)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
if self.model != None:
|
if self.model != None:
|
||||||
@@ -366,27 +406,26 @@ class ClientSubmissionRunModel(QStandardItemModel):
|
|||||||
self.setColumnCount(len(headers))
|
self.setColumnCount(len(headers))
|
||||||
self.setHorizontalHeaderLabels(headers)
|
self.setHorizontalHeaderLabels(headers)
|
||||||
|
|
||||||
def add_group(self, item_name, query_str: str):
|
def add_group(self, parent, item_data):
|
||||||
item_root = QStandardItem()
|
item_root = QStandardItem()
|
||||||
item_root.setEditable(False)
|
item_root.setEditable(False)
|
||||||
item = QStandardItem(item_name)
|
item = QStandardItem(item_data['name'])
|
||||||
item.setEditable(False)
|
item.setEditable(False)
|
||||||
ii = self.invisibleRootItem()
|
i = parent.rowCount()
|
||||||
i = ii.rowCount()
|
|
||||||
for j, it in enumerate((item_root, item)):
|
for j, it in enumerate((item_root, item)):
|
||||||
# NOTE: Adding item to invisible root row i, column j (wherever j comes from)
|
# NOTE: Adding item to invisible root row i, column j (wherever j comes from)
|
||||||
ii.setChild(i, j, it)
|
parent.setChild(i, j, it)
|
||||||
ii.setEditable(False)
|
parent.setEditable(False)
|
||||||
for j in range(self.columnCount()):
|
for j in range(self.columnCount()):
|
||||||
it = ii.child(i, j)
|
it = parent.child(i, j)
|
||||||
if it is None:
|
if it is None:
|
||||||
# NOTE: Set invisible root child to empty if it is None.
|
# NOTE: Set invisible root child to empty if it is None.
|
||||||
it = QStandardItem()
|
it = QStandardItem()
|
||||||
ii.setChild(i, j, it)
|
parent.setChild(i, j, it)
|
||||||
item_root.setData(dict(item_type=ClientSubmission, query_str=query_str), 1)
|
item_root.setData(dict(item_type=item_data['item_type'], query_str=item_data['query_str']), 1)
|
||||||
return item_root
|
return item_root
|
||||||
|
|
||||||
def append_element_to_group(self, group_item, element: dict):
|
def append_element_to_group(self, group_item, item_data: dict):
|
||||||
# logger.debug(f"Element: {pformat(element)}")
|
# logger.debug(f"Element: {pformat(element)}")
|
||||||
j = group_item.rowCount()
|
j = group_item.rowCount()
|
||||||
item_icon = QStandardItem()
|
item_icon = QStandardItem()
|
||||||
@@ -402,14 +441,15 @@ class ClientSubmissionRunModel(QStandardItemModel):
|
|||||||
key = None
|
key = None
|
||||||
if not key:
|
if not key:
|
||||||
continue
|
continue
|
||||||
value = str(element[key])
|
value = str(item_data[key])
|
||||||
item = QStandardItem(value)
|
item = QStandardItem(value)
|
||||||
item.setBackground(QColor("#CFE2F3"))
|
item.setBackground(QColor("#CFE2F3"))
|
||||||
item.setEditable(False)
|
item.setEditable(False)
|
||||||
# item_icon.setChild(j, i, item)
|
# item_icon.setChild(j, i, item)
|
||||||
item.setData(dict(item_type=Run, query_str=element['plate_number']),1)
|
item.setData(dict(item_type=item_data['item_type'], query_str=item_data['query_str']),1)
|
||||||
group_item.setChild(j, i, item)
|
group_item.setChild(j, i, item)
|
||||||
# group_item.setChild(j, 1, QStandardItem("B"))
|
# group_item.setChild(j, 1, QStandardItem("B"))
|
||||||
|
return item
|
||||||
|
|
||||||
def get_value(self, idx: int, column: int = 1):
|
def get_value(self, idx: int, column: int = 1):
|
||||||
return self.item(idx, column)
|
return self.item(idx, column)
|
||||||
|
|||||||
@@ -85,6 +85,27 @@ div.gallery {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 150px;
|
||||||
|
margin: 32px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.context-menu--active {
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: white;
|
||||||
|
border: 2px solid black;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.plate {
|
.plate {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
@@ -111,15 +132,17 @@ div.gallery {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
ul.no-bullets {
|
||||||
display: none;
|
list-style-type: none; /* Remove bullets */
|
||||||
position: absolute;
|
padding: 0; /* Remove padding */
|
||||||
z-index: 10;
|
margin: 0; /* Remove margins */
|
||||||
background-color: rgba(229, 231, 228, 0.7);
|
|
||||||
border-radius: 2px;
|
|
||||||
border-color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu--active {
|
.negativecontrol {
|
||||||
display: block;
|
background-color: cyan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.positivecontrol {
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// menuIndex = eval(event.target.parentNode.id.slice(-1));
|
// menuIndex = eval(event.target.parentNode.id.slice(-1));
|
||||||
// document.querySelectorAll(".multiSelect")[menuIndex].style.transform =
|
// document.querySelectorAll(".multiSelect")[menuIndex].style.transform =
|
||||||
// "translateX(-100%)";
|
// "translateX(-100%)";
|
||||||
// // document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath = "polygon(0 0, 0 0, 0 100%, 0% 100%)";
|
// document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath = "polygon(0 0, 0 0, 0 100%, 0% 100%)";
|
||||||
// document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath =
|
// document.querySelectorAll(".multiSelect")[menuIndex].style.clipPath =
|
||||||
// "polygon(100% 0, 100% 0, 100% 100%, 100% 100%)";
|
// "polygon(100% 0, 100% 0, 100% 100%, 100% 100%)";
|
||||||
// document.querySelectorAll(".multiSelect")[menuIndex + 1].style.transform =
|
// document.querySelectorAll(".multiSelect")[menuIndex + 1].style.transform =
|
||||||
@@ -116,7 +116,7 @@ function menuItemListener( link ) {
|
|||||||
insertSample(contextIndex, task_id);
|
insertSample(contextIndex, task_id);
|
||||||
break;
|
break;
|
||||||
case "InsertControl":
|
case "InsertControl":
|
||||||
insertControl(contextIndex);
|
insertControl(taskItemInContext);
|
||||||
break;
|
break;
|
||||||
case "RemoveSample":
|
case "RemoveSample":
|
||||||
removeSample(contextIndex);
|
removeSample(contextIndex);
|
||||||
@@ -125,6 +125,7 @@ function menuItemListener( link ) {
|
|||||||
backend.log("default");
|
backend.log("default");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
rearrange_plate();
|
||||||
toggleMenuOff();
|
toggleMenuOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ var clickCoordsX;
|
|||||||
var clickCoordsY;
|
var clickCoordsY;
|
||||||
var menu = document.getElementById(contextMenuClassName);
|
var menu = document.getElementById(contextMenuClassName);
|
||||||
var menuItems = menu.getElementsByClassName(contextMenuItemClassName);
|
var menuItems = menu.getElementsByClassName(contextMenuItemClassName);
|
||||||
var menuHeader = document.getElementById("menu-header");
|
const menuHeader = document.getElementById("menu-header");
|
||||||
var menuState = 0;
|
var menuState = 0;
|
||||||
var menuWidth;
|
var menuWidth;
|
||||||
var menuHeight;
|
var menuHeight;
|
||||||
@@ -158,6 +159,7 @@ var menuPositionX;
|
|||||||
var menuPositionY;
|
var menuPositionY;
|
||||||
var windowWidth;
|
var windowWidth;
|
||||||
var windowHeight;
|
var windowHeight;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise our application's code.
|
* Initialise our application's code.
|
||||||
*/
|
*/
|
||||||
@@ -192,6 +194,7 @@ function contextListener() {
|
|||||||
function clickListener() {
|
function clickListener() {
|
||||||
document.addEventListener( "click", function(e) {
|
document.addEventListener( "click", function(e) {
|
||||||
var clickeElIsLink = clickInsideElement( e, contextMenuLinkClassName );
|
var clickeElIsLink = clickInsideElement( e, contextMenuLinkClassName );
|
||||||
|
backend.log(e.target.id)
|
||||||
if ( clickeElIsLink ) {
|
if ( clickeElIsLink ) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
menuItemListener( clickeElIsLink );
|
menuItemListener( clickeElIsLink );
|
||||||
@@ -241,17 +244,39 @@ function insertSample( index ) {
|
|||||||
backend.log( "Index - " + index + ", InsertSample");
|
backend.log( "Index - " + index + ", InsertSample");
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertControl( index ) {
|
function insertControl( targetItem ) {
|
||||||
backend.log( "Index - " + index + ", InsertEN");
|
|
||||||
|
const gridC = document.getElementById("plate-container");
|
||||||
var existing_ens = document.getElementsByClassName("EN");
|
var existing_ens = document.getElementsByClassName("EN");
|
||||||
backend.log(existing_ens.length);
|
var en_num = existing_ens.length + 1;
|
||||||
|
const en_name = "EN" + en_num + "-" + rsl_plate_num;
|
||||||
|
var elem = document.createElement("div");
|
||||||
|
elem.setAttribute("id", en_name);
|
||||||
|
elem.setAttribute("class", "well negativecontrol EN");
|
||||||
|
elem.setAttribute("draggable", "true");
|
||||||
|
elem.innerHTML = '<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">' + en_name + '</p>'
|
||||||
|
gridC.insertBefore(elem, targetItem.nextSibling);
|
||||||
|
targetItem.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSample( index ) {
|
function removeSample( targetItem ) {
|
||||||
backend.log( "Index - " + index + ", RemoveSample");
|
const gridC = document.getElementById("plate-container");
|
||||||
|
var existing_wells = document.getElementsByClassName("well");
|
||||||
|
var en_num = existing_wells.length + 1;
|
||||||
|
var well_name = "blank_" + en_num;
|
||||||
|
var elem = document.createElement("div");
|
||||||
|
elem.setAttribute("id", well_name);
|
||||||
|
elem.setAttribute("class", "well");
|
||||||
|
elem.setAttribute("draggable", "true");
|
||||||
|
elem.innerHTML = '<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;"></p>'
|
||||||
|
gridC.insertBefore(elem, targetItem.nextSibling);
|
||||||
|
targetItem.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the app.
|
* Run the app.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,51 +1,55 @@
|
|||||||
|
|
||||||
|
|
||||||
const gridContainer = document.getElementById("plate-container");
|
const gridContainer = document.getElementById("plate-container");
|
||||||
let draggedItem = null;
|
let draggedItem = null;
|
||||||
|
|
||||||
|
//Handle Drag start
|
||||||
|
gridContainer.addEventListener("dragstart", (e) => {
|
||||||
|
|
||||||
//Handle Drag start
|
|
||||||
gridContainer.addEventListener("dragstart", (e) => {
|
|
||||||
draggedItem = e.target;
|
draggedItem = e.target;
|
||||||
draggedItem.style.opacity = "0.5";
|
draggedItem.style.opacity = "0.5";
|
||||||
});
|
});
|
||||||
|
|
||||||
//Handle Drag End
|
//Handle Drag End
|
||||||
gridContainer.addEventListener("dragend", (e) => {
|
gridContainer.addEventListener("dragend", (e) => {
|
||||||
e.target.style.opacity = "1";
|
e.target.style.opacity = "1";
|
||||||
draggedItem = null;
|
draggedItem = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
//handle dragging ove grid items
|
//handle dragging ove grid items
|
||||||
gridContainer.addEventListener("dragover", (e) => {
|
gridContainer.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Handle Drop
|
//Handle Drop
|
||||||
gridContainer.addEventListener("drop", (e) => {
|
gridContainer.addEventListener("drop", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const targetItem = e.target;
|
const targetItem = e.target;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
targetItem &&
|
targetItem &&
|
||||||
targetItem !== draggedItem &&
|
targetItem !== draggedItem //&&
|
||||||
targetItem.classList.contains("well")
|
//targetItem.classList.contains("well")
|
||||||
) {
|
) {
|
||||||
|
backend.log(targetItem.id);
|
||||||
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
|
const draggedIndex = [...gridContainer.children].indexOf(draggedItem);
|
||||||
const targetIndex = [...gridContainer.children].indexOf(targetItem);
|
const targetIndex = [...gridContainer.children].indexOf(targetItem);
|
||||||
if (draggedIndex < targetIndex) {
|
if (draggedIndex < targetIndex) {
|
||||||
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Lesser");
|
backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Lesser");
|
||||||
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
|
gridContainer.insertBefore(draggedItem, targetItem.nextSibling);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
backend.log_drag(draggedIndex.toString(), targetIndex.toString() + " Greater");
|
backend.log(draggedIndex.toString() + " " + targetIndex.toString() + " Greater");
|
||||||
gridContainer.insertBefore(draggedItem, targetItem);
|
gridContainer.insertBefore(draggedItem, targetItem);
|
||||||
|
|
||||||
}
|
}
|
||||||
output = [];
|
// output = [];
|
||||||
fullGrid = [...gridContainer.children];
|
// fullGrid = [...gridContainer.children];
|
||||||
fullGrid.forEach(function(item, index) {
|
// fullGrid.forEach(function(item, index) {
|
||||||
output.push({sample_id: item.id, index: index + 1})
|
// output.push({sample_id: item.id, index: index + 1})
|
||||||
});
|
// });
|
||||||
backend.rearrange_plate(output);
|
// backend.rearrange_plate(output);
|
||||||
|
rearrange_plate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -50,6 +50,17 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
|
<script>
|
||||||
|
var rsl_plate_num = "{{ run['plate_number'] }}";
|
||||||
|
function rearrange_plate() {
|
||||||
|
output = [];
|
||||||
|
fullGrid = [...gridContainer.children];
|
||||||
|
fullGrid.forEach(function(item, index) {
|
||||||
|
output.push({sample_id: item.id, index: index + 1})
|
||||||
|
});
|
||||||
|
backend.rearrange_plate(output);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<nav class="context-menu" id="context-menu">
|
<nav class="flexDiv context-menu" id="context-menu">
|
||||||
<h4 id="menu-header"></h4>
|
<h4 id="menu-header"></h4>
|
||||||
<ul class="context-menu__items">
|
<ul class="context-menu__items no-bullets">
|
||||||
<li class="context-menu__item">
|
<li class="context-menu__item">
|
||||||
<a href="#" class="context-menu__link" data-action="InsertSample">
|
<a href="#" class="context-menu__link" data-action="InsertSample">
|
||||||
<i class="fa fa-eye"></i> Insert Sample
|
<i class="fa fa-eye"></i> Insert Sample
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
{% for sample in samples %}
|
{% for sample in samples %}
|
||||||
<div class="well" draggable="true" id="{{ sample['well_id'] }}" style="background-color: {{ sample['background_color'] }};">
|
<div class="well" draggable="true" id="{{ sample['well_id'] }}" style="background-color: {{ sample['background_color'] }};">
|
||||||
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
<p style="font-size: 0.7em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}</p>
|
||||||
<!-- <div class="tooltip" style="font-size: 0.5em; text-align: center; word-wrap: break-word;">{{ sample['sample_id'] }}-->
|
|
||||||
<!-- <span class="tooltiptext">{{ sample['tooltip'] }}</span>-->
|
|
||||||
<!-- </div>--
|
|
||||||
grid-column-start: {{sample['column']}};
|
|
||||||
grid-column-end: {{sample['column']}};
|
|
||||||
grid-row-start: {{sample['row']}};
|
|
||||||
grid-row-end: {{sample['row']}};-->
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user