Getting results referencing ProcedureSampleAssociation

This commit is contained in:
lwark
2025-06-09 11:05:10 -05:00
parent db0b65b07b
commit 10d4c9f155
17 changed files with 476 additions and 208 deletions

View File

@@ -14,7 +14,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property
from datetime import date, datetime, timedelta
from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator, timezone, \
jinja_template_loading
jinja_template_loading, ctx
from typing import List, Literal, Generator, Any, Tuple, TYPE_CHECKING
from pandas import ExcelFile
from pathlib import Path
@@ -1053,6 +1053,7 @@ class ProcedureType(BaseClass):
reagent_map = Column(JSON)
plate_columns = Column(INTEGER, default=0)
plate_rows = Column(INTEGER, default=0)
allowed_result_methods = Column(JSON)
procedure = relationship("Procedure",
back_populates="proceduretype") #: Concrete control of this type.
@@ -1095,6 +1096,10 @@ class ProcedureType(BaseClass):
cascade="all, delete-orphan"
) #: Association of tiproles
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.allowed_result_methods = dict()
def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
"""
Make a map of all locations for tips or equipment.
@@ -1223,9 +1228,10 @@ class ProcedureType(BaseClass):
class Procedure(BaseClass):
id = Column(INTEGER, primary_key=True)
_name = Column(String, unique=True)
name = Column(String, unique=True)
repeat = Column(INTEGER, nullable=False)
technician = Column(JSON) #: name of processing tech(s)
technician = Column(String(64)) #: name of processing tech(s)
results = relationship("Results", back_populates="procedure", uselist=True)
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")
@@ -1274,14 +1280,6 @@ class Procedure(BaseClass):
tips = association_proxy("proceduretipsassociation",
"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')
def validate_repeat(self, key, value):
if value > 1:
@@ -1314,6 +1312,33 @@ class Procedure(BaseClass):
output['name'] = self.name
return output
@property
def custom_context_events(self) -> dict:
"""
Creates dictionary of str:function to be passed to context menu
Returns:
dict: dictionary of functions
"""
names = ["Add Results", "Edit", "Add Comment", "Show Details", "Delete"]
return {item: self.__getattribute__(item.lower().replace(" ", "_")) for item in names}
def add_results(self, obj, resultstype_name:str):
logger.debug(f"Add Results! {resultstype_name}")
def edit(self, obj):
logger.debug("Edit!")
def add_comment(self, obj):
logger.debug("Add Comment!")
def show_details(self, obj):
logger.debug("Show Details!")
def delete(self, obj):
logger.debug("Delete!")
class ProcedureTypeKitTypeAssociation(BaseClass):
@@ -2634,3 +2659,33 @@ class ProcedureTipsAssociation(BaseClass):
return PydTips(name=self.tips.name, lot=self.tips.lot, role=self.role_name)
class Results(BaseClass):
id = Column(INTEGER, primary_key=True)
result = Column(JSON)
procedure_id = Column(INTEGER, ForeignKey("_procedure.id", ondelete='SET NULL',
name="fk_RES_procedure_id"))
procedure = relationship("Procedure", back_populates="results")
assoc_id = Column(INTEGER, ForeignKey("_proceduresampleassociation.id", ondelete='SET NULL',
name="fk_RES_ASSOC_id"))
sampleprocedureassociation = relationship("ProcedureSampleAssociation", back_populates="results")
_img = Column(String(128))
@property
def image(self) -> bytes:
dir = self.__directory_path__.joinpath("submission_imgs.zip")
try:
assert dir.exists()
except AssertionError:
raise FileNotFoundError(f"{dir} not found.")
logger.debug(f"Getting image from {self.__directory_path__}")
with zipfile.ZipFile(dir) as zf:
with zf.open(self._img) as f:
return f.read()
@image.setter
def image(self, value):
self._img = value

View File

@@ -16,7 +16,7 @@ from pprint import pformat
from pandas import DataFrame
from sqlalchemy.ext.hybrid import hybrid_property
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, Sequence
from sqlalchemy.orm import relationship, validates, Query
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.ext.associationproxy import association_proxy
@@ -2028,6 +2028,8 @@ class RunSampleAssociation(BaseClass):
class ProcedureSampleAssociation(BaseClass):
id = Column(INTEGER, primary_key=True)
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)
@@ -2038,3 +2040,29 @@ class ProcedureSampleAssociation(BaseClass):
sample = relationship(Sample, back_populates="sampleprocedureassociation") #: associated equipment
results = relationship("Results", back_populates="sampleprocedureassociation")
# def __init__(self, new_id:int|None=None, **kwarg):
# if new_id:
# self.id = new_id
# else:
# self.id = self.__class__.autoincrement_id()
# # new_id = self.__class__.autoincrement_id()
# super().__init__(**kwarg)
@classmethod
def autoincrement_id(cls) -> int:
"""
Increments the association id automatically
Returns:
int: incremented id
"""
try:
return max([item.id for item in cls.query()]) + 1
except ValueError as e:
logger.error(f"Problem incrementing id: {e}")
return 1

View File

@@ -1,26 +1,25 @@
"""
"""
import logging, re
from pathlib import Path
from typing import Generator, Tuple
from openpyxl import load_workbook
from pandas import DataFrame
from backend.validators import pydant
logger = logging.getLogger(f"submissions.{__name__}")
class DefaultParser(object):
default_range_dict = dict(
start_row=2,
end_row=18,
key_column=1,
value_column=2,
sheet="Sample List"
)
def __repr__(self):
return f"{self.__class__.__name__}<{self.filepath.stem}>"
def __init__(self, filepath: Path | str, range_dict: dict | None = None):
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}")
def __init__(self, filepath: Path | str, range_dict: dict | None = None, *args, **kwargs):
try:
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}")
except AttributeError:
self._pyd_object = pydant.PydResults
if isinstance(filepath, str):
self.filepath = Path(filepath)
else:
@@ -30,5 +29,63 @@ class DefaultParser(object):
self.range_dict = self.__class__.default_range_dict
else:
self.range_dict = range_dict
for item in self.range_dict:
item['worksheet'] = self.workbook[item['sheet']]
from .submission_parser import *
def to_pydantic(self):
data = {key: value for key, value in self.parsed_info}
data['filepath'] = self.filepath
return self._pyd_object(**data)
class DefaultKEYVALUEParser(DefaultParser):
default_range_dict = [dict(
start_row=2,
end_row=18,
key_column=1,
value_column=2,
sheet="Sample List"
)]
@property
def parsed_info(self) -> Generator[Tuple, None, None]:
for item in self.range_dict:
rows = range(item['start_row'], item['end_row'] + 1)
for row in rows:
key = item['worksheet'].cell(row, item['key_column']).value
if key:
# Note: Remove anything in brackets.
key = re.sub(r"\(.*\)", "", key)
key = key.lower().replace(":", "").strip().replace(" ", "_")
value = item['worksheet'].cell(row, item['value_column']).value
value = dict(value=value, missing=False if value else True)
yield key, value
class DefaultTABLEParser(DefaultParser):
default_range_dict = [dict(
header_row=20,
end_row=116,
sheet="Sample List"
)]
@property
def parsed_info(self):
for item in self.range_dict:
list_worksheet = self.workbook[item['sheet']]
list_df = DataFrame([item for item in list_worksheet.values][item['header_row'] - 1:])
list_df.columns = list_df.iloc[0]
list_df = list_df[1:]
list_df = list_df.dropna(axis=1, how='all')
for ii, row in enumerate(list_df.iterrows()):
output = {key.lower().replace(" ", "_"): value for key, value in row[1].to_dict().items()}
yield output
def to_pydantic(self, **kwargs):
return [self._pyd_object(**output) for output in self.parsed_info]
from .submission_parser import *

View File

@@ -0,0 +1,99 @@
"""
"""
import logging, re, sys
from pprint import pformat
from pathlib import Path
from typing import Generator, Tuple
from openpyxl import load_workbook
from backend.db.models import Run, Sample
from . import DefaultKEYVALUEParser, DefaultTABLEParser
logger = logging.getLogger(f"submissions.{__name__}")
class PCRSampleParser(DefaultTABLEParser):
"""Object to pull data from Design and Analysis PCR export file."""
default_range_dict = [dict(
header_row=25,
sheet="Results"
)]
@property
def parsed_info(self):
output = [item for item in super().parsed_info]
merge_column = "sample"
sample_names = list(set([item['sample'] for item in output]))
for sample in sample_names:
multi = dict()
sois = (item for item in output if item['sample']==sample)
for soi in sois:
multi[soi['target']] = {k:v for k, v in soi.items() if k != "target"}
yield (sample, multi)
def to_pydantic(self):
for key, sample_info in self.parsed_info:
sample_obj = Sample.query(sample_id=key)
if sample_obj and not isinstance(sample_obj, list):
yield self._pyd_object(results=sample_info, parent=sample_obj)
else:
continue
class PCRInfoParser(DefaultKEYVALUEParser):
default_range_dict = [dict(
start_row=1,
end_row=24,
key_column=1,
value_column=2,
sheet="Results"
)]
# def __init__(self, filepath: Path | str, range_dict: dict | None = None):
# super().__init__(filepath=filepath, range_dict=range_dict)
# self.worksheet = self.workbook[self.range_dict['sheet']]
# self.rows = range(self.range_dict['start_row'], self.range_dict['end_row'] + 1)
#
# @property
# def parsed_info(self) -> Generator[Tuple, None, None]:
# for row in self.rows:
# key = self.worksheet.cell(row, self.range_dict['key_column']).value
# if key:
# key = re.sub(r"\(.*\)", "", key)
# key = key.lower().replace(":", "").strip().replace(" ", "_")
# value = self.worksheet.cell(row, self.range_dict['value_column']).value
# value = dict(value=value, missing=False if value else True)
# yield key, value
#
def to_pydantic(self):
from backend.db.models import Procedure
data = {key: value for key, value in self.parsed_info}
data['filepath'] = self.filepath
return self._pyd_object(**data, parent=Procedure)
# @property
# def pcr_info(self) -> dict:
# """
# Parse general info rows for all types of PCR results
# """
# info_map = self.submission_obj.get_submission_type().sample_map['pcr_general_info']
# sheet = self.xl[info_map['sheet']]
# iter_rows = sheet.iter_rows(min_row=info_map['start_row'], max_row=info_map['end_row'])
# pcr = {}
# for row in iter_rows:
# try:
# key = row[0].value.lower().replace(' ', '_')
# except AttributeError as e:
# logger.error(f"No key: {row[0].value} due to {e}")
# continue
# value = row[1].value or ""
# pcr[key] = value
# pcr['imported_by'] = getuser()
# return pcr

View File

@@ -1,66 +1,50 @@
"""
"""
import logging, re
from pathlib import Path
from typing import Generator, Tuple
from pandas import DataFrame
from . import DefaultParser
import logging
from string import ascii_lowercase
from typing import Generator
from tools import row_keys
from . import DefaultKEYVALUEParser, DefaultTABLEParser
logger = logging.getLogger(f"submissions.{__name__}")
class ClientSubmissionParser(DefaultParser):
class ClientSubmissionParser(DefaultKEYVALUEParser):
"""
Object for retrieving submitter info from "sample list" sheet
"""
def __init__(self, filepath: Path | str, range_dict: dict | None = None):
super().__init__(filepath=filepath, range_dict=range_dict)
self.worksheet = self.workbook[self.range_dict['sheet']]
self.rows = range(self.range_dict['start_row'], self.range_dict['end_row'] + 1)
@property
def parsed_info(self) -> Generator[Tuple, None, None]:
for row in self.rows:
key = self.worksheet.cell(row, self.range_dict['key_column']).value
if key:
key = re.sub(r"\(.*\)", "", key)
key = key.lower().replace(":", "").strip().replace(" ", "_")
value = self.worksheet.cell(row, self.range_dict['value_column']).value
value = dict(value=value, missing=False if value else True)
yield key, value
def to_pydantic(self):
data = {key: value for key, value in self.parsed_info}
data['filepath'] = self.filepath
return self._pyd_object(**data)
default_range_dict = [dict(
start_row=2,
end_row=18,
key_column=1,
value_column=2,
sheet="Sample List"
)]
class SampleParser(DefaultParser):
class SampleParser(DefaultTABLEParser):
"""
Object for retrieving submitter info from "sample list" sheet
"""
default_range_dict = dict(
default_range_dict = [dict(
header_row=20,
end_row=116,
list_sheet="Sample List"
)
def __init__(self, filepath: Path | str, range_dict: dict | None = None):
super().__init__(filepath=filepath, range_dict=range_dict)
self.list_worksheet = self.workbook[self.range_dict['list_sheet']]
self.list_df = DataFrame([item for item in self.list_worksheet.values][self.range_dict['header_row'] - 1:])
self.list_df.columns = self.list_df.iloc[0]
self.list_df = self.list_df[1:]
self.list_df = self.list_df.dropna(axis=1, how='all')
sheet="Sample List"
)]
@property
def parsed_info(self) -> Generator[dict, None, None]:
for ii, row in enumerate(self.list_df.iterrows()):
sample = {key.lower().replace(" ", "_"): value for key, value in row[1].to_dict().items()}
output = super().parsed_info
for ii, sample in enumerate(output):
if isinstance(sample["row"], str) and sample["row"].lower() in ascii_lowercase[0:8]:
try:
sample["row"] = row_keys[sample["row"]]
except KeyError:
pass
sample['submission_rank'] = ii + 1
yield sample

View File

@@ -208,4 +208,4 @@ class RSLNamer(object):
from .pydant import PydSubmission, PydKitType, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
PydEquipment, PydEquipmentRole, PydTips, PydProcess, PydElastic, PydClientSubmission, PydProcedure
PydEquipment, PydEquipmentRole, PydTips, PydProcess, PydElastic, PydClientSubmission, PydProcedure, PydResults

View File

@@ -780,7 +780,7 @@ class PydSubmission(BaseModel, extra='allow'):
return missing_info, missing_reagents
@report_result
def to_sql(self) -> Tuple[BasicRun | None, Report]:
def to_sql(self) -> Tuple[Run | None, Report]:
"""
Converts this instance into a backend.db.models.procedure.BasicRun instance
@@ -791,7 +791,7 @@ class PydSubmission(BaseModel, extra='allow'):
dicto = self.improved_dict()
# logger.debug(f"Pydantic procedure type: {self.proceduretype['value']}")
# logger.debug(f"Pydantic improved_dict: {pformat(dicto)}")
instance, result = BasicRun.query_or_create(submissiontype=self.submission_type['value'],
instance, result = Run.query_or_create(submissiontype=self.submission_type['value'],
rsl_plate_num=self.rsl_plate_num['value'])
# logger.debug(f"Created or queried instance: {instance}")
if instance is None:
@@ -1353,7 +1353,15 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
def rescue_name(cls, value, values):
if value['value'] == cls.model_fields['name'].default['value']:
if values.data['proceduretype']:
value['value'] = values.data['proceduretype'].name
procedure_type = values.data['proceduretype'].name
else:
procedure_type = None
if values.data['run']:
run = values.data['run'].rsl_plate_num
else:
run = None
value['value'] = f"{procedure_type}-{run}"
value['missing'] = True
return value
@field_validator("possible_kits")
@@ -1522,3 +1530,10 @@ class PydClientSubmission(PydBaseClass):
"""
from frontend.widgets.submission_widget import ClientSubmissionFormWidget
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
class PydResults(PydBaseClass, arbitrary_types_allowed=True):
results: dict = Field(default={})
parent: Sample|Procedure

View File

@@ -0,0 +1,26 @@
"""
"""
from pathlib import Path
from backend.validators import PydResults
from backend.db.models import Procedure, Results
from backend.excel.parsers.pcr_parser import PCRSampleParser, PCRInfoParser
from frontend.widgets.functions import select_open_file
from . import DefaultResults
class PCR(DefaultResults):
def __init__(self, procedure: Procedure, fname:Path|str|None=None):
self.procedure = procedure
if not fname:
self.fname = select_open_file(file_extension="xlsx")
elif isinstance(fname, str):
self.fname = Path(fname)
self.info_parser = PCRInfoParser(filepath=fname)
self.sample_parser = PCRSampleParser(filepath=fname)
def build_procedure(self):
results = PydResults(parent=self.procedure)
results.results =

View File

@@ -0,0 +1,7 @@
class DefaultResults(object):
pass
from .PCR import pcr

View File

@@ -231,27 +231,27 @@ class SubmissionsSheet(QTableView):
return report
class ClientSubmissionDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(ClientSubmissionDelegate, self).__init__(parent)
pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
icon1 = QWidget().style().standardIcon(pixmapi)
pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton
icon2 = QWidget().style().standardIcon(pixmapi)
self._plus_icon = icon1
self._minus_icon = icon2
def initStyleOption(self, option, index):
super(ClientSubmissionDelegate, self).initStyleOption(option, index)
if not index.parent().isValid():
is_open = bool(option.state & QStyle.StateFlag.State_Open)
option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration
option.icon = self._minus_icon if is_open else self._plus_icon
# class ClientSubmissionDelegate(QStyledItemDelegate):
#
# def __init__(self, parent=None):
# super(ClientSubmissionDelegate, self).__init__(parent)
# pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
# icon1 = QWidget().style().standardIcon(pixmapi)
# pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton
# icon2 = QWidget().style().standardIcon(pixmapi)
# self._plus_icon = icon1
# self._minus_icon = icon2
#
# def initStyleOption(self, option, index):
# super(ClientSubmissionDelegate, self).initStyleOption(option, index)
# if not index.parent().isValid():
# is_open = bool(option.state & QStyle.StateFlag.State_Open)
# option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration
# option.icon = self._minus_icon if is_open else self._plus_icon
class RunDelegate(ClientSubmissionDelegate):
pass
# class RunDelegate(ClientSubmissionDelegate):
# pass
class SubmissionsTree(QTreeView):
@@ -262,11 +262,11 @@ class SubmissionsTree(QTreeView):
def __init__(self, model, parent=None):
super(SubmissionsTree, self).__init__(parent)
self.total_count = ClientSubmission.__database_session__.query(ClientSubmission).count()
self.setIndentation(0)
# self.setIndentation(0)
self.setExpandsOnDoubleClick(False)
self.clicked.connect(self.on_clicked)
delegate1 = ClientSubmissionDelegate(self)
self.setItemDelegateForColumn(0, delegate1)
# self.clicked.connect(self.on_clicked)
# delegate1 = ClientSubmissionDelegate(self)
# self.setItemDelegateForColumn(0, delegate1)
self.model = model
self.setModel(self.model)
# self.header().setSectionResizeMode(0, QHeaderView.sectionResizeMode(self,0).ResizeToContents)
@@ -276,14 +276,42 @@ class SubmissionsTree(QTreeView):
self.doubleClicked.connect(self.show_details)
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
# self.customContextMenuRequested.connect(self.open_menu)
self.setStyleSheet("""
QTreeView {
background-color: #f5f5f5;
alternate-background-color: "#cfe2f3";
border: 1px solid #d3d3d3;
}
QTreeView::item {
padding: 5px;
border-bottom: 1px solid #d3d3d3;
}
QTreeView::item:selected {
background-color: #0078d7;
color: white;
}
""")
# Enable alternating row colors
self.setAlternatingRowColors(True)
self.setIndentation(20)
self.setItemsExpandable(True)
self.expanded.connect(self.expand_item)
for ii in range(2):
self.resizeColumnToContents(ii)
@pyqtSlot(QModelIndex)
def on_clicked(self, index):
if not index.parent().isValid() and index.column() == 0:
self.setExpanded(index, not self.isExpanded(index))
# @pyqtSlot(QModelIndex)
# def on_clicked(self, index):
# if not index.parent().isValid() and index.column() == 0:
# self.setExpanded(index, not self.isExpanded(index))
def expand_item(self, event: QModelIndex):
logger.debug(f"Data: {event.data()}")
logger.debug(f"Parent {event.parent().data()}")
logger.debug(f"Row: {event.row()}")
logger.debug(f"Sibling: {event.siblingAtRow(event.row()).data()}")
logger.debug(f"Model: {event.model().event()}")
def contextMenuEvent(self, event: QContextMenuEvent):
"""
@@ -306,19 +334,30 @@ class SubmissionsTree(QTreeView):
self.menu = QMenu(self)
self.con_actions = query_obj.custom_context_events
for key in self.con_actions.keys():
if key.lower() == "add procedure":
action = QMenu(self.menu)
action.setTitle("Add Procedure")
for procedure in query_obj.allowed_procedures:
proc_name = procedure.name
proc = QAction(proc_name, action)
proc.triggered.connect(lambda _, procedure_name=proc_name: self.con_actions['Add Procedure'](obj=self, proceduretype_name=procedure_name))
action.addAction(proc)
self.menu.addMenu(action)
else:
action = QAction(key, self)
action.triggered.connect(lambda _, action_name=key: self.con_actions[action_name](obj=self))
self.menu.addAction(action)
logger.debug(key)
match key.lower():
case "add procedure":
action = QMenu(self.menu)
action.setTitle("Add Procedure")
for procedure in query_obj.allowed_procedures:
proc_name = procedure.name
proc = QAction(proc_name, action)
proc.triggered.connect(lambda _, procedure_name=proc_name: self.con_actions['Add Procedure'](obj=self, proceduretype_name=procedure_name))
action.addAction(proc)
self.menu.addMenu(action)
case "add results":
action = QMenu(self.menu)
action.setTitle("Add Results")
for results in query_obj.proceduretype.allowed_result_methods:
res_name = results
res = QAction(res_name, action)
res.triggered.connect(lambda _, procedure_name=res_name: self.con_actions['Add Results'](obj=self, resultstype_name=procedure_name))
action.addAction(res)
self.menu.addMenu(action)
case _:
action = QAction(key, self)
action.triggered.connect(lambda _, action_name=key: self.con_actions[action_name](obj=self))
self.menu.addAction(action)
# # NOTE: add other required actions
self.menu.popup(QCursor.pos())
@@ -334,36 +373,27 @@ class SubmissionsTree(QTreeView):
root = self.model.invisibleRootItem()
for submission in self.data:
group_str = f"{submission['submissiontype']}-{submission['submitter_plate_id']}-{submission['submitted_date']}"
submission_item = self.model.add_group(parent=root, item_data=dict(
submission_item = self.model.add_child(parent=root, child=dict(
name=group_str,
query_str=submission['submitter_plate_id'],
item_type=ClientSubmission
))
logger.debug(f"Added {submission_item}")
for run in submission['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(
run_item = self.model.add_child(parent=submission_item, child=dict(
name=run['plate_number'],
query_str=run['plate_number'],
item_type=Run
))
logger.debug(f"Added {run_item}")
for procedure in run['procedures']:
self.model.add_group(parent=run_item, item_data=dict(
procedure_item = self.model.add_child(parent=run_item, child=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())
logger.debug(f"Added {procedure_item}")
def _populateTree(self, children, parent):
for child in children:
@@ -381,14 +411,20 @@ class SubmissionsTree(QTreeView):
self.model.setRowCount(0) # works
def show_details(self, sel: QModelIndex):
id = self.selectionModel().currentIndex()
# id = self.selectionModel().currentIndex()
# NOTE: Convert to data in id column (i.e. column 0)
id = id.sibling(id.row(), 1)
try:
id = int(id.data())
except ValueError:
return
Run.query(id=id).show_details(self)
# id = id.sibling(id.row(), 1)
indexes = self.selectedIndexes()
dicto = next((item.data(1) for item in indexes if item.data(1)))
logger.debug(dicto)
# try:
# id = int(id.data())
# except ValueError:
# return
# Run.query(id=id).show_details(self)
obj = dicto['item_type'].query(name=dicto['query_str'], limit=1)
logger.debug(obj)
def link_extractions(self):
pass
@@ -402,59 +438,18 @@ class ClientSubmissionRunModel(QStandardItemModel):
def __init__(self, parent=None):
super(ClientSubmissionRunModel, self).__init__(parent)
headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"]
self.setColumnCount(len(headers))
self.setHorizontalHeaderLabels(headers)
# headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"]
# self.setColumnCount(len(headers))
# self.setHorizontalHeaderLabels(headers)
def add_group(self, parent, item_data):
item_root = QStandardItem()
item_root.setEditable(False)
item = QStandardItem(item_data['name'])
def add_child(self, parent: QStandardItem, child:dict):
item = QStandardItem(child['name'])
item.setData(dict(item_type=child['item_type'], query_str=child['query_str']), 1)
parent.appendRow(item)
item.setEditable(False)
i = parent.rowCount()
for j, it in enumerate((item_root, item)):
# NOTE: Adding item to invisible root row i, column j (wherever j comes from)
parent.setChild(i, j, it)
parent.setEditable(False)
for j in range(self.columnCount()):
it = parent.child(i, j)
if it is None:
# NOTE: Set invisible root child to empty if it is None.
it = QStandardItem()
parent.setChild(i, j, it)
item_root.setData(dict(item_type=item_data['item_type'], query_str=item_data['query_str']), 1)
return item_root
def append_element_to_group(self, group_item, item_data: dict):
# logger.debug(f"Element: {pformat(element)}")
j = group_item.rowCount()
item_icon = QStandardItem()
item_icon.setEditable(False)
# item_icon.setBackground(QColor("#0D1225"))
# item_icon.setData(dict(item_type="Run", query_str=element['plate_number']), 1)
# group_item.setChild(j, 0, item_icon)
for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i)
try:
key = it.text().lower().replace(" ", "_")
except AttributeError:
key = None
if not key:
continue
value = str(item_data[key])
item = QStandardItem(value)
item.setBackground(QColor("#CFE2F3"))
item.setEditable(False)
# item_icon.setChild(j, i, item)
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, 1, QStandardItem("B"))
return item
def get_value(self, idx: int, column: int = 1):
return self.item(idx, column)
def query_group_object(self, idx: int):
row_obj = self.get_value(idx)
logger.debug(row_obj.query_str)
return self.sql_object.query(name=row_obj.query_str, limit=1)
def edit_item(self):
pass

View File

@@ -23,10 +23,10 @@
{% endif %}
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
var processSelection = document.getElementsByClassName('process');
for(let i = 0; i < processSelection.length; i++) {
@@ -45,6 +45,7 @@
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
}, false);
</script>
{% endblock %}
</script>
</html>

View File

@@ -23,10 +23,10 @@
{% endif %}
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
var equipmentSelection = document.getElementsByClassName('equipment');
for(let i = 0; i < equipmentSelection.length; i++) {
@@ -44,6 +44,7 @@
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
}, false);
</script>
{% endblock %}
</script>
</html>

View File

@@ -23,9 +23,10 @@
{% endif %}
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
document.getElementById("save_btn").addEventListener("click", function(){
var new_lot = document.getElementById('lot').value
var new_exp = document.getElementById('expiry').value
@@ -39,6 +40,7 @@
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
}, false);
</script>
{% endblock %}
</script>
</html>

View File

@@ -77,10 +77,10 @@
<br>
<br>
</body>
<script>
{% block script %}
{{ super() }}
<script>
var sampleSelection = document.getElementsByClassName('sample');
for(let i = 0; i < sampleSelection.length; i++) {
@@ -129,7 +129,7 @@
document.getElementById("sign_btn").addEventListener("click", function(){
backend.sign_off("{{ sub['plate_number'] }}");
});
</script>
{% endblock %}
</script>
</html>

View File

@@ -30,9 +30,10 @@
</form>
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
{% for sample in samples %}
document.getElementById("{{ sample['submission_rank'] }}_id").addEventListener("input", function(){
backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);
@@ -42,12 +43,6 @@
backend.enable_sample("{{ sample['submission_rank'] }}", this.checked);
});
{% endif %}
<!-- document.getElementById("{{ sample['submission_rank'] }}_row").addEventListener("input", function(){-->
<!-- backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);-->
<!-- });-->
<!-- document.getElementById("{{ sample['submission_rank'] }}_column").addEventListener("input", function(){-->
<!-- backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);-->
<!-- });-->
{% endfor %}
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
@@ -55,5 +50,5 @@
document.getElementById("rsl_plate_num").addEventListener("input", function(){
backend.set_rsl_plate_num(this.value);
});
</script>
{% endblock %}
</script>

View File

@@ -21,9 +21,10 @@
{% endif %}
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
{% for submission in sample['submissions'] %}
document.getElementById("{{ submission['plate_name'] }}").addEventListener("click", function(){
backend.submission_details("{{ submission['plate_name'] }}");
@@ -32,6 +33,7 @@
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
}, false);
</script>
{% endblock %}
</script>
</html>

View File

@@ -23,10 +23,10 @@
{% endif %}
{% endblock %}
</body>
<script>
{% block script %}
{{ super() }}
<script>
var processSelection = document.getElementsByClassName('process');
for(let i = 0; i < processSelection.length; i++) {
@@ -45,6 +45,7 @@
document.addEventListener('DOMContentLoaded', function() {
backend.activate_export(false);
}, false);
</script>
{% endblock %}
</script>
</html>