Getting results referencing ProcedureSampleAssociation
This commit is contained in:
@@ -14,7 +14,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
|
|||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator, timezone, \
|
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 typing import List, Literal, Generator, Any, Tuple, TYPE_CHECKING
|
||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -1053,6 +1053,7 @@ class ProcedureType(BaseClass):
|
|||||||
reagent_map = Column(JSON)
|
reagent_map = Column(JSON)
|
||||||
plate_columns = Column(INTEGER, default=0)
|
plate_columns = Column(INTEGER, default=0)
|
||||||
plate_rows = Column(INTEGER, default=0)
|
plate_rows = Column(INTEGER, default=0)
|
||||||
|
allowed_result_methods = Column(JSON)
|
||||||
|
|
||||||
procedure = relationship("Procedure",
|
procedure = relationship("Procedure",
|
||||||
back_populates="proceduretype") #: Concrete control of this type.
|
back_populates="proceduretype") #: Concrete control of this type.
|
||||||
@@ -1095,6 +1096,10 @@ class ProcedureType(BaseClass):
|
|||||||
cascade="all, delete-orphan"
|
cascade="all, delete-orphan"
|
||||||
) #: Association of tiproles
|
) #: 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]:
|
def construct_field_map(self, field: Literal['equipment', 'tip']) -> Generator[(str, dict), None, None]:
|
||||||
"""
|
"""
|
||||||
Make a map of all locations for tips or equipment.
|
Make a map of all locations for tips or equipment.
|
||||||
@@ -1223,9 +1228,10 @@ 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(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",
|
proceduretype_id = Column(INTEGER, ForeignKey("_proceduretype.id", ondelete="SET NULL",
|
||||||
name="fk_PRO_proceduretype_id")) #: client lab id from _organizations))
|
name="fk_PRO_proceduretype_id")) #: client lab id from _organizations))
|
||||||
proceduretype = relationship("ProcedureType", back_populates="procedure")
|
proceduretype = relationship("ProcedureType", back_populates="procedure")
|
||||||
@@ -1274,14 +1280,6 @@ 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:
|
||||||
@@ -1314,6 +1312,33 @@ class Procedure(BaseClass):
|
|||||||
output['name'] = self.name
|
output['name'] = self.name
|
||||||
return output
|
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):
|
class ProcedureTypeKitTypeAssociation(BaseClass):
|
||||||
@@ -2634,3 +2659,33 @@ class ProcedureTipsAssociation(BaseClass):
|
|||||||
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 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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ 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, Procedure, kittype_procedure
|
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 import relationship, validates, Query
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
@@ -2028,6 +2028,8 @@ class RunSampleAssociation(BaseClass):
|
|||||||
|
|
||||||
|
|
||||||
class ProcedureSampleAssociation(BaseClass):
|
class ProcedureSampleAssociation(BaseClass):
|
||||||
|
|
||||||
|
id = Column(INTEGER, primary_key=True)
|
||||||
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
|
||||||
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated equipment
|
sample_id = Column(INTEGER, ForeignKey("_sample.id"), primary_key=True) #: id of associated equipment
|
||||||
row = Column(INTEGER)
|
row = Column(INTEGER)
|
||||||
@@ -2038,3 +2040,29 @@ class ProcedureSampleAssociation(BaseClass):
|
|||||||
|
|
||||||
sample = relationship(Sample, back_populates="sampleprocedureassociation") #: associated equipment
|
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
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import logging, re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Generator, Tuple
|
||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
|
from pandas import DataFrame
|
||||||
from backend.validators import pydant
|
from backend.validators import pydant
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
class DefaultParser(object):
|
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):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__name__}<{self.filepath.stem}>"
|
return f"{self.__class__.__name__}<{self.filepath.stem}>"
|
||||||
|
|
||||||
def __init__(self, filepath: Path | str, range_dict: dict | None = None):
|
def __init__(self, filepath: Path | str, range_dict: dict | None = None, *args, **kwargs):
|
||||||
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}")
|
try:
|
||||||
|
self._pyd_object = getattr(pydant, f"Pyd{self.__class__.__name__.replace('Parser', '')}")
|
||||||
|
except AttributeError:
|
||||||
|
self._pyd_object = pydant.PydResults
|
||||||
if isinstance(filepath, str):
|
if isinstance(filepath, str):
|
||||||
self.filepath = Path(filepath)
|
self.filepath = Path(filepath)
|
||||||
else:
|
else:
|
||||||
@@ -30,5 +29,63 @@ class DefaultParser(object):
|
|||||||
self.range_dict = self.__class__.default_range_dict
|
self.range_dict = self.__class__.default_range_dict
|
||||||
else:
|
else:
|
||||||
self.range_dict = range_dict
|
self.range_dict = range_dict
|
||||||
|
for item in self.range_dict:
|
||||||
|
item['worksheet'] = self.workbook[item['sheet']]
|
||||||
|
|
||||||
|
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 *
|
from .submission_parser import *
|
||||||
99
src/submissions/backend/excel/parsers/pcr_parser.py
Normal file
99
src/submissions/backend/excel/parsers/pcr_parser.py
Normal 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
|
||||||
@@ -1,66 +1,50 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging, re
|
import logging
|
||||||
from pathlib import Path
|
from string import ascii_lowercase
|
||||||
from typing import Generator, Tuple
|
from typing import Generator
|
||||||
from pandas import DataFrame
|
from tools import row_keys
|
||||||
|
from . import DefaultKEYVALUEParser, DefaultTABLEParser
|
||||||
from . import DefaultParser
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
|
||||||
class ClientSubmissionParser(DefaultParser):
|
class ClientSubmissionParser(DefaultKEYVALUEParser):
|
||||||
"""
|
"""
|
||||||
Object for retrieving submitter info from "sample list" sheet
|
Object for retrieving submitter info from "sample list" sheet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filepath: Path | str, range_dict: dict | None = None):
|
default_range_dict = [dict(
|
||||||
super().__init__(filepath=filepath, range_dict=range_dict)
|
start_row=2,
|
||||||
self.worksheet = self.workbook[self.range_dict['sheet']]
|
end_row=18,
|
||||||
self.rows = range(self.range_dict['start_row'], self.range_dict['end_row'] + 1)
|
key_column=1,
|
||||||
|
value_column=2,
|
||||||
@property
|
sheet="Sample List"
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class SampleParser(DefaultParser):
|
|
||||||
|
class SampleParser(DefaultTABLEParser):
|
||||||
"""
|
"""
|
||||||
Object for retrieving submitter info from "sample list" sheet
|
Object for retrieving submitter info from "sample list" sheet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_range_dict = dict(
|
default_range_dict = [dict(
|
||||||
header_row=20,
|
header_row=20,
|
||||||
end_row=116,
|
end_row=116,
|
||||||
list_sheet="Sample 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')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parsed_info(self) -> Generator[dict, None, None]:
|
def parsed_info(self) -> Generator[dict, None, None]:
|
||||||
for ii, row in enumerate(self.list_df.iterrows()):
|
output = super().parsed_info
|
||||||
sample = {key.lower().replace(" ", "_"): value for key, value in row[1].to_dict().items()}
|
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
|
sample['submission_rank'] = ii + 1
|
||||||
yield sample
|
yield sample
|
||||||
|
|
||||||
|
|||||||
@@ -208,4 +208,4 @@ class RSLNamer(object):
|
|||||||
|
|
||||||
|
|
||||||
from .pydant import PydSubmission, PydKitType, PydContact, PydOrganization, PydSample, PydReagent, PydReagentRole, \
|
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
|
||||||
|
|||||||
@@ -780,7 +780,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
return missing_info, missing_reagents
|
return missing_info, missing_reagents
|
||||||
|
|
||||||
@report_result
|
@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
|
Converts this instance into a backend.db.models.procedure.BasicRun instance
|
||||||
|
|
||||||
@@ -791,7 +791,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
dicto = self.improved_dict()
|
dicto = self.improved_dict()
|
||||||
# logger.debug(f"Pydantic procedure type: {self.proceduretype['value']}")
|
# logger.debug(f"Pydantic procedure type: {self.proceduretype['value']}")
|
||||||
# logger.debug(f"Pydantic improved_dict: {pformat(dicto)}")
|
# 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'])
|
rsl_plate_num=self.rsl_plate_num['value'])
|
||||||
# logger.debug(f"Created or queried instance: {instance}")
|
# logger.debug(f"Created or queried instance: {instance}")
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@@ -1353,7 +1353,15 @@ class PydProcedure(PydBaseClass, arbitrary_types_allowed=True):
|
|||||||
def rescue_name(cls, value, values):
|
def rescue_name(cls, value, values):
|
||||||
if value['value'] == cls.model_fields['name'].default['value']:
|
if value['value'] == cls.model_fields['name'].default['value']:
|
||||||
if values.data['proceduretype']:
|
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
|
return value
|
||||||
|
|
||||||
@field_validator("possible_kits")
|
@field_validator("possible_kits")
|
||||||
@@ -1522,3 +1530,10 @@ class PydClientSubmission(PydBaseClass):
|
|||||||
"""
|
"""
|
||||||
from frontend.widgets.submission_widget import ClientSubmissionFormWidget
|
from frontend.widgets.submission_widget import ClientSubmissionFormWidget
|
||||||
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
|
return ClientSubmissionFormWidget(parent=parent, clientsubmission=self, samples=samples, disable=disable)
|
||||||
|
|
||||||
|
|
||||||
|
class PydResults(PydBaseClass, arbitrary_types_allowed=True):
|
||||||
|
|
||||||
|
results: dict = Field(default={})
|
||||||
|
parent: Sample|Procedure
|
||||||
|
|
||||||
|
|||||||
26
src/submissions/frontend/widgets/results/PCR.py
Normal file
26
src/submissions/frontend/widgets/results/PCR.py
Normal 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 =
|
||||||
|
|
||||||
|
|
||||||
7
src/submissions/frontend/widgets/results/__init__.py
Normal file
7
src/submissions/frontend/widgets/results/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class DefaultResults(object):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
from .PCR import pcr
|
||||||
@@ -231,27 +231,27 @@ class SubmissionsSheet(QTableView):
|
|||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
class ClientSubmissionDelegate(QStyledItemDelegate):
|
# class ClientSubmissionDelegate(QStyledItemDelegate):
|
||||||
|
#
|
||||||
def __init__(self, parent=None):
|
# def __init__(self, parent=None):
|
||||||
super(ClientSubmissionDelegate, self).__init__(parent)
|
# super(ClientSubmissionDelegate, self).__init__(parent)
|
||||||
pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
|
# pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
|
||||||
icon1 = QWidget().style().standardIcon(pixmapi)
|
# icon1 = QWidget().style().standardIcon(pixmapi)
|
||||||
pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton
|
# pixmapi = QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton
|
||||||
icon2 = QWidget().style().standardIcon(pixmapi)
|
# icon2 = QWidget().style().standardIcon(pixmapi)
|
||||||
self._plus_icon = icon1
|
# self._plus_icon = icon1
|
||||||
self._minus_icon = icon2
|
# self._minus_icon = icon2
|
||||||
|
#
|
||||||
def initStyleOption(self, option, index):
|
# def initStyleOption(self, option, index):
|
||||||
super(ClientSubmissionDelegate, self).initStyleOption(option, index)
|
# super(ClientSubmissionDelegate, self).initStyleOption(option, index)
|
||||||
if not index.parent().isValid():
|
# if not index.parent().isValid():
|
||||||
is_open = bool(option.state & QStyle.StateFlag.State_Open)
|
# is_open = bool(option.state & QStyle.StateFlag.State_Open)
|
||||||
option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration
|
# option.features |= QStyleOptionViewItem.ViewItemFeature.HasDecoration
|
||||||
option.icon = self._minus_icon if is_open else self._plus_icon
|
# option.icon = self._minus_icon if is_open else self._plus_icon
|
||||||
|
|
||||||
|
|
||||||
class RunDelegate(ClientSubmissionDelegate):
|
# class RunDelegate(ClientSubmissionDelegate):
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
|
|
||||||
class SubmissionsTree(QTreeView):
|
class SubmissionsTree(QTreeView):
|
||||||
@@ -262,11 +262,11 @@ class SubmissionsTree(QTreeView):
|
|||||||
def __init__(self, model, parent=None):
|
def __init__(self, model, parent=None):
|
||||||
super(SubmissionsTree, self).__init__(parent)
|
super(SubmissionsTree, self).__init__(parent)
|
||||||
self.total_count = ClientSubmission.__database_session__.query(ClientSubmission).count()
|
self.total_count = ClientSubmission.__database_session__.query(ClientSubmission).count()
|
||||||
self.setIndentation(0)
|
# self.setIndentation(0)
|
||||||
self.setExpandsOnDoubleClick(False)
|
self.setExpandsOnDoubleClick(False)
|
||||||
self.clicked.connect(self.on_clicked)
|
# self.clicked.connect(self.on_clicked)
|
||||||
delegate1 = ClientSubmissionDelegate(self)
|
# delegate1 = ClientSubmissionDelegate(self)
|
||||||
self.setItemDelegateForColumn(0, delegate1)
|
# 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)
|
||||||
@@ -276,14 +276,42 @@ class SubmissionsTree(QTreeView):
|
|||||||
self.doubleClicked.connect(self.show_details)
|
self.doubleClicked.connect(self.show_details)
|
||||||
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
# self.customContextMenuRequested.connect(self.open_menu)
|
# 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):
|
for ii in range(2):
|
||||||
self.resizeColumnToContents(ii)
|
self.resizeColumnToContents(ii)
|
||||||
|
|
||||||
@pyqtSlot(QModelIndex)
|
# @pyqtSlot(QModelIndex)
|
||||||
def on_clicked(self, index):
|
# def on_clicked(self, index):
|
||||||
if not index.parent().isValid() and index.column() == 0:
|
# if not index.parent().isValid() and index.column() == 0:
|
||||||
self.setExpanded(index, not self.isExpanded(index))
|
# 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):
|
def contextMenuEvent(self, event: QContextMenuEvent):
|
||||||
"""
|
"""
|
||||||
@@ -306,19 +334,30 @@ class SubmissionsTree(QTreeView):
|
|||||||
self.menu = QMenu(self)
|
self.menu = QMenu(self)
|
||||||
self.con_actions = query_obj.custom_context_events
|
self.con_actions = query_obj.custom_context_events
|
||||||
for key in self.con_actions.keys():
|
for key in self.con_actions.keys():
|
||||||
if key.lower() == "add procedure":
|
logger.debug(key)
|
||||||
action = QMenu(self.menu)
|
match key.lower():
|
||||||
action.setTitle("Add Procedure")
|
case "add procedure":
|
||||||
for procedure in query_obj.allowed_procedures:
|
action = QMenu(self.menu)
|
||||||
proc_name = procedure.name
|
action.setTitle("Add Procedure")
|
||||||
proc = QAction(proc_name, action)
|
for procedure in query_obj.allowed_procedures:
|
||||||
proc.triggered.connect(lambda _, procedure_name=proc_name: self.con_actions['Add Procedure'](obj=self, proceduretype_name=procedure_name))
|
proc_name = procedure.name
|
||||||
action.addAction(proc)
|
proc = QAction(proc_name, action)
|
||||||
self.menu.addMenu(action)
|
proc.triggered.connect(lambda _, procedure_name=proc_name: self.con_actions['Add Procedure'](obj=self, proceduretype_name=procedure_name))
|
||||||
else:
|
action.addAction(proc)
|
||||||
action = QAction(key, self)
|
self.menu.addMenu(action)
|
||||||
action.triggered.connect(lambda _, action_name=key: self.con_actions[action_name](obj=self))
|
case "add results":
|
||||||
self.menu.addAction(action)
|
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
|
# # NOTE: add other required actions
|
||||||
self.menu.popup(QCursor.pos())
|
self.menu.popup(QCursor.pos())
|
||||||
|
|
||||||
@@ -334,36 +373,27 @@ class SubmissionsTree(QTreeView):
|
|||||||
root = self.model.invisibleRootItem()
|
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']}"
|
||||||
submission_item = self.model.add_group(parent=root, item_data=dict(
|
submission_item = self.model.add_child(parent=root, child=dict(
|
||||||
name=group_str,
|
name=group_str,
|
||||||
query_str=submission['submitter_plate_id'],
|
query_str=submission['submitter_plate_id'],
|
||||||
item_type=ClientSubmission
|
item_type=ClientSubmission
|
||||||
))
|
))
|
||||||
|
logger.debug(f"Added {submission_item}")
|
||||||
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(
|
run_item = self.model.add_child(parent=submission_item, child=dict(
|
||||||
name=run['plate_number'],
|
name=run['plate_number'],
|
||||||
query_str=run['plate_number'],
|
query_str=run['plate_number'],
|
||||||
item_type=Run
|
item_type=Run
|
||||||
))
|
))
|
||||||
|
logger.debug(f"Added {run_item}")
|
||||||
for procedure in run['procedures']:
|
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'],
|
name=procedure['name'],
|
||||||
query_str=procedure['name'],
|
query_str=procedure['name'],
|
||||||
item_type=Procedure
|
item_type=Procedure
|
||||||
))
|
))
|
||||||
# root = self.model.invisibleRootItem()
|
logger.debug(f"Added {procedure_item}")
|
||||||
# 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):
|
def _populateTree(self, children, parent):
|
||||||
for child in children:
|
for child in children:
|
||||||
@@ -381,14 +411,20 @@ class SubmissionsTree(QTreeView):
|
|||||||
self.model.setRowCount(0) # works
|
self.model.setRowCount(0) # works
|
||||||
|
|
||||||
def show_details(self, sel: QModelIndex):
|
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)
|
# NOTE: Convert to data in id column (i.e. column 0)
|
||||||
id = id.sibling(id.row(), 1)
|
# id = id.sibling(id.row(), 1)
|
||||||
try:
|
indexes = self.selectedIndexes()
|
||||||
id = int(id.data())
|
|
||||||
except ValueError:
|
dicto = next((item.data(1) for item in indexes if item.data(1)))
|
||||||
return
|
logger.debug(dicto)
|
||||||
Run.query(id=id).show_details(self)
|
# 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):
|
def link_extractions(self):
|
||||||
pass
|
pass
|
||||||
@@ -402,59 +438,18 @@ class ClientSubmissionRunModel(QStandardItemModel):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(ClientSubmissionRunModel, self).__init__(parent)
|
super(ClientSubmissionRunModel, self).__init__(parent)
|
||||||
headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"]
|
# headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Signed By"]
|
||||||
self.setColumnCount(len(headers))
|
# self.setColumnCount(len(headers))
|
||||||
self.setHorizontalHeaderLabels(headers)
|
# self.setHorizontalHeaderLabels(headers)
|
||||||
|
|
||||||
def add_group(self, parent, item_data):
|
|
||||||
item_root = QStandardItem()
|
|
||||||
item_root.setEditable(False)
|
def add_child(self, parent: QStandardItem, child:dict):
|
||||||
item = QStandardItem(item_data['name'])
|
item = QStandardItem(child['name'])
|
||||||
|
item.setData(dict(item_type=child['item_type'], query_str=child['query_str']), 1)
|
||||||
|
parent.appendRow(item)
|
||||||
item.setEditable(False)
|
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
|
return item
|
||||||
|
|
||||||
def get_value(self, idx: int, column: int = 1):
|
def edit_item(self):
|
||||||
return self.item(idx, column)
|
pass
|
||||||
|
|
||||||
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)
|
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
var processSelection = document.getElementsByClassName('process');
|
var processSelection = document.getElementsByClassName('process');
|
||||||
|
|
||||||
for(let i = 0; i < processSelection.length; i++) {
|
for(let i = 0; i < processSelection.length; i++) {
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
}, false);
|
}, false);
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
var equipmentSelection = document.getElementsByClassName('equipment');
|
var equipmentSelection = document.getElementsByClassName('equipment');
|
||||||
|
|
||||||
for(let i = 0; i < equipmentSelection.length; i++) {
|
for(let i = 0; i < equipmentSelection.length; i++) {
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
}, false);
|
}, false);
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
@@ -23,9 +23,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
document.getElementById("save_btn").addEventListener("click", function(){
|
document.getElementById("save_btn").addEventListener("click", function(){
|
||||||
var new_lot = document.getElementById('lot').value
|
var new_lot = document.getElementById('lot').value
|
||||||
var new_exp = document.getElementById('expiry').value
|
var new_exp = document.getElementById('expiry').value
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
}, false);
|
}, false);
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
var sampleSelection = document.getElementsByClassName('sample');
|
var sampleSelection = document.getElementsByClassName('sample');
|
||||||
|
|
||||||
for(let i = 0; i < sampleSelection.length; i++) {
|
for(let i = 0; i < sampleSelection.length; i++) {
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
document.getElementById("sign_btn").addEventListener("click", function(){
|
document.getElementById("sign_btn").addEventListener("click", function(){
|
||||||
backend.sign_off("{{ sub['plate_number'] }}");
|
backend.sign_off("{{ sub['plate_number'] }}");
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -30,9 +30,10 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
{% for sample in samples %}
|
{% for sample in samples %}
|
||||||
document.getElementById("{{ sample['submission_rank'] }}_id").addEventListener("input", function(){
|
document.getElementById("{{ sample['submission_rank'] }}_id").addEventListener("input", function(){
|
||||||
backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);
|
backend.text_changed("{{ sample['submission_rank'] }}", this.name, this.value);
|
||||||
@@ -42,12 +43,6 @@
|
|||||||
backend.enable_sample("{{ sample['submission_rank'] }}", this.checked);
|
backend.enable_sample("{{ sample['submission_rank'] }}", this.checked);
|
||||||
});
|
});
|
||||||
{% endif %}
|
{% 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 %}
|
{% endfor %}
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
@@ -55,5 +50,5 @@
|
|||||||
document.getElementById("rsl_plate_num").addEventListener("input", function(){
|
document.getElementById("rsl_plate_num").addEventListener("input", function(){
|
||||||
backend.set_rsl_plate_num(this.value);
|
backend.set_rsl_plate_num(this.value);
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
@@ -21,9 +21,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
{% for submission in sample['submissions'] %}
|
{% for submission in sample['submissions'] %}
|
||||||
document.getElementById("{{ submission['plate_name'] }}").addEventListener("click", function(){
|
document.getElementById("{{ submission['plate_name'] }}").addEventListener("click", function(){
|
||||||
backend.submission_details("{{ submission['plate_name'] }}");
|
backend.submission_details("{{ submission['plate_name'] }}");
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
}, false);
|
}, false);
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
<script>
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
<script>
|
||||||
var processSelection = document.getElementsByClassName('process');
|
var processSelection = document.getElementsByClassName('process');
|
||||||
|
|
||||||
for(let i = 0; i < processSelection.length; i++) {
|
for(let i = 0; i < processSelection.length; i++) {
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
backend.activate_export(false);
|
backend.activate_export(false);
|
||||||
}, false);
|
}, false);
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user