Prior to merging omni_search.py and sample_search.py

This commit is contained in:
lwark
2024-11-18 09:53:03 -06:00
parent 3d6a42b36f
commit 506aac80c1
13 changed files with 162 additions and 73 deletions

View File

@@ -13,7 +13,7 @@ from PyQt6.QtGui import QAction
from pathlib import Path
from markdown import markdown
from __init__ import project_path
from backend import SubmissionType
from backend import SubmissionType, Reagent
from tools import check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size
from .functions import select_save_file, select_open_file
from datetime import date
@@ -23,8 +23,9 @@ import logging, webbrowser, sys, shutil
from .submission_table import SubmissionsSheet
from .submission_widget import SubmissionFormContainer
from .controls_chart import ControlsViewer
from .sample_search import SearchBox
from .sample_search import SampleSearchBox
from .summary import Summary
from .omni_search import SearchBox
logger = logging.getLogger(f'submissions.{__name__}')
logger.info("Hello, I am a logger")
@@ -185,7 +186,7 @@ class App(QMainWindow):
"""
Create a search for samples.
"""
dlg = SearchBox(self)
dlg = SampleSearchBox(self)
dlg.exec()
def backup_database(self):
@@ -226,7 +227,7 @@ class App(QMainWindow):
@check_authorization
def edit_reagent(self, *args, **kwargs):
dlg = EditReagent()
dlg = SearchBox(parent=self, object_type=Reagent, extras=['role'])
dlg.exec()
@check_authorization

View File

@@ -30,8 +30,8 @@ class AddReagentForm(QDialog):
def __init__(self, reagent_lot: str | None = None, reagent_role: str | None = None, expiry: date | None = None,
reagent_name: str | None = None) -> None:
super().__init__()
if reagent_lot is None:
reagent_lot = reagent_role
if reagent_name is None:
reagent_name = reagent_role
self.setWindowTitle("Add Reagent")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)

View File

@@ -2,14 +2,13 @@
Search box that performs fuzzy search for samples
'''
from pprint import pformat
from typing import Tuple
from typing import Tuple, Any, List
from pandas import DataFrame
from PyQt6.QtCore import QSortFilterProxyModel
from PyQt6.QtWidgets import (
QLabel, QVBoxLayout, QDialog,
QComboBox, QTableView, QWidget, QLineEdit, QGridLayout
QTableView, QWidget, QLineEdit, QGridLayout, QComboBox
)
from backend.db.models import BasicSample
from .submission_table import pandasModel
import logging
@@ -18,21 +17,22 @@ logger = logging.getLogger(f"submissions.{__name__}")
class SearchBox(QDialog):
def __init__(self, parent):
def __init__(self, parent, object_type: Any, extras: List[str], **kwargs):
super().__init__(parent)
self.layout = QGridLayout(self)
self.sample_type = QComboBox(self)
self.sample_type.setObjectName("sample_type")
self.sample_type.currentTextChanged.connect(self.update_widgets)
options = ["Any"] + [cls.__mapper_args__['polymorphic_identity'] for cls in BasicSample.__subclasses__()]
self.sample_type.addItems(options)
self.sample_type.setEditable(False)
self.object_type = object_type
# options = ["Any"] + [cls.__name__ for cls in self.object_type.__subclasses__()]
# self.sub_class = QComboBox(self)
# self.sub_class.setObjectName("sub_class")
# self.sub_class.currentTextChanged.connect(self.update_widgets)
# self.sub_class.addItems(options)
# self.sub_class.setEditable(False)
self.setMinimumSize(600, 600)
self.sample_type.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.sample_type, 0, 0)
self.results = SearchResults()
# self.sub_class.setMinimumWidth(self.minimumWidth())
# self.layout.addWidget(self.sub_class, 0, 0)
self.results = SearchResults(parent=self, object_type=self.object_type, extras=extras, **kwargs)
self.layout.addWidget(self.results, 5, 0)
self.setLayout(self.layout)
self.setWindowTitle(f"Search {self.object_type.__name__}")
self.update_widgets()
self.update_data()
@@ -40,21 +40,10 @@ class SearchBox(QDialog):
"""
Changes form inputs based on sample type
"""
deletes = [item for item in self.findChildren(FieldSearch)]
# logger.debug(deletes)
for item in deletes:
item.setParent(None)
if self.sample_type.currentText() == "Any":
self.type = BasicSample
else:
self.type = BasicSample.find_polymorphic_subclass(self.sample_type.currentText())
# logger.debug(f"Sample type: {self.type}")
searchables = self.type.get_searchables()
start_row = 1
for iii, item in enumerate(searchables):
widget = FieldSearch(parent=self, label=item['label'], field_name=item['field'])
self.layout.addWidget(widget, start_row + iii, 0)
widget.search_widget.textChanged.connect(self.update_data)
for iii, searchable in enumerate(self.object_type.searchables):
self.widget = FieldSearch(parent=self, label=searchable, field_name=searchable)
self.layout.addWidget(self.widget, 1, 0)
self.widget.search_widget.textChanged.connect(self.update_data)
self.update_data()
def parse_form(self) -> dict:
@@ -74,9 +63,8 @@ class SearchBox(QDialog):
# logger.debug(f"Running update_data with sample type: {self.type}")
fields = self.parse_form()
# logger.debug(f"Got fields: {fields}")
sample_list_creator = self.type.fuzzy_search(**fields)
data = self.type.samples_to_df(sample_list=sample_list_creator)
# logger.debug(f"Data: {data}")
sample_list_creator = self.object_type.fuzzy_search(**fields)
data = self.object_type.results_to_df(objects=sample_list_creator)
self.results.setData(df=data)
@@ -108,21 +96,43 @@ class FieldSearch(QWidget):
class SearchResults(QTableView):
def __init__(self):
def __init__(self, parent: SearchBox, object_type: Any, extras: List[str], **kwargs):
super().__init__()
self.doubleClicked.connect(
lambda x: BasicSample.query(submitter_id=x.sibling(x.row(), 0).data()).show_details(self))
self.context = kwargs
self.parent = parent
self.object_type = object_type
self.extras = extras + self.object_type.searchables
def setData(self, df: DataFrame) -> None:
"""
sets data in model
"""
self.data = df
print(self.data)
try:
self.columns_of_interest = [dict(name=item, column=self.data.columns.get_loc(item)) for item in self.extras]
except KeyError:
self.columns_of_interest = []
try:
self.data['id'] = self.data['id'].apply(str)
self.data['id'] = self.data['id'].str.zfill(3)
except (TypeError, KeyError):
logger.error("Couldn't format id string.")
except (TypeError, KeyError) as e:
logger.error(f"Couldn't format id string: {e}")
proxy_model = QSortFilterProxyModel()
proxy_model.setSourceModel(pandasModel(self.data))
self.setModel(proxy_model)
self.doubleClicked.connect(self.parse_row)
def parse_row(self, x):
context = {item['name']: x.sibling(x.row(), item['column']).data() for item in self.columns_of_interest}
try:
object = self.object_type.query(**{self.object_type.search: context[self.object_type.search]})
except KeyError:
object = None
try:
object.edit_from_search(**context)
except AttributeError:
pass
self.doubleClicked.disconnect()
self.parent.update_data()

View File

@@ -16,7 +16,7 @@ import logging
logger = logging.getLogger(f"submissions.{__name__}")
class SearchBox(QDialog):
class SampleSearchBox(QDialog):
def __init__(self, parent):
super().__init__(parent)

View File

@@ -106,10 +106,10 @@ class SubmissionDetails(QDialog):
@pyqtSlot(str, str)
def reagent_details(self, reagent: str | Reagent, kit: str | KitType):
if isinstance(reagent, str):
reagent = Reagent.query(lot_number=reagent)
reagent = Reagent.query(lot=reagent)
if isinstance(kit, str):
kit = KitType.query(name=kit)
base_dict = reagent.to_sub_dict(extraction_kit=kit, full_data=True)
self.kit = KitType.query(name=kit)
base_dict = reagent.to_sub_dict(extraction_kit=self.kit, full_data=True)
env = jinja_template_loading()
temp_name = "reagent_details.html"
# logger.debug(f"Returning template: {temp_name}")
@@ -121,10 +121,22 @@ class SubmissionDetails(QDialog):
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read()
html = template.render(reagent=base_dict, css=css)
html = template.render(reagent=base_dict, permission=is_power_user(), css=css)
self.webview.setHtml(html)
self.setWindowTitle(f"Reagent Details - {reagent.name} - {reagent.lot}")
@pyqtSlot(str, str, str)
def update_reagent(self, old_lot: str, new_lot: str, expiry: str):
expiry = datetime.strptime(expiry, "%Y-%m-%d")
reagent = Reagent.query(lot=old_lot)
if reagent:
reagent.lot = new_lot
reagent.expiry = expiry
reagent.save()
self.reagent_details(reagent=reagent, kit=self.kit)
else:
logger.error(f"Reagent with lot {old_lot} not found.")
@pyqtSlot(str)
def submission_details(self, submission: str | BasicSubmission):
"""
@@ -150,7 +162,7 @@ class SubmissionDetails(QDialog):
css = f.read()
# logger.debug(f"Submission_details: {pformat(self.base_dict)}")
# logger.debug(f"User is power user: {is_power_user()}")
self.html = self.template.render(sub=self.base_dict, signing_permission=is_power_user(), css=css)
self.html = self.template.render(sub=self.base_dict, permission=is_power_user(), css=css)
self.webview.setHtml(self.html)
@pyqtSlot(str)

View File

@@ -675,7 +675,7 @@ class SubmissionFormWidget(QWidget):
report = Report()
lot = self.lot.currentText()
# logger.debug(f"Using this lot for the reagent {self.reagent}: {lot}")
wanted_reagent = Reagent.query(lot_number=lot, reagent_role=self.reagent.role)
wanted_reagent = Reagent.query(lot=lot, role=self.reagent.role)
# NOTE: if reagent doesn't exist in database, offer to add it (uses App.add_reagent)
if wanted_reagent is None:
dlg = QuestionAsker(title=f"Add {lot}?",
@@ -745,7 +745,7 @@ class SubmissionFormWidget(QWidget):
relevant_reagents.insert(0, str(reagent.lot))
else:
try:
looked_up_reg = Reagent.query(lot_number=looked_up_rt.last_used)
looked_up_reg = Reagent.query(lot=looked_up_rt.last_used)
except AttributeError:
looked_up_reg = None
if isinstance(looked_up_reg, list):