placeholder

This commit is contained in:
lwark
2025-05-09 14:35:53 -05:00
parent 20952f2edd
commit 8a16738e93
6 changed files with 225 additions and 34 deletions

View File

@@ -389,13 +389,13 @@ class BaseClass(Base):
except AttributeError: except AttributeError:
return super().__setattr__(key, value) return super().__setattr__(key, value)
if isinstance(field_type, InstrumentedAttribute): if isinstance(field_type, InstrumentedAttribute):
# logger.debug(f"{key} is an InstrumentedAttribute.") logger.debug(f"{key} is an InstrumentedAttribute.")
match field_type.property: match field_type.property:
case ColumnProperty(): case ColumnProperty():
# logger.debug(f"Setting ColumnProperty to {value}") logger.debug(f"Setting ColumnProperty to {value}")
return super().__setattr__(key, value) return super().__setattr__(key, value)
case _RelationshipDeclared(): case _RelationshipDeclared():
# logger.debug(f"{self.__class__.__name__} Setting _RelationshipDeclared for {key} to {value}") logger.debug(f"{self.__class__.__name__} Setting _RelationshipDeclared for {key} to {value}")
if field_type.property.uselist: if field_type.property.uselist:
logger.debug(f"Setting with uselist") logger.debug(f"Setting with uselist")
existing = self.__getattribute__(key) existing = self.__getattribute__(key)
@@ -409,7 +409,8 @@ class BaseClass(Base):
value = existing + [value] value = existing + [value]
else: else:
if isinstance(value, list): if isinstance(value, list):
value = value # value = value
pass
else: else:
value = [value] value = [value]
value = list(set(value)) value = list(set(value))
@@ -421,7 +422,13 @@ class BaseClass(Base):
value = value[0] value = value[0]
else: else:
raise ValueError("Object is too long to parse a single value.") raise ValueError("Object is too long to parse a single value.")
return super().__setattr__(key, value) try:
return super().__setattr__(key, value)
except AttributeError:
logger.debug(f"Possible attempt to set relationship to simple var type.")
relationship_class = field_type.property.entity.entity
value = relationship_class.query(name=value)
return super().__setattr__(key, value)
case _: case _:
return super().__setattr__(key, value) return super().__setattr__(key, value)
else: else:

View File

@@ -46,7 +46,7 @@ class ClientSubmission(BaseClass, LogMixin):
""" """
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
submitter_plate_num = Column(String(127), unique=True) #: The number given to the submission by the submitting lab submitter_plate_id = Column(String(127), unique=True) #: The number given to the submission by the submitting lab
submitted_date = Column(TIMESTAMP) #: Date submission received submitted_date = Column(TIMESTAMP) #: Date submission received
submitting_lab = relationship("Organization", back_populates="submissions") #: client org submitting_lab = relationship("Organization", back_populates="submissions") #: client org
submitting_lab_id = Column(INTEGER, ForeignKey("_organization.id", ondelete="SET NULL", submitting_lab_id = Column(INTEGER, ForeignKey("_organization.id", ondelete="SET NULL",
@@ -56,7 +56,7 @@ class ClientSubmission(BaseClass, LogMixin):
sample_count = Column(INTEGER) #: Number of samples in the submission sample_count = Column(INTEGER) #: Number of samples in the submission
comment = Column(JSON) comment = Column(JSON)
runs = relationship("BasicSubmission", back_populates="client_submission") #: many-to-one relationship runs = relationship("BasicSubmission", back_populates="client_submission") #: many-to-one relationship
misc_info = Column(JSON)
contact = relationship("Contact", back_populates="submissions") #: client org contact = relationship("Contact", back_populates="submissions") #: client org
contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL", contact_id = Column(INTEGER, ForeignKey("_contact.id", ondelete="SET NULL",
name="fk_BS_contact_id")) #: client lab id from _organizations name="fk_BS_contact_id")) #: client lab id from _organizations
@@ -92,6 +92,16 @@ class ClientSubmission(BaseClass, LogMixin):
except AttributeError: except AttributeError:
self._submission_category = "NA" self._submission_category = "NA"
def __init__(self):
super().__init__()
self.misc_info = {}
def set_attribute(self, key, value):
if hasattr(self, key):
super().__setattr__(key, value)
else:
self.misc_info[key] = value
@classmethod @classmethod
def recruit_parser(cls): def recruit_parser(cls):
pass pass
@@ -237,7 +247,7 @@ class ClientSubmission(BaseClass, LogMixin):
output = { output = {
"id": self.id, "id": self.id,
"submission_type": self.submission_type_name, "submission_type": self.submission_type_name,
"submitter_plate_number": self.submitter_plate_num, "submitter_plate_number": self.submitter_plate_id,
"submitted_date": self.submitted_date.strftime("%Y-%m-%d"), "submitted_date": self.submitted_date.strftime("%Y-%m-%d"),
"submitting_lab": sub_lab, "submitting_lab": sub_lab,
"sample_count": self.sample_count, "sample_count": self.sample_count,
@@ -279,11 +289,14 @@ class ClientSubmission(BaseClass, LogMixin):
output["runs"] = runs output["runs"] = runs
return output return output
class BasicSubmission(BaseClass, LogMixin): class BasicSubmission(BaseClass, LogMixin):
""" """
Object for an entire submission run. Links to client submissions, reagents, equipment, processes Object for an entire submission run. Links to client submissions, reagents, equipment, processes
""" """
id = Column(INTEGER, primary_key=True) #: primary key id = Column(INTEGER, primary_key=True) #: primary key
rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012) rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
client_submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL", client_submission_id = Column(INTEGER, ForeignKey("_clientsubmission.id", ondelete="SET NULL",
@@ -384,6 +397,10 @@ class BasicSubmission(BaseClass, LogMixin):
def organization(self): def organization(self):
return self.submitting_lab return self.submitting_lab
@hybrid_property
def name(self):
return self.rsl_plate_num
@classproperty @classproperty
def jsons(cls) -> List[str]: def jsons(cls) -> List[str]:
""" """
@@ -567,8 +584,8 @@ class BasicSubmission(BaseClass, LogMixin):
"id": self.id, "id": self.id,
"plate_number": self.rsl_plate_num, "plate_number": self.rsl_plate_num,
"submission_type": self.client_submission.submission_type_name, "submission_type": self.client_submission.submission_type_name,
"submitter_plate_number": self.client_submission.submitter_plate_num, "submitter_plate_number": self.client_submission.submitter_plate_id,
"submitted_date": self.client_submission.submitted_date.strftime("%Y-%m-%d"), "started_date": self.client_submission.submitted_date.strftime("%Y-%m-%d"),
"submitting_lab": sub_lab, "submitting_lab": sub_lab,
"sample_count": self.client_submission.sample_count, "sample_count": self.client_submission.sample_count,
"extraction_kit": "Change submissions.py line 388", "extraction_kit": "Change submissions.py line 388",

View File

@@ -68,7 +68,7 @@ class ReportMaker(object):
{'extraction_kit': 'count', 'cost': 'sum', 'sample_count': 'sum'}) {'extraction_kit': 'count', 'cost': 'sum', 'sample_count': 'sum'})
df2 = df2.rename(columns={"extraction_kit": 'run_count'}) df2 = df2.rename(columns={"extraction_kit": 'run_count'})
df = df.drop('id', axis=1) df = df.drop('id', axis=1)
df = df.sort_values(['submitting_lab', "submitted_date"]) df = df.sort_values(['submitting_lab', "started_date"])
return df, df2 return df, df2
def make_report_html(self, df: DataFrame) -> str: def make_report_html(self, df: DataFrame) -> str:

View File

@@ -384,7 +384,6 @@ class PydSubmission(BaseModel, extra='allow'):
filepath: Path filepath: Path
submission_type: dict | None submission_type: dict | None
submitter_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) submitter_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
submitted_date: dict | None
rsl_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) rsl_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True) submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True)
submitting_lab: dict | None submitting_lab: dict | None
@@ -1331,21 +1330,93 @@ class PydElastic(BaseModel, extra="allow", arbitrary_types_allowed=True):
# NOTE: Generified objects below: # NOTE: Generified objects below:
class PydClientSubmission(BaseModel, extra="allow"): class PydClientSubmission(BaseModel, extra="allow", validate_assignment=True):
sql_object: ClassVar = ClientSubmission
filepath: Path filepath: Path
submission_type: dict | None submission_type: dict | None
submitter_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
submitted_date: dict | None
submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True) submitted_date: dict | None = Field(default=dict(value=date.today(), missing=True), validate_default=True)
submitting_lab: dict | None submitting_lab: dict | None
sample_count: dict | None sample_count: dict | None
kittype: dict | None
submission_category: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) submission_category: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
comment: dict | None = Field(default=dict(value="", missing=True), validate_default=True) comment: dict | None = Field(default=dict(value="", missing=True), validate_default=True)
cost_centre: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) cost_centre: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True) contact: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
submitter_plate_num: dict | None = Field(default=dict(value=None, missing=True), validate_default=True)
@field_validator("sample_count")
@classmethod
def enforce_integer(cls, value):
try:
value['value'] = int(value['value'])
except ValueError:
raise f"sample count value must be an integer"
return value
@field_validator("submitter_plate_num")
@classmethod
def create_submitter_plate_num(cls, value, values):
if value['value'] in [None, "None"]:
val = f"{values.data['submission_type']['value']}-{values.data['submission_category']['value']}-{values.data['submitted_date']['value']}"
return dict(value=val, missing=True)
else:
value['value'] = value['value'].strip()
return value
@field_validator("submitted_date")
@classmethod
def rescue_date(cls, value):
try:
check = value['value'] is None
except TypeError:
check = True
if check:
return dict(value=date.today(), missing=True)
return value
def filter_field(self, key: str) -> Any:
"""
Attempts to get value from field dictionary
Args:
key (str): name of the field of interest
Returns:
Any (): Value found.
"""
item = getattr(self, key)
match item:
case dict():
try:
item = item['value']
except KeyError:
logger.error(f"Couldn't get dict value: {item}")
case _:
pass
return item
def improved_dict(self, dictionaries: bool = True) -> dict:
"""
Adds model_extra to fields.
Args:
dictionaries (bool, optional): Are dictionaries expected as input? i.e. Should key['value'] be retrieved. Defaults to True.
Returns:
dict: This instance as a dictionary
"""
fields = list(self.model_fields.keys()) + list(self.model_extra.keys())
if dictionaries:
output = {k: getattr(self, k) for k in fields}
else:
output = {k: self.filter_field(k) for k in fields}
try:
del output['filepath']
except KeyError:
pass
logger.debug(f"Output; {pformat(output)}")
return output
def to_form(self, parent: QWidget, disable: list | None = None): def to_form(self, parent: QWidget, disable: list | None = None):
""" """
@@ -1360,3 +1431,17 @@ class PydClientSubmission(BaseModel, extra="allow"):
""" """
from frontend.widgets.submission_widget import ClientSubmissionFormWidget from frontend.widgets.submission_widget import ClientSubmissionFormWidget
return ClientSubmissionFormWidget(parent=parent, submission=self, disable=disable) return ClientSubmissionFormWidget(parent=parent, submission=self, disable=disable)
def to_sql(self):
sql = self.sql_object()
for key, value in self.improved_dict().items():
if isinstance(value, dict):
value = value['value']
# if hasattr(sql, key):
# try:
sql.set_attribute(key, value)
# except AttributeError:
# continue
# else:
# sql.misc_info[key] = value
print(sql.__dict__)

View File

@@ -5,7 +5,7 @@ import logging
import sys import sys
from pprint import pformat from pprint import pformat
from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, QStyle, QStyleOptionViewItem, \ from PyQt6.QtWidgets import QTableView, QMenu, QTreeView, QStyledItemDelegate, QStyle, QStyleOptionViewItem, \
QHeaderView, QAbstractItemView QHeaderView, QAbstractItemView, QWidget
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 from PyQt6.QtGui import QAction, QCursor, QStandardItemModel, QStandardItem, QIcon, QColor
from backend.db.models import BasicSubmission, ClientSubmission from backend.db.models import BasicSubmission, ClientSubmission
@@ -230,8 +230,12 @@ class SubmissionsSheet(QTableView):
class RunDelegate(QStyledItemDelegate): class RunDelegate(QStyledItemDelegate):
def __init__(self, parent=None): def __init__(self, parent=None):
super(RunDelegate, self).__init__(parent) super(RunDelegate, self).__init__(parent)
self._plus_icon = QIcon("plus.png") pixmapi = QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton
self._minus_icon = QIcon("minus.png") 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): def initStyleOption(self, option, index):
super(RunDelegate, self).initStyleOption(option, index) super(RunDelegate, self).initStyleOption(option, index)
@@ -258,6 +262,11 @@ class SubmissionsTree(QTreeView):
self.setSelectionBehavior(QAbstractItemView.selectionBehavior(self).SelectRows) self.setSelectionBehavior(QAbstractItemView.selectionBehavior(self).SelectRows)
# self.setStyleSheet("background-color: #0D1225;") # self.setStyleSheet("background-color: #0D1225;")
self.set_data() self.set_data()
self.doubleClicked.connect(self.show_details)
for ii in range(2):
self.resizeColumnToContents(ii)
@pyqtSlot(QModelIndex) @pyqtSlot(QModelIndex)
def on_clicked(self, index): def on_clicked(self, index):
@@ -273,9 +282,20 @@ class SubmissionsTree(QTreeView):
logger.debug(pformat(self.data)) logger.debug(pformat(self.data))
# sys.exit() # sys.exit()
for submission in self.data: for submission in self.data:
group_item = self.model.add_group(submission['submitter_plate_number']) group_str = f"{submission['submission_type']}-{submission['submitter_plate_number']}-{submission['submitted_date']}"
group_item = self.model.add_group(group_str)
for run in submission['runs']: for run in submission['runs']:
self.model.append_element_to_group(group_item=group_item, texts=run['plate_number']) self.model.append_element_to_group(group_item=group_item, element=run)
def show_details(self, sel: QModelIndex):
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
BasicSubmission.query(id=id).show_details(self)
def link_extractions(self): def link_extractions(self):
@@ -286,12 +306,19 @@ class SubmissionsTree(QTreeView):
class ClientRunModel(QStandardItemModel): class ClientRunModel(QStandardItemModel):
def __init__(self, parent=None): def __init__(self, parent=None):
super(ClientRunModel, self).__init__(parent) super(ClientRunModel, self).__init__(parent)
self.setColumnCount(8) headers = ["", "id", "Plate Number", "Started Date", "Completed Date", "Technician", "Signed By"]
self.setHorizontalHeaderLabels(["id", "Name", "Library", "Release Date", "Genre(s)", "Last Played", "Time Played", ""]) self.setColumnCount(len(headers))
self.setHorizontalHeaderLabels(headers)
for i in range(self.columnCount()): for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i) it = self.horizontalHeaderItem(i)
try:
logger.debug(it.text())
except AttributeError:
pass
# it.setForeground(QColor("#F2F2F2")) # it.setForeground(QColor("#F2F2F2"))
def add_group(self, group_name): def add_group(self, group_name):
@@ -313,18 +340,29 @@ class ClientRunModel(QStandardItemModel):
# it.setForeground(QColor("#F2F2F2")) # it.setForeground(QColor("#F2F2F2"))
return item_root return item_root
def append_element_to_group(self, group_item, texts): def append_element_to_group(self, group_item, element:dict):
logger.debug(f"Element: {pformat(element)}")
j = group_item.rowCount() j = group_item.rowCount()
item_icon = QStandardItem() item_icon = QStandardItem()
item_icon.setEditable(False) item_icon.setEditable(False)
item_icon.setIcon(QIcon("game.png"))
# item_icon.setBackground(QColor("#0D1225")) # item_icon.setBackground(QColor("#0D1225"))
group_item.setChild(j, 0, item_icon) # group_item.setChild(j, 0, item_icon)
for i, text in enumerate(texts): for i in range(self.columnCount()):
item = QStandardItem(text) it = self.horizontalHeaderItem(i)
try:
key = it.text().lower().replace(" ", "_")
except AttributeError:
continue
if not key:
continue
logger.debug(f"Looking for {key} in column {i}")
value = str(element[key])
logger.debug(f"Got value: {value}")
item = QStandardItem(value)
item.setBackground(QColor("#CFE2F3"))
item.setEditable(False) item.setEditable(False)
# item.setBackground(QColor("#0D1225")) group_item.setChild(j, i, item)
# item.setForeground(QColor("#F2F2F2")) # group_item.setChild(j, 1, QStandardItem("B"))
group_item.setChild(j, i+1, item)

View File

@@ -438,7 +438,7 @@ class SubmissionFormWidget(QWidget):
value = getattr(self, item) value = getattr(self, item)
info[item] = value info[item] = value
for k, v in info.items(): for k, v in info.items():
self.pyd.set_attribute(key=k, value=v) self.pyd.__setattr__(k, v)
report.add_result(report) report.add_result(report)
return report return report
@@ -786,9 +786,53 @@ class ClientSubmissionFormWidget(SubmissionFormWidget):
def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None: def __init__(self, parent: QWidget, submission: PydSubmission, disable: list | None = None) -> None:
super().__init__(parent, submission=submission, disable=disable) super().__init__(parent, submission=submission, disable=disable)
save_btn = QPushButton("Save") save_btn = QPushButton("Save")
start_run_btn = QPushButton("Save && Start Run") start_run_btn = QPushButton("Save && Add Run")
self.layout.addWidget(save_btn) self.layout.addWidget(save_btn)
self.layout.addWidget(start_run_btn) self.layout.addWidget(start_run_btn)
start_run_btn.clicked.connect(self.create_new_submission)
del self.disabler
def parse_form(self) -> Report:
"""
Transforms form info into PydSubmission
Returns:
Report: Report on status of parse.
"""
report = Report()
logger.info(f"Hello from client submission form parser!")
info = {}
reagents = []
for widget in self.findChildren(QWidget):
match widget:
case self.ReagentFormWidget():
reagent = widget.parse_form()
if reagent is not None:
reagents.append(reagent)
else:
report.add_result(Result(msg="Failed integrity check", status="Critical"))
return report
case self.InfoItem():
field, value = widget.parse_form()
if field is not None:
info[field] = value
# logger.debug(f"Reagents from form: {reagents}")
for item in self.recover:
if hasattr(self, item):
value = getattr(self, item)
info[item] = value
for k, v in info.items():
self.pyd.__setattr__(k, v)
report.add_result(report)
return report
@report_result
def to_pydantic(self, *args):
self.parse_form()
return self.pyd
def create_new_submission(self, *args) -> Report:
self.parse_form()
sql = self.pyd.to_sql()