Post code clean-up, before attempt to upgrade controls to FigureWidgets
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
'''
|
||||
"""
|
||||
Contains all custom generated PyQT6 derivative widgets.
|
||||
'''
|
||||
"""
|
||||
|
||||
# from .app import App
|
||||
from .functions import *
|
||||
from .misc import *
|
||||
from .pop_ups import *
|
||||
from .submission_table import *
|
||||
from .submission_widget import *
|
||||
from .controls_chart import *
|
||||
from .kit_creator import *
|
||||
from .submission_details import *
|
||||
from .equipment_usage import *
|
||||
from .gel_checker import *
|
||||
from .submission_type_creator import *
|
||||
from .app import App
|
||||
|
||||
@@ -9,7 +9,6 @@ from PyQt6.QtWidgets import (
|
||||
)
|
||||
from PyQt6.QtGui import QAction
|
||||
from pathlib import Path
|
||||
|
||||
from markdown import markdown
|
||||
from __init__ import project_path
|
||||
from tools import check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size
|
||||
@@ -21,8 +20,6 @@ import logging, webbrowser, sys, shutil
|
||||
from .submission_table import SubmissionsSheet
|
||||
from .submission_widget import SubmissionFormContainer
|
||||
from .controls_chart import ControlsViewer
|
||||
from .kit_creator import KitAdder
|
||||
from .submission_type_creator import SubmissionTypeAdder, SubmissionType
|
||||
from .sample_search import SearchBox
|
||||
from .summary import Summary
|
||||
|
||||
@@ -72,7 +69,6 @@ class App(QMainWindow):
|
||||
fileMenu = menuBar.addMenu("&File")
|
||||
# NOTE: Creating menus using a title
|
||||
methodsMenu = menuBar.addMenu("&Methods")
|
||||
# reportMenu = menuBar.addMenu("&Reports")
|
||||
maintenanceMenu = menuBar.addMenu("&Monthly")
|
||||
helpMenu = menuBar.addMenu("&Help")
|
||||
helpMenu.addAction(self.helpAction)
|
||||
@@ -83,7 +79,6 @@ class App(QMainWindow):
|
||||
fileMenu.addAction(self.yamlImportAction)
|
||||
methodsMenu.addAction(self.searchLog)
|
||||
methodsMenu.addAction(self.searchSample)
|
||||
# reportMenu.addAction(self.generateReportAction)
|
||||
maintenanceMenu.addAction(self.joinExtractionAction)
|
||||
maintenanceMenu.addAction(self.joinPCRAction)
|
||||
|
||||
@@ -105,7 +100,6 @@ class App(QMainWindow):
|
||||
# logger.debug(f"Creating actions...")
|
||||
self.importAction = QAction("&Import Submission", self)
|
||||
self.addReagentAction = QAction("Add Reagent", self)
|
||||
# self.generateReportAction = QAction("Make Report", self)
|
||||
self.addKitAction = QAction("Import Kit", self)
|
||||
self.addOrgAction = QAction("Import Org", self)
|
||||
self.joinExtractionAction = QAction("Link Extraction Logs")
|
||||
@@ -125,7 +119,6 @@ class App(QMainWindow):
|
||||
# logger.debug(f"Connecting actions...")
|
||||
self.importAction.triggered.connect(self.table_widget.formwidget.importSubmission)
|
||||
self.addReagentAction.triggered.connect(self.table_widget.formwidget.add_reagent)
|
||||
# self.generateReportAction.triggered.connect(self.table_widget.sub_wid.generate_report)
|
||||
self.joinExtractionAction.triggered.connect(self.table_widget.sub_wid.link_extractions)
|
||||
self.joinPCRAction.triggered.connect(self.table_widget.sub_wid.link_pcr)
|
||||
self.helpAction.triggered.connect(self.showAbout)
|
||||
@@ -233,6 +226,7 @@ class App(QMainWindow):
|
||||
ap.exec()
|
||||
st = SubmissionType.import_from_json(filepath=fname)
|
||||
if st:
|
||||
# NOTE: Do not delete the print statement below.
|
||||
print(pformat(st.to_export_dict()))
|
||||
choice = input("Save the above submission type? [y/N]: ")
|
||||
if choice.lower() == "y":
|
||||
@@ -262,7 +256,6 @@ class AddSubForm(QWidget):
|
||||
self.tabs.addTab(self.tab2, "Irida Controls")
|
||||
self.tabs.addTab(self.tab3, "PCR Controls")
|
||||
self.tabs.addTab(self.tab4, "Cost Report")
|
||||
# self.tabs.addTab(self.tab4, "Add Kit")
|
||||
# NOTE: Create submission adder form
|
||||
self.formwidget = SubmissionFormContainer(self)
|
||||
self.formlayout = QVBoxLayout(self)
|
||||
@@ -294,16 +287,10 @@ class AddSubForm(QWidget):
|
||||
self.pcr_viewer = ControlsViewer(self, archetype="PCR Control")
|
||||
self.tab3.layout.addWidget(self.pcr_viewer)
|
||||
self.tab3.setLayout(self.tab3.layout)
|
||||
# NOTE: create custom widget to add new tabs
|
||||
# ST_adder = SubmissionTypeAdder(self)
|
||||
summary_report = Summary(self)
|
||||
self.tab4.layout = QVBoxLayout(self)
|
||||
self.tab4.layout.addWidget(summary_report)
|
||||
self.tab4.setLayout(self.tab4.layout)
|
||||
# kit_adder = KitAdder(self)
|
||||
# self.tab4.layout = QVBoxLayout(self)
|
||||
# self.tab4.layout.addWidget(kit_adder)
|
||||
# self.tab4.setLayout(self.tab4.layout)
|
||||
# NOTE: add tabs to main widget
|
||||
self.layout.addWidget(self.tabs)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
"""
|
||||
Handles display of control charts
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from datetime import timedelta, date
|
||||
from datetime import date
|
||||
from pprint import pformat
|
||||
from typing import Tuple
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QComboBox, QHBoxLayout,
|
||||
QDateEdit, QLabel, QSizePolicy, QPushButton, QGridLayout
|
||||
QWidget, QComboBox, QPushButton, QGridLayout
|
||||
)
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
from backend.db import ControlType, IridaControl
|
||||
from PyQt6.QtCore import QDate, QSize
|
||||
import logging
|
||||
from pandas import DataFrame
|
||||
from tools import Report, Result, get_unique_values_in_df_column, Settings, report_result
|
||||
from frontend.visualizations import IridaFigure, PCRFigure, CustomFigure
|
||||
from tools import Report, report_result
|
||||
from frontend.visualizations import CustomFigure
|
||||
from .misc import StartEndDatePicker
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
@@ -70,19 +64,12 @@ class ControlsViewer(QWidget):
|
||||
self.save_button.pressed.connect(self.save_chart_function)
|
||||
self.export_button.pressed.connect(self.save_data_function)
|
||||
|
||||
|
||||
def save_chart_function(self):
|
||||
self.fig.save_figure(parent=self)
|
||||
|
||||
def save_data_function(self):
|
||||
self.fig.save_data(parent=self)
|
||||
|
||||
# def controls_getter(self):
|
||||
# """
|
||||
# Lookup controls from database and send to chartmaker
|
||||
# """
|
||||
# self.controls_getter_function()
|
||||
|
||||
@report_result
|
||||
def controls_getter_function(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -128,7 +115,18 @@ class ControlsViewer(QWidget):
|
||||
self.chart_maker_function()
|
||||
return report
|
||||
|
||||
def diff_month(self, d1: date, d2: date):
|
||||
@classmethod
|
||||
def diff_month(self, d1: date, d2: date) -> float:
|
||||
"""
|
||||
Gets the number of months difference between two different dates
|
||||
|
||||
Args:
|
||||
d1 (date): Start date.
|
||||
d2 (date): End date.
|
||||
|
||||
Returns:
|
||||
float: Number of months difference
|
||||
"""
|
||||
return abs((d1.year - d2.year) * 12 + d1.month - d2.month)
|
||||
|
||||
@report_result
|
||||
@@ -169,164 +167,3 @@ class ControlsViewer(QWidget):
|
||||
# logger.debug("Figure updated... I hope.")
|
||||
return report
|
||||
|
||||
# def convert_data_list_to_df(self, input_df: list[dict]) -> DataFrame:
|
||||
# """
|
||||
# Convert list of control records to dataframe
|
||||
#
|
||||
# Args:
|
||||
# ctx (dict): settings passed from gui
|
||||
# input_df (list[dict]): list of dictionaries containing records
|
||||
# mode_sub_type (str | None, optional): sub_type of submission type. Defaults to None.
|
||||
#
|
||||
# Returns:
|
||||
# DataFrame: dataframe of controls
|
||||
# """
|
||||
#
|
||||
# df = DataFrame.from_records(input_df)
|
||||
# safe = ['name', 'submitted_date', 'genus', 'target']
|
||||
# for column in df.columns:
|
||||
# if column not in safe:
|
||||
# if self.mode_sub_type is not None and column != self.mode_sub_type:
|
||||
# continue
|
||||
# else:
|
||||
# safe.append(column)
|
||||
# if "percent" in column:
|
||||
# # count_col = [item for item in df.columns if "count" in item][0]
|
||||
# try:
|
||||
# count_col = next(item for item in df.columns if "count" in item)
|
||||
# except StopIteration:
|
||||
# continue
|
||||
# # NOTE: The actual percentage from kraken was off due to exclusion of NaN, recalculating.
|
||||
# df[column] = 100 * df[count_col] / df.groupby('name')[count_col].transform('sum')
|
||||
# df = df[[c for c in df.columns if c in safe]]
|
||||
# # NOTE: move date of sample submitted on same date as previous ahead one.
|
||||
# df = self.displace_date(df=df)
|
||||
# # NOTE: ad hoc method to make data labels more accurate.
|
||||
# df = self.df_column_renamer(df=df)
|
||||
# return df
|
||||
#
|
||||
# def df_column_renamer(self, df: DataFrame) -> DataFrame:
|
||||
# """
|
||||
# Ad hoc function I created to clarify some fields
|
||||
#
|
||||
# Args:
|
||||
# df (DataFrame): input dataframe
|
||||
#
|
||||
# Returns:
|
||||
# DataFrame: dataframe with 'clarified' column names
|
||||
# """
|
||||
# df = df[df.columns.drop(list(df.filter(regex='_hashes')))]
|
||||
# return df.rename(columns={
|
||||
# "contains_ratio": "contains_shared_hashes_ratio",
|
||||
# "matches_ratio": "matches_shared_hashes_ratio",
|
||||
# "kraken_count": "kraken2_read_count_(top_50)",
|
||||
# "kraken_percent": "kraken2_read_percent_(top_50)"
|
||||
# })
|
||||
#
|
||||
# def displace_date(self, df: DataFrame) -> DataFrame:
|
||||
# """
|
||||
# This function serves to split samples that were submitted on the same date by incrementing dates.
|
||||
# It will shift the date forward by one day if it is the same day as an existing date in a list.
|
||||
#
|
||||
# Args:
|
||||
# df (DataFrame): input dataframe composed of control records
|
||||
#
|
||||
# Returns:
|
||||
# DataFrame: output dataframe with dates incremented.
|
||||
# """
|
||||
# # logger.debug(f"Unique items: {df['name'].unique()}")
|
||||
# # NOTE: get submitted dates for each control
|
||||
# dict_list = [dict(name=item, date=df[df.name == item].iloc[0]['submitted_date']) for item in
|
||||
# sorted(df['name'].unique())]
|
||||
# previous_dates = set()
|
||||
# # for _, item in enumerate(dict_list):
|
||||
# for item in dict_list:
|
||||
# df, previous_dates = self.check_date(df=df, item=item, previous_dates=previous_dates)
|
||||
# return df
|
||||
#
|
||||
# def check_date(self, df: DataFrame, item: dict, previous_dates: set) -> Tuple[DataFrame, list]:
|
||||
# """
|
||||
# Checks if an items date is already present in df and adjusts df accordingly
|
||||
#
|
||||
# Args:
|
||||
# df (DataFrame): input dataframe
|
||||
# item (dict): control for checking
|
||||
# previous_dates (list): list of dates found in previous controls
|
||||
#
|
||||
# Returns:
|
||||
# Tuple[DataFrame, list]: Output dataframe and appended list of previous dates
|
||||
# """
|
||||
# try:
|
||||
# check = item['date'] in previous_dates
|
||||
# except IndexError:
|
||||
# check = False
|
||||
# previous_dates.add(item['date'])
|
||||
# if check:
|
||||
# # logger.debug(f"We found one! Increment date!\n\t{item['date']} to {item['date'] + timedelta(days=1)}")
|
||||
# # NOTE: get df locations where name == item name
|
||||
# mask = df['name'] == item['name']
|
||||
# # NOTE: increment date in dataframe
|
||||
# df.loc[mask, 'submitted_date'] = df.loc[mask, 'submitted_date'].apply(lambda x: x + timedelta(days=1))
|
||||
# item['date'] += timedelta(days=1)
|
||||
# passed = False
|
||||
# else:
|
||||
# passed = True
|
||||
# # logger.debug(f"\n\tCurrent date: {item['date']}\n\tPrevious dates:{previous_dates}")
|
||||
# # logger.debug(f"DF: {type(df)}, previous_dates: {type(previous_dates)}")
|
||||
# # NOTE: if run didn't lead to changed date, return values
|
||||
# if passed:
|
||||
# # logger.debug(f"Date check passed, returning.")
|
||||
# return df, previous_dates
|
||||
# # NOTE: if date was changed, rerun with new date
|
||||
# else:
|
||||
# logger.warning(f"Date check failed, running recursion")
|
||||
# df, previous_dates = self.check_date(df, item, previous_dates)
|
||||
# return df, previous_dates
|
||||
#
|
||||
# def prep_df(self, ctx: Settings, df: DataFrame) -> Tuple[DataFrame, list]:
|
||||
# """
|
||||
# Constructs figures based on parsed pandas dataframe.
|
||||
#
|
||||
# Args:
|
||||
# ctx (Settings): settings passed down from gui
|
||||
# df (pd.DataFrame): input dataframe
|
||||
# ytitle (str | None, optional): title for the y-axis. Defaults to None.
|
||||
#
|
||||
# Returns:
|
||||
# Figure: Plotly figure
|
||||
# """
|
||||
# # NOTE: converts starred genera to normal and splits off list of starred
|
||||
# if df.empty:
|
||||
# return None
|
||||
# df['genus'] = df['genus'].replace({'\*': ''}, regex=True).replace({"NaN": "Unknown"})
|
||||
# df['genera'] = [item[-1] if item and item[-1] == "*" else "" for item in df['genus'].to_list()]
|
||||
# # NOTE: remove original runs, using reruns if applicable
|
||||
# df = self.drop_reruns_from_df(ctx=ctx, df=df)
|
||||
# # NOTE: sort by and exclude from
|
||||
# sorts = ['submitted_date', "target", "genus"]
|
||||
# exclude = ['name', 'genera']
|
||||
# modes = [item for item in df.columns if item not in sorts and item not in exclude]
|
||||
# # NOTE: Set descending for any columns that have "{mode}" in the header.
|
||||
# ascending = [False if item == "target" else True for item in sorts]
|
||||
# df = df.sort_values(by=sorts, ascending=ascending)
|
||||
# # logger.debug(df[df.isna().any(axis=1)])
|
||||
# # NOTE: actual chart construction is done by
|
||||
# return df, modes
|
||||
#
|
||||
# def drop_reruns_from_df(self, ctx: Settings, df: DataFrame) -> DataFrame:
|
||||
# """
|
||||
# Removes semi-duplicates from dataframe after finding sequencing repeats.
|
||||
#
|
||||
# Args:
|
||||
# settings (dict): settings passed from gui
|
||||
# df (DataFrame): initial dataframe
|
||||
#
|
||||
# Returns:
|
||||
# DataFrame: dataframe with originals removed in favour of repeats.
|
||||
# """
|
||||
# if 'rerun_regex' in ctx:
|
||||
# sample_names = get_unique_values_in_df_column(df, column_name="name")
|
||||
# rerun_regex = re.compile(fr"{ctx.rerun_regex}")
|
||||
# exclude = [re.sub(rerun_regex, "", sample) for sample in sample_names if rerun_regex.search(sample)]
|
||||
# df = df[df.name not in exclude]
|
||||
# return df
|
||||
|
||||
@@ -8,7 +8,7 @@ from PyQt6.QtWidgets import (QDialog, QComboBox, QCheckBox,
|
||||
from backend.db.models import Equipment, BasicSubmission, Process
|
||||
from backend.validators.pydant import PydEquipment, PydEquipmentRole, PydTips
|
||||
import logging
|
||||
from typing import List, Generator
|
||||
from typing import Generator
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -50,7 +50,7 @@ class EquipmentUsage(QDialog):
|
||||
Pull info from all RoleComboBox widgets
|
||||
|
||||
Returns:
|
||||
List[PydEquipment]: All equipment pulled from widgets
|
||||
Generator[PydEquipment, None, None]: All equipment pulled from widgets
|
||||
"""
|
||||
for widget in self.findChildren(QWidget):
|
||||
match widget:
|
||||
|
||||
@@ -4,7 +4,6 @@ functions used by all windows in the application's frontend
|
||||
from pathlib import Path
|
||||
import logging
|
||||
from PyQt6.QtWidgets import QMainWindow, QFileDialog
|
||||
from tools import Result
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QScrollArea,
|
||||
QGridLayout, QPushButton, QLabel,
|
||||
QLineEdit, QComboBox, QDoubleSpinBox,
|
||||
QSpinBox, QDateEdit
|
||||
)
|
||||
from sqlalchemy import FLOAT, INTEGER
|
||||
from backend.db import SubmissionTypeKitTypeAssociation, SubmissionType, ReagentRole
|
||||
from backend.validators import PydReagentRole, PydKit
|
||||
import logging
|
||||
from pprint import pformat
|
||||
from tools import Report
|
||||
from typing import Tuple
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
class KitAdder(QWidget):
|
||||
"""
|
||||
dialog to get information to add kit
|
||||
"""
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
self.report = Report()
|
||||
self.app = parent.parent
|
||||
main_box = QVBoxLayout(self)
|
||||
scroll = QScrollArea(self)
|
||||
main_box.addWidget(scroll)
|
||||
scroll.setWidgetResizable(True)
|
||||
scrollContent = QWidget(scroll)
|
||||
self.grid = QGridLayout()
|
||||
scrollContent.setLayout(self.grid)
|
||||
# NOTE: insert submit button at top
|
||||
self.submit_btn = QPushButton("Submit")
|
||||
self.grid.addWidget(self.submit_btn,0,0,1,1)
|
||||
self.grid.addWidget(QLabel("Kit Name:"),2,0)
|
||||
# NOTE: widget to get kit name
|
||||
kit_name = QLineEdit()
|
||||
kit_name.setObjectName("kit_name")
|
||||
self.grid.addWidget(kit_name,2,1)
|
||||
self.grid.addWidget(QLabel("Used For Submission Type:"),3,0)
|
||||
# NOTE: widget to get uses of kit
|
||||
used_for = QComboBox()
|
||||
used_for.setObjectName("used_for")
|
||||
# NOTE: Insert all existing sample types
|
||||
used_for.addItems([item.name for item in SubmissionType.query()])
|
||||
used_for.setEditable(True)
|
||||
self.grid.addWidget(used_for,3,1)
|
||||
# NOTE: Get all fields in SubmissionTypeKitTypeAssociation
|
||||
self.columns = [item for item in SubmissionTypeKitTypeAssociation.__table__.columns if len(item.foreign_keys) == 0]
|
||||
for iii, column in enumerate(self.columns):
|
||||
idx = iii + 4
|
||||
# NOTE: convert field name to human readable.
|
||||
field_name = column.name.replace("_", " ").title()
|
||||
self.grid.addWidget(QLabel(field_name),idx,0)
|
||||
match column.type:
|
||||
case FLOAT():
|
||||
add_widget = QDoubleSpinBox()
|
||||
add_widget.setMinimum(0)
|
||||
add_widget.setMaximum(9999)
|
||||
case INTEGER():
|
||||
add_widget = QSpinBox()
|
||||
add_widget.setMinimum(0)
|
||||
add_widget.setMaximum(9999)
|
||||
case _:
|
||||
add_widget = QLineEdit()
|
||||
add_widget.setObjectName(column.name)
|
||||
self.grid.addWidget(add_widget, idx,1)
|
||||
self.add_RT_btn = QPushButton("Add Reagent Type")
|
||||
self.grid.addWidget(self.add_RT_btn)
|
||||
self.add_RT_btn.clicked.connect(self.add_RT)
|
||||
self.submit_btn.clicked.connect(self.submit)
|
||||
scroll.setWidget(scrollContent)
|
||||
self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
|
||||
"qt_scrollarea_vcontainer", "submit_btn"
|
||||
]
|
||||
|
||||
def add_RT(self) -> None:
|
||||
"""
|
||||
insert new reagent type row
|
||||
"""
|
||||
# NOTE: get bottommost row
|
||||
maxrow = self.grid.rowCount()
|
||||
reg_form = ReagentRoleForm(parent=self)
|
||||
reg_form.setObjectName(f"ReagentForm_{maxrow}")
|
||||
self.grid.addWidget(reg_form, maxrow,0,1,4)
|
||||
|
||||
def submit(self) -> None:
|
||||
"""
|
||||
send kit to database
|
||||
"""
|
||||
report = Report()
|
||||
# NOTE: get form info
|
||||
info, reagents = self.parse_form()
|
||||
info = {k:v for k,v in info.items() if k in [column.name for column in self.columns] + ['kit_name', 'used_for']}
|
||||
# logger.debug(f"kit info: {pformat(info)}")
|
||||
# logger.debug(f"kit reagents: {pformat(reagents)}")
|
||||
info['reagent_roles'] = reagents
|
||||
# logger.debug(pformat(info))
|
||||
# NOTE: send to kit constructor
|
||||
kit = PydKit(name=info['kit_name'])
|
||||
for reagent in info['reagent_roles']:
|
||||
uses = {
|
||||
info['used_for']:
|
||||
{'sheet':reagent['sheet'],
|
||||
'name':reagent['name'],
|
||||
'lot':reagent['lot'],
|
||||
'expiry':reagent['expiry']
|
||||
}}
|
||||
kit.reagent_roles.append(PydReagentRole(name=reagent['rtname'], eol_ext=reagent['eol'], uses=uses))
|
||||
# logger.debug(f"Output pyd object: {kit.__dict__}")
|
||||
sqlobj, result = kit.toSQL(self.ctx)
|
||||
report.add_result(result=result)
|
||||
sqlobj.save()
|
||||
|
||||
self.__init__(self.parent())
|
||||
|
||||
def parse_form(self) -> Tuple[dict, list]:
|
||||
"""
|
||||
Pulls reagent and general info from form
|
||||
|
||||
Returns:
|
||||
Tuple[dict, list]: dict=info, list=reagents
|
||||
"""
|
||||
# logger.debug(f"Hello from {self.__class__} parser!")
|
||||
info = {}
|
||||
reagents = []
|
||||
widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore and not isinstance(widget.parent(), ReagentRoleForm)]
|
||||
for widget in widgets:
|
||||
# logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
|
||||
match widget:
|
||||
case ReagentRoleForm():
|
||||
reagents.append(widget.parse_form())
|
||||
case QLineEdit():
|
||||
info[widget.objectName()] = widget.text()
|
||||
case QComboBox():
|
||||
info[widget.objectName()] = widget.currentText()
|
||||
case QDateEdit():
|
||||
info[widget.objectName()] = widget.date().toPyDate()
|
||||
return info, reagents
|
||||
|
||||
|
||||
class ReagentRoleForm(QWidget):
|
||||
"""
|
||||
custom widget to add information about a new reagenttype
|
||||
"""
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
grid = QGridLayout()
|
||||
self.setLayout(grid)
|
||||
grid.addWidget(QLabel("Reagent Type Name"),0,0)
|
||||
# Widget to get reagent info
|
||||
self.reagent_getter = QComboBox()
|
||||
self.reagent_getter.setObjectName("rtname")
|
||||
# lookup all reagent type names from db
|
||||
lookup = ReagentRole.query()
|
||||
# logger.debug(f"Looked up ReagentType names: {lookup}")
|
||||
self.reagent_getter.addItems([item.name for item in lookup])
|
||||
self.reagent_getter.setEditable(True)
|
||||
grid.addWidget(self.reagent_getter,0,1)
|
||||
grid.addWidget(QLabel("Extension of Life (months):"),0,2)
|
||||
# NOTE: widget to get extension of life
|
||||
self.eol = QSpinBox()
|
||||
self.eol.setObjectName('eol')
|
||||
self.eol.setMinimum(0)
|
||||
grid.addWidget(self.eol, 0,3)
|
||||
grid.addWidget(QLabel("Excel Location Sheet Name:"),1,0)
|
||||
self.location_sheet_name = QLineEdit()
|
||||
self.location_sheet_name.setObjectName("sheet")
|
||||
self.location_sheet_name.setText("e.g. 'Reagent Info'")
|
||||
grid.addWidget(self.location_sheet_name, 1,1)
|
||||
for iii, item in enumerate(["Name", "Lot", "Expiry"]):
|
||||
idx = iii + 2
|
||||
grid.addWidget(QLabel(f"{item} Row:"), idx, 0)
|
||||
row = QSpinBox()
|
||||
row.setFixedWidth(50)
|
||||
row.setObjectName(f'{item.lower()}_row')
|
||||
row.setMinimum(0)
|
||||
grid.addWidget(row, idx, 1)
|
||||
grid.addWidget(QLabel(f"{item} Column:"), idx, 2)
|
||||
col = QSpinBox()
|
||||
col.setFixedWidth(50)
|
||||
col.setObjectName(f'{item.lower()}_column')
|
||||
col.setMinimum(0)
|
||||
grid.addWidget(col, idx, 3)
|
||||
self.setFixedHeight(175)
|
||||
max_row = grid.rowCount()
|
||||
self.r_button = QPushButton("Remove")
|
||||
self.r_button.clicked.connect(self.remove)
|
||||
grid.addWidget(self.r_button,max_row,0,1,1)
|
||||
self.ignore = [None, "", "qt_spinbox_lineedit", "qt_scrollarea_viewport", "qt_scrollarea_hcontainer",
|
||||
"qt_scrollarea_vcontainer", "submit_btn", "eol", "sheet", "rtname"
|
||||
]
|
||||
|
||||
def remove(self):
|
||||
"""
|
||||
Destroys this row of reagenttype from the form
|
||||
"""
|
||||
self.setParent(None)
|
||||
self.destroy()
|
||||
|
||||
def parse_form(self) -> dict:
|
||||
"""
|
||||
Pulls ReagentType info from the form.
|
||||
|
||||
Returns:
|
||||
dict: _description_
|
||||
"""
|
||||
# logger.debug(f"Hello from {self.__class__} parser!")
|
||||
info = {}
|
||||
info['eol'] = self.eol.value()
|
||||
info['sheet'] = self.location_sheet_name.text()
|
||||
info['rtname'] = self.reagent_getter.currentText()
|
||||
widgets = [widget for widget in self.findChildren(QWidget) if widget.objectName() not in self.ignore]
|
||||
for widget in widgets:
|
||||
# logger.debug(f"Parsed widget: {widget.objectName()} of type {type(widget)} with parent {widget.parent()}")
|
||||
match widget:
|
||||
case QLineEdit():
|
||||
info[widget.objectName()] = widget.text()
|
||||
case QComboBox():
|
||||
info[widget.objectName()] = widget.currentText()
|
||||
case QDateEdit():
|
||||
info[widget.objectName()] = widget.date().toPyDate()
|
||||
case QSpinBox() | QDoubleSpinBox():
|
||||
if "_" in widget.objectName():
|
||||
key, sub_key = widget.objectName().split("_")
|
||||
if key not in info.keys():
|
||||
info[key] = {}
|
||||
# logger.debug(f"Adding key {key}, {sub_key} and value {widget.value()} to {info}")
|
||||
info[key][sub_key] = widget.value()
|
||||
return info
|
||||
|
||||
@@ -3,7 +3,6 @@ Contains miscellaneous widgets for frontend functions
|
||||
'''
|
||||
import math
|
||||
from datetime import date
|
||||
|
||||
from PyQt6.QtGui import QPageLayout, QPageSize, QStandardItem, QIcon
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import (
|
||||
@@ -51,7 +50,6 @@ class AddReagentForm(QDialog):
|
||||
self.exp_input.setObjectName('expiry')
|
||||
# NOTE: if expiry is not passed in from gui, use today
|
||||
if expiry is None:
|
||||
# self.exp_input.setDate(QDate.currentDate())
|
||||
self.exp_input.setDate(QDate(1970, 1, 1))
|
||||
else:
|
||||
try:
|
||||
@@ -244,4 +242,4 @@ class Pagifier(QWidget):
|
||||
self.update_current_page()
|
||||
|
||||
def update_current_page(self):
|
||||
self.current_page.setText(f"{self.page_anchor} of {self.page_max}")
|
||||
self.current_page.setText(f"{self.page_anchor} of {self.page_max}")
|
||||
|
||||
@@ -9,7 +9,7 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from tools import jinja_template_loading
|
||||
import logging
|
||||
from backend.db import models
|
||||
from typing import Literal
|
||||
from typing import Any, Literal
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
@@ -54,9 +54,8 @@ class AlertPop(QMessageBox):
|
||||
|
||||
class HTMLPop(QDialog):
|
||||
|
||||
def __init__(self, html: str, owner: str | None = None, title: str = "python"):
|
||||
def __init__(self, html: str, title: str = "python"):
|
||||
super().__init__()
|
||||
|
||||
self.webview = QWebEngineView(parent=self)
|
||||
self.layout = QVBoxLayout()
|
||||
self.setWindowTitle(title)
|
||||
|
||||
@@ -74,7 +74,6 @@ 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(sample_type=self.type, **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}")
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
"""
|
||||
Webview to show submission and sample details.
|
||||
"""
|
||||
from PyQt6.QtGui import QColor, QPageSize, QPageLayout
|
||||
from PyQt6.QtPrintSupport import QPrinter
|
||||
from PyQt6.QtWidgets import (QDialog, QPushButton, QVBoxLayout,
|
||||
QDialogButtonBox, QTextEdit, QGridLayout)
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWebChannel import QWebChannel
|
||||
from PyQt6.QtCore import Qt, pyqtSlot, QMarginsF, QSize
|
||||
from PyQt6.QtCore import Qt, pyqtSlot
|
||||
from jinja2 import TemplateNotFound
|
||||
from backend.db.models import BasicSubmission, BasicSample, Reagent, KitType
|
||||
from tools import is_power_user, jinja_template_loading
|
||||
@@ -41,7 +39,6 @@ class SubmissionDetails(QDialog):
|
||||
self.webview.setMaximumWidth(900)
|
||||
self.webview.loadFinished.connect(self.activate_export)
|
||||
self.layout = QGridLayout()
|
||||
# self.setFixedSize(900, 500)
|
||||
# NOTE: button to export a pdf version
|
||||
self.btn = QPushButton("Export PDF")
|
||||
self.btn.setFixedWidth(775)
|
||||
@@ -69,7 +66,6 @@ class SubmissionDetails(QDialog):
|
||||
def back_function(self):
|
||||
self.webview.back()
|
||||
|
||||
# @pyqtSlot(bool)
|
||||
def activate_export(self):
|
||||
title = self.webview.title()
|
||||
self.setWindowTitle(title)
|
||||
@@ -144,7 +140,6 @@ class SubmissionDetails(QDialog):
|
||||
self.base_dict = submission.to_dict(full_data=True)
|
||||
# logger.debug(f"Submission details data:\n{pformat({k:v for k,v in self.base_dict.items() if k == 'reagents'})}")
|
||||
# NOTE: don't want id
|
||||
self.base_dict = submission.finalize_details(self.base_dict)
|
||||
# logger.debug(f"Creating barcode.")
|
||||
# logger.debug(f"Making platemap...")
|
||||
self.base_dict['platemap'] = submission.make_plate_map(sample_list=submission.hitpick_plate())
|
||||
|
||||
@@ -10,8 +10,6 @@ from backend.db.models import BasicSubmission
|
||||
from tools import Report, Result, report_result
|
||||
from .functions import select_open_file
|
||||
|
||||
# from .misc import ReportDatePicker
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
@@ -91,7 +89,7 @@ class SubmissionsSheet(QTableView):
|
||||
"""
|
||||
sets data in model
|
||||
"""
|
||||
self.data = BasicSubmission.submissions_to_df(page=page)
|
||||
self.data = BasicSubmission.submissions_to_df(page=page, page_size=page_size)
|
||||
try:
|
||||
self.data['Id'] = self.data['Id'].apply(str)
|
||||
self.data['Id'] = self.data['Id'].str.zfill(4)
|
||||
@@ -101,7 +99,7 @@ class SubmissionsSheet(QTableView):
|
||||
proxyModel.setSourceModel(pandasModel(self.data))
|
||||
self.setModel(proxyModel)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
def contextMenuEvent(self):
|
||||
"""
|
||||
Creates actions for right click menu events.
|
||||
|
||||
@@ -157,7 +155,7 @@ class SubmissionsSheet(QTableView):
|
||||
report = Report()
|
||||
fname = select_open_file(self, file_extension="csv")
|
||||
with open(fname.__str__(), 'r') as f:
|
||||
# split csv on commas
|
||||
# NOTE: split csv on commas
|
||||
runs = [col.strip().split(",") for col in f.readlines()]
|
||||
count = 0
|
||||
for run in runs:
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QScrollArea,
|
||||
QGridLayout, QPushButton, QLabel,
|
||||
QLineEdit, QSpinBox, QCheckBox
|
||||
)
|
||||
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
||||
from backend.db import SubmissionType, BasicSubmission
|
||||
import logging
|
||||
from tools import Report
|
||||
from .functions import select_open_file
|
||||
|
||||
logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
class SubmissionTypeAdder(QWidget):
|
||||
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
self.report = Report()
|
||||
self.app = parent.parent()
|
||||
self.template_path = ""
|
||||
main_box = QVBoxLayout(self)
|
||||
scroll = QScrollArea(self)
|
||||
main_box.addWidget(scroll)
|
||||
scroll.setWidgetResizable(True)
|
||||
scrollContent = QWidget(scroll)
|
||||
self.grid = QGridLayout()
|
||||
scrollContent.setLayout(self.grid)
|
||||
# NOTE: insert submit button at top
|
||||
self.submit_btn = QPushButton("Submit")
|
||||
self.grid.addWidget(self.submit_btn,0,0,1,1)
|
||||
self.grid.addWidget(QLabel("Submission Type Name:"),2,0)
|
||||
# NOTE: widget to get kit name
|
||||
self.st_name = QLineEdit()
|
||||
self.st_name.setObjectName("submission_type_name")
|
||||
self.grid.addWidget(self.st_name,2,1,1,2)
|
||||
self.grid.addWidget(QLabel("Template File"),3,0)
|
||||
template_selector = QPushButton("Select")
|
||||
self.grid.addWidget(template_selector,3,1)
|
||||
self.template_label = QLabel("None")
|
||||
self.grid.addWidget(self.template_label,3,2)
|
||||
# NOTE: widget to get uses of kit
|
||||
exclude = ['id', 'submitting_lab_id', 'extraction_kit_id', 'reagents_id', 'extraction_info', 'pcr_info', 'run_cost']
|
||||
self.columns = {key:value for key, value in BasicSubmission.__dict__.items() if isinstance(value, InstrumentedAttribute)}
|
||||
self.columns = {key:value for key, value in self.columns.items() if hasattr(value, "type") and key not in exclude}
|
||||
for iii, key in enumerate(self.columns):
|
||||
idx = iii + 4
|
||||
self.grid.addWidget(InfoWidget(parent=self, key=key), idx,0,1,3)
|
||||
scroll.setWidget(scrollContent)
|
||||
self.submit_btn.clicked.connect(self.submit)
|
||||
template_selector.clicked.connect(self.get_template_path)
|
||||
|
||||
def submit(self):
|
||||
"""
|
||||
Create SubmissionType and send to db
|
||||
"""
|
||||
info = self.parse_form()
|
||||
ST = SubmissionType(name=self.st_name.text(), info_map=info)
|
||||
try:
|
||||
with open(self.template_path, "rb") as f:
|
||||
ST.template_file = f.read()
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Could not find template file: {self.template_path}")
|
||||
ST.save()
|
||||
|
||||
def parse_form(self) -> dict:
|
||||
"""
|
||||
Pulls info from form
|
||||
|
||||
Returns:
|
||||
dict: information from form
|
||||
"""
|
||||
widgets = [widget for widget in self.findChildren(QWidget) if isinstance(widget, InfoWidget)]
|
||||
return {widget.objectName():widget.parse_form() for widget in widgets}
|
||||
|
||||
def get_template_path(self):
|
||||
"""
|
||||
Sets path for loading a submission form template
|
||||
"""
|
||||
self.template_path = select_open_file(obj=self, file_extension="xlsx")
|
||||
self.template_label.setText(self.template_path.__str__())
|
||||
|
||||
|
||||
class InfoWidget(QWidget):
|
||||
|
||||
def __init__(self, parent: QWidget, key) -> None:
|
||||
super().__init__(parent)
|
||||
grid = QGridLayout()
|
||||
self.setLayout(grid)
|
||||
self.active = QCheckBox()
|
||||
self.active.setChecked(True)
|
||||
grid.addWidget(self.active, 0,0,1,1)
|
||||
grid.addWidget(QLabel(key.replace("_", " ").title()),0,1,1,4)
|
||||
self.setObjectName(key)
|
||||
grid.addWidget(QLabel("Sheet Names (comma seperated):"),1,0)
|
||||
self.sheet = QLineEdit()
|
||||
self.sheet.setObjectName("sheets")
|
||||
grid.addWidget(self.sheet, 1,1,1,3)
|
||||
grid.addWidget(QLabel("Row:"),2,0,alignment=Qt.AlignmentFlag.AlignRight)
|
||||
self.row = QSpinBox()
|
||||
self.row.setObjectName("row")
|
||||
grid.addWidget(self.row,2,1)
|
||||
grid.addWidget(QLabel("Column:"),2,2,alignment=Qt.AlignmentFlag.AlignRight)
|
||||
self.column = QSpinBox()
|
||||
self.column.setObjectName("column")
|
||||
grid.addWidget(self.column,2,3)
|
||||
|
||||
def parse_form(self) -> dict|None:
|
||||
"""
|
||||
Pulls info from the Info form.
|
||||
|
||||
Returns:
|
||||
dict: sheets, row, column
|
||||
"""
|
||||
if self.active.isChecked():
|
||||
return dict(
|
||||
sheets = self.sheet.text().split(","),
|
||||
row = self.row.value(),
|
||||
column = self.column.value()
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'''
|
||||
Contains all submission related frontend functions
|
||||
'''
|
||||
import sys
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QPushButton, QVBoxLayout,
|
||||
QComboBox, QDateEdit, QLineEdit, QLabel
|
||||
@@ -11,7 +9,7 @@ from PyQt6.QtCore import pyqtSignal, Qt
|
||||
from . import select_open_file, select_save_file
|
||||
import logging, difflib
|
||||
from pathlib import Path
|
||||
from tools import Report, Result, check_not_nan, main_form_style, report_result, check_regex_match
|
||||
from tools import Report, Result, check_not_nan, main_form_style, report_result
|
||||
from backend.excel.parser import SheetParser
|
||||
from backend.validators import PydSubmission, PydReagent
|
||||
from backend.db import (
|
||||
@@ -28,6 +26,9 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
||||
|
||||
|
||||
class MyQComboBox(QComboBox):
|
||||
"""
|
||||
Custom combobox that disables wheel events until focussed on.
|
||||
"""
|
||||
def __init__(self, scrollWidget=None, *args, **kwargs):
|
||||
super(MyQComboBox, self).__init__(*args, **kwargs)
|
||||
self.scrollWidget = scrollWidget
|
||||
@@ -42,6 +43,9 @@ class MyQComboBox(QComboBox):
|
||||
|
||||
|
||||
class MyQDateEdit(QDateEdit):
|
||||
"""
|
||||
Custom date editor that disables wheel events until focussed on.
|
||||
"""
|
||||
def __init__(self, scrollWidget=None, *args, **kwargs):
|
||||
super(MyQDateEdit, self).__init__(*args, **kwargs)
|
||||
self.scrollWidget = scrollWidget
|
||||
@@ -340,8 +344,6 @@ class SubmissionFormWidget(QWidget):
|
||||
_, result = self.pyd.check_kit_integrity()
|
||||
report.add_result(result)
|
||||
if len(result.results) > 0:
|
||||
# self.app.report.add_result(report)
|
||||
# self.app.report_result()
|
||||
return
|
||||
# logger.debug(f"PYD before transformation into SQL:\n\n{self.pyd}\n\n")
|
||||
base_submission, result = self.pyd.to_sql()
|
||||
@@ -370,14 +372,10 @@ class SubmissionFormWidget(QWidget):
|
||||
else:
|
||||
self.app.ctx.database_session.rollback()
|
||||
report.add_result(Result(msg="Overwrite cancelled", status="Information"))
|
||||
# self.app.report.add_result(report)
|
||||
# self.app.report_result()
|
||||
return report
|
||||
# NOTE: code 2: No RSL plate number given
|
||||
case 2:
|
||||
report.add_result(result)
|
||||
# self.app.report.add_result(report)
|
||||
# self.app.report_result()
|
||||
return report
|
||||
case _:
|
||||
pass
|
||||
@@ -451,7 +449,6 @@ class SubmissionFormWidget(QWidget):
|
||||
info[item] = value
|
||||
for k, v in info.items():
|
||||
self.pyd.set_attribute(key=k, value=v)
|
||||
# NOTE: return submission
|
||||
report.add_result(report)
|
||||
return report
|
||||
|
||||
@@ -527,18 +524,18 @@ class SubmissionFormWidget(QWidget):
|
||||
match key:
|
||||
case 'submitting_lab':
|
||||
add_widget = MyQComboBox(scrollWidget=parent)
|
||||
# lookup organizations suitable for submitting_lab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
|
||||
# NOTE: lookup organizations suitable for submitting_lab (ctx: self.InfoItem.SubmissionFormWidget.SubmissionFormContainer.AddSubForm )
|
||||
labs = [item.name for item in Organization.query()]
|
||||
# try to set closest match to top of list
|
||||
# NOTE: try to set closest match to top of list
|
||||
try:
|
||||
labs = difflib.get_close_matches(value, labs, len(labs), 0)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
# set combobox values to lookedup values
|
||||
# NOTE: set combobox values to lookedup values
|
||||
add_widget.addItems(labs)
|
||||
add_widget.setToolTip("Select submitting lab.")
|
||||
case 'extraction_kit':
|
||||
# if extraction kit not available, all other values fail
|
||||
# NOTE: if extraction kit not available, all other values fail
|
||||
if not check_not_nan(value):
|
||||
msg = AlertPop(message="Make sure to check your extraction kit in the excel sheet!",
|
||||
status="warning")
|
||||
@@ -573,8 +570,6 @@ class SubmissionFormWidget(QWidget):
|
||||
add_widget.addItems(cats)
|
||||
add_widget.setToolTip("Enter submission category or select from list.")
|
||||
case _:
|
||||
# if key in sub_obj.get_default_info("form_ignore", submission_type=submission_type):
|
||||
# return None
|
||||
if key in sub_obj.timestamps():
|
||||
add_widget = MyQDateEdit(calendarPopup=True, scrollWidget=parent)
|
||||
# NOTE: sets submitted date based on date found in excel sheet
|
||||
@@ -593,7 +588,6 @@ class SubmissionFormWidget(QWidget):
|
||||
if add_widget is not None:
|
||||
add_widget.setObjectName(key)
|
||||
add_widget.setParent(parent)
|
||||
# add_widget.setStyleSheet(main_form_style)
|
||||
return add_widget
|
||||
|
||||
def update_missing(self):
|
||||
@@ -649,7 +643,6 @@ class SubmissionFormWidget(QWidget):
|
||||
self.label = self.ReagentParsedLabel(reagent=reagent)
|
||||
layout.addWidget(self.label)
|
||||
self.lot = self.ReagentLot(scrollWidget=parent, reagent=reagent, extraction_kit=extraction_kit)
|
||||
# self.lot.setStyleSheet(main_form_style)
|
||||
layout.addWidget(self.lot)
|
||||
# NOTE: Remove spacing between reagents
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -738,8 +731,6 @@ class SubmissionFormWidget(QWidget):
|
||||
if check_not_nan(reagent.lot):
|
||||
relevant_reagents.insert(0, str(reagent.lot))
|
||||
else:
|
||||
# looked_up_rt = KitTypeReagentRoleAssociation.query(reagent_role=reagent.role,
|
||||
# kit_type=extraction_kit)
|
||||
try:
|
||||
looked_up_reg = Reagent.query(lot_number=looked_up_rt.last_used)
|
||||
except AttributeError:
|
||||
@@ -768,22 +759,4 @@ class SubmissionFormWidget(QWidget):
|
||||
self.setObjectName(f"lot_{reagent.role}")
|
||||
self.addItems(relevant_reagents)
|
||||
self.setToolTip(f"Enter lot number for the reagent used for {reagent.role}")
|
||||
# self.setStyleSheet(main_form_style)
|
||||
|
||||
# def relevant_reagents(self, assoc: KitTypeReagentRoleAssociation):
|
||||
# # logger.debug(f"Attempting lookup of reagents by type: {reagent.type}")
|
||||
# lookup = Reagent.query(reagent_role=assoc.reagent_role)
|
||||
# try:
|
||||
# regex = assoc.uses['exclude_regex']
|
||||
# except KeyError:
|
||||
# regex = "^$"
|
||||
# relevant_reagents = [item for item in lookup if
|
||||
# not check_regex_match(pattern=regex, check=str(item.lot))]
|
||||
# for rel_reagent in relevant_reagents:
|
||||
# # # NOTE: extract strings from any sets.
|
||||
# # if isinstance(rel_reagent, set):
|
||||
# # for thing in rel_reagent:
|
||||
# # yield thing
|
||||
# # elif isinstance(rel_reagent, str):
|
||||
# # yield rel_reagent
|
||||
# yield rel_reagent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from PyQt6.QtCore import QSignalBlocker
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QComboBox, QLabel
|
||||
from PyQt6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel
|
||||
from backend.db import Organization
|
||||
from backend.excel import ReportMaker
|
||||
from tools import Report
|
||||
@@ -34,7 +34,6 @@ class Summary(QWidget):
|
||||
for org in [org.name for org in Organization.query()]:
|
||||
self.org_select.addItem(org)
|
||||
self.org_select.model().itemChanged.connect(self.get_report)
|
||||
# self.org_select.itemChecked.connect(self.get_report)
|
||||
self.layout.addWidget(self.save_excel_button, 0, 2, 1, 1)
|
||||
self.layout.addWidget(self.save_pdf_button, 0, 3, 1, 1)
|
||||
self.layout.addWidget(self.webview, 2, 0, 1, 4)
|
||||
|
||||
Reference in New Issue
Block a user