Code cleanup.

This commit is contained in:
lwark
2025-04-11 14:35:00 -05:00
parent ae6717bc77
commit 58f5d361b3
20 changed files with 53 additions and 689 deletions

View File

@@ -8,8 +8,6 @@ if check_if_app():
# NOTE: setup custom logger
logging.setLoggerClass(CustomLogger)
# logger = logging.getLogger("submissions")
# logger = setup_logger(verbosity=3)
from PyQt6.QtWidgets import QApplication
from frontend.widgets.app import App

View File

@@ -1573,17 +1573,13 @@ class BacterialCulture(BasicSubmission):
def get_provisional_controls(self, include: List[str] = []):
# NOTE To ensure Samples are done last.
include = sorted(include)
logger.debug(include)
# logger.debug(include)
pos_str = "(ATCC)|(MCS)"
pos_regex = re.compile(rf"^{pos_str}")
neg_str = "(EN)"
neg_regex = re.compile(rf"^{neg_str}")
total_str = pos_str + "|" + neg_str
total_regex = re.compile(rf"^{total_str}")
output = []
for item in include:
# if self.controls:
# logger.debug(item)
match item:
case "Positive":
if self.controls:

View File

@@ -21,7 +21,6 @@ class IridaFigure(CustomFigure):
self.df = df
self.construct_chart(df=df, modes=modes, start_date=settings['start_date'], end_date=settings['end_date'])
def construct_chart(self, df: pd.DataFrame, modes: list, start_date: date, end_date:date):
"""
Creates a plotly chart for controls from a pandas dataframe

View File

@@ -17,4 +17,4 @@ from .submission_widget import *
from .summary import *
from .turnaround import *
from .omni_add_edit import *
from .omni_manager import *
from .omni_manager_pydant import *

View File

@@ -1,7 +1,7 @@
"""
Constructs main application.
"""
import getpass, logging, webbrowser, sys, shutil
import getpass, logging, webbrowser, sys
from pprint import pformat
from PyQt6.QtCore import qInstallMessageHandler
from PyQt6.QtWidgets import (
@@ -13,8 +13,7 @@ from PyQt6.QtGui import QAction
from pathlib import Path
from markdown import markdown
from pandas import ExcelWriter
from __init__ import project_path
from backend import SubmissionType, Reagent, BasicSample, Organization, KitType, BasicSubmission
from backend import Reagent, BasicSample, Organization, KitType, BasicSubmission
from tools import (
check_if_app, Settings, Report, jinja_template_loading, check_authorization, page_size, is_power_user,
under_development
@@ -30,7 +29,6 @@ from .summary import Summary
from .turnaround import TurnaroundTime
from .concentrations import Concentrations
from .omni_search import SearchBox
from .omni_manager import ManagerWindow
logger = logging.getLogger(f'submissions.{__name__}')
@@ -83,7 +81,6 @@ class App(QMainWindow):
helpMenu.addAction(self.githubAction)
fileMenu.addAction(self.importAction)
fileMenu.addAction(self.archiveSubmissionsAction)
# fileMenu.addAction(self.yamlImportAction)
methodsMenu.addAction(self.searchSample)
maintenanceMenu.addAction(self.joinExtractionAction)
maintenanceMenu.addAction(self.joinPCRAction)
@@ -123,7 +120,7 @@ class App(QMainWindow):
"""
connect menu and tool bar item to functions
"""
self.importAction.triggered.connect(self.table_widget.formwidget.importSubmission)
self.importAction.triggered.connect(lambda fname: self.table_widget.formwidget.import_submission_function(fname=fname))
self.addReagentAction.triggered.connect(self.table_widget.formwidget.add_reagent)
self.joinExtractionAction.triggered.connect(self.table_widget.sub_wid.link_extractions)
self.joinPCRAction.triggered.connect(self.table_widget.sub_wid.link_pcr)
@@ -192,7 +189,6 @@ class App(QMainWindow):
# TODO: Change this to the Pydantic version.
def manage_orgs(self):
from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd
# dlg = ManagerWindow(parent=self, object_type=Organization, extras=[], add_edit='edit', managers=set())
dlg = ManagerWindowPyd(parent=self, object_type=Organization, extras=[], add_edit='edit', managers=set())
if dlg.exec():
new_org = dlg.parse_form()
@@ -202,10 +198,10 @@ class App(QMainWindow):
from frontend.widgets.omni_manager_pydant import ManagerWindow as ManagerWindowPyd
dlg = ManagerWindowPyd(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set())
if dlg.exec():
logger.debug("\n\nBeginning parsing\n\n")
# logger.debug("\n\nBeginning parsing\n\n")
output = dlg.parse_form()
logger.debug(f"Kit output: {pformat(output.__dict__)}")
logger.debug("\n\nBeginning transformation\n\n")
# logger.debug(f"Kit output: {pformat(output.__dict__)}")
# logger.debug("\n\nBeginning transformation\n\n")
sql = output.to_sql()
assert isinstance(sql, KitType)
sql.save()

View File

@@ -1,7 +1,7 @@
"""
Pane showing BC control concentrations summary.
"""
from PyQt6.QtWidgets import QWidget, QPushButton, QCheckBox, QLabel
from PyQt6.QtWidgets import QWidget, QPushButton, QLabel
from .info_tab import InfoPane
from backend.excel.reports import ConcentrationMaker
from frontend.visualizations.concentrations_chart import ConcentrationsChart
@@ -25,6 +25,7 @@ class Concentrations(InfoPane):
self.pos_neg = CheckableComboBox(parent=self)
self.pos_neg.model().itemChanged.connect(self.update_data)
self.pos_neg.setEditable(False)
self.pos_neg.addItem("Select", header=True)
self.pos_neg.addItem("Positive")
self.pos_neg.addItem("Negative")
self.pos_neg.addItem("Samples", start_checked=False)
@@ -46,7 +47,6 @@ class Concentrations(InfoPane):
super().update_data()
months = self.diff_month(self.start_date, self.end_date)
# logger.debug(f"Box checked: {self.all_box.isChecked()}")
# chart_settings = dict(start_date=self.start_date, end_date=self.end_date, controls_only=self.all_box.isChecked())
chart_settings = dict(start_date=self.start_date, end_date=self.end_date,
include=include)
self.report_obj = ConcentrationMaker(**chart_settings)

View File

@@ -108,7 +108,7 @@ class ControlsViewer(InfoPane):
parent=self,
months=months
)
logger.debug(f"Chart settings: {chart_settings}")
# logger.debug(f"Chart settings: {chart_settings}")
self.fig = self.archetype.instance_class.make_chart(chart_settings=chart_settings, parent=self, ctx=self.app.ctx)
self.report_obj = ChartReportMaker(df=self.fig.df, sheet_name=self.archetype.name)
if issubclass(self.fig.__class__, CustomFigure):

View File

@@ -1,6 +1,5 @@
from PyQt6.QtWidgets import (
QLabel, QVBoxLayout, QDialog,
QDialogButtonBox, QMessageBox, QComboBox
QVBoxLayout, QDialog, QDialogButtonBox
)
from .misc import CheckableComboBox, StartEndDatePicker
from backend.db import SubmissionType
@@ -28,7 +27,8 @@ class DateTypePicker(QDialog):
self.setLayout(self.layout)
def parse_form(self):
sub_types = [self.typepicker.itemText(i) for i in range(self.typepicker.count()) if self.typepicker.itemChecked(i)]
# sub_types = [self.typepicker.itemText(i) for i in range(self.typepicker.count()) if self.typepicker.itemChecked(i)]
sub_types = self.typepicker.get_checked()
start_date = self.datepicker.start_date.date().toPyDate()
end_date = self.datepicker.end_date.date().toPyDate()
return dict(submissiontype=sub_types, start_date=start_date, end_date=end_date)

View File

@@ -74,10 +74,10 @@ class EquipmentUsage(QDialog):
self.layout.addWidget(self.check, 0, 0)
self.check.stateChanged.connect(self.check_all)
for iii, item in enumerate(["Role", "Equipment", "Process", "Tips"], start=1):
l = QLabel(item)
l.setMaximumWidth(200)
l.setMinimumWidth(200)
self.layout.addWidget(l, 0, iii, alignment=Qt.AlignmentFlag.AlignRight)
label = QLabel(item)
label.setMaximumWidth(200)
label.setMinimumWidth(200)
self.layout.addWidget(label, 0, iii, alignment=Qt.AlignmentFlag.AlignRight)
self.setLayout(self.layout)
def check_all(self):

View File

@@ -1,7 +1,7 @@
"""
A pane to show info e.g. cost reports and turnaround times.
"""
from datetime import date, datetime
from datetime import date
from PyQt6.QtCore import QSignalBlocker
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QWidget, QGridLayout
@@ -32,11 +32,9 @@ class InfoPane(QWidget):
@report_result
def update_data(self, *args, **kwargs):
report = Report()
# self.start_date = self.datepicker.start_date.date().toPyDate()
# self.end_date = self.datepicker.end_date.date().toPyDate()
self.start_date = self.datepicker.start_date.date().toPyDate()
self.end_date = self.datepicker.end_date.date().toPyDate()
logger.debug(f"Start date: {self.start_date}, End date: {self.end_date}")
# logger.debug(f"Start date: {self.start_date}, End date: {self.end_date}")
if self.datepicker.start_date.date() > self.datepicker.end_date.date():
lastmonth = self.datepicker.end_date.date().addDays(-31)
msg = f"Start date after end date is not allowed! Setting to {lastmonth.toString()}."

View File

@@ -44,7 +44,7 @@ class StartEndDatePicker(QWidget):
class CheckableComboBox(QComboBox):
# once there is a checkState set, it is rendered
# here we assume default Unchecked
# here we assume default checked
def addItem(self, item, header: bool = False, start_checked: bool = True):
super(CheckableComboBox, self).addItem(item)
@@ -64,8 +64,7 @@ class CheckableComboBox(QComboBox):
self.updated.emit()
def get_checked(self):
checked = [self.itemText(i) for i in range(self.count()) if self.itemChecked(i)]
return checked
return [self.itemText(i) for i in range(self.count()) if self.itemChecked(i)]
class Pagifier(QWidget):

View File

@@ -1,7 +1,6 @@
"""
A widget to handle adding/updating any database object.
"""
from copy import deepcopy
from datetime import date
from pprint import pformat
from typing import Any, Tuple
@@ -11,7 +10,7 @@ from PyQt6.QtWidgets import (
QCheckBox
)
from sqlalchemy import String, TIMESTAMP, INTEGER, FLOAT, JSON, BLOB
from sqlalchemy.orm import InstrumentedAttribute, ColumnProperty
from sqlalchemy.orm import ColumnProperty
import logging
from sqlalchemy.orm.relationships import _RelationshipDeclared
from tools import Report, report_result
@@ -23,18 +22,11 @@ class AddEdit(QDialog):
def __init__(self, parent, instance: Any | None = None, managers: set = set()):
super().__init__(parent)
logger.debug(f"Managers: {managers}")
# logger.debug(f"Managers: {managers}")
self.instance = instance
self.object_type = instance.__class__
# self.managers = deepcopy(managers)
self.managers = managers
# if instance.level < 2:
# try:
# logger.debug(f"Parent instance: {self.parent().instance}")
# self.managers.add(self.parent().instance)
# except AttributeError:
# pass
logger.debug(f"Managers: {managers}")
# logger.debug(f"Managers: {managers}")
self.layout = QGridLayout(self)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
@@ -72,11 +64,11 @@ class AddEdit(QDialog):
report = Report()
parsed = {result[0].strip(":"): result[1] for result in
[item.parse_form() for item in self.findChildren(EditProperty)] if result[0]}
logger.debug(f"Parsed form: {parsed}")
# logger.debug(f"Parsed form: {parsed}")
model = self.object_type.pydantic_model
logger.debug(f"Model type: {model.__name__}")
# logger.debug(f"Model type: {model.__name__}")
if model.__name__ == "PydElastic":
logger.debug(f"We have an elastic model.")
# logger.debug(f"We have an elastic model.")
parsed['instance'] = self.instance
# NOTE: Hand-off to pydantic model for validation.
# NOTE: Also, why am I not just using the toSQL method here. I could write one for contacts.
@@ -120,7 +112,7 @@ class EditProperty(QWidget):
# logger.debug(self.parent().managers)
for manager in self.parent().managers:
if self.name in manager.aliases:
logger.debug(f"Name: {self.name} is in aliases: {manager.aliases}")
# logger.debug(f"Name: {self.name} is in aliases: {manager.aliases}")
choices = [manager.name]
self.widget.setEnabled(False)
break
@@ -139,7 +131,7 @@ class EditProperty(QWidget):
self.widget.addItems(choices)
def column_property_set(self, column_property, value=None):
logger.debug(f"Column Property: {column_property['class_attr'].expression} {column_property}, Value: {value}")
# logger.debug(f"Column Property: {column_property['class_attr'].expression} {column_property}, Value: {value}")
match column_property['class_attr'].expression.type:
case String():
if value is None:
@@ -184,7 +176,8 @@ class EditProperty(QWidget):
check = self.widget
except AttributeError:
return None, None
match self.widget:
# match self.widget
match check:
case QLineEdit():
value = self.widget.text()
case QDateEdit():
@@ -198,5 +191,3 @@ class EditProperty(QWidget):
case _:
value = None
return self.objectName(), value

View File

@@ -1,594 +0,0 @@
"""
Provides a screen for managing all attributes of a database object.
"""
import json, logging
from pprint import pformat
from typing import Any, List, Literal
from PyQt6.QtCore import QSortFilterProxyModel, Qt
from PyQt6.QtGui import QAction, QCursor
from PyQt6.QtWidgets import (
QLabel, QDialog,
QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit, QMenu,
QDoubleSpinBox, QSpinBox, QCheckBox, QTextEdit, QVBoxLayout, QHBoxLayout
)
from sqlalchemy import String, TIMESTAMP, FLOAT, INTEGER, JSON, BLOB
from sqlalchemy.orm import InstrumentedAttribute
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.orm.relationships import _RelationshipDeclared
from pandas import DataFrame
from backend import db
from tools import check_object_in_manager
from .omni_search import SearchBox
from frontend.widgets.submission_table import pandasModel
logger = logging.getLogger(f"submissions.{__name__}")
class ManagerWindow(QDialog):
"""
Initially this is a window to manage Organization Contacts, but hope to abstract it more later.
"""
def __init__(self, parent,
object_type: Any,
extras: List[str],
manager: Any | None = None,
add_edit: Literal['add', 'edit'] = 'edit',
**kwargs):
super().__init__(parent)
self.class_object = self.original_type = object_type
self.add_edit = add_edit
# NOTE: Should I pass in an instance?
self.instance = None
if manager is None:
try:
self.manager = self.parent().instance
except AttributeError:
self.manager = None
else:
self.manager = manager
# logger.debug(f"Managers: {managers}")
self.extras = extras
self.context = kwargs
self.layout = QGridLayout(self)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.setMinimumSize(600, 600)
sub_classes = ["Any"] + [cls.__name__ for cls in self.class_object.__subclasses__()]
if len(sub_classes) > 1:
self.sub_class = QComboBox(self)
self.sub_class.setObjectName("sub_class")
self.sub_class.addItems(sub_classes)
self.sub_class.currentTextChanged.connect(self.update_options)
self.sub_class.setEditable(False)
self.sub_class.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.sub_class, 0, 0)
else:
self.sub_class = None
if self.add_edit == "edit":
self.options = QComboBox(self)
self.options.setObjectName("options")
self.update_options()
else:
self.update_data(initial=True)
self.setLayout(self.layout)
self.setWindowTitle(f"Manage {self.class_object.__name__} - Manager: {self.manager}")
def update_options(self) -> None:
"""
Changes form inputs based on sample type
"""
# logger.debug(f"Instance: {self.instance}")
if self.sub_class:
self.class_object = getattr(db, self.sub_class.currentText())
# logger.debug(f"From update options, managers: {self.managers}")
try:
query_kwargs = {self.parent().instance.query_alias: self.parent().instance}
except AttributeError as e:
# logger.debug(f"Couldn't set query kwargs due to: {e}")
query_kwargs = {}
# logger.debug(f"Query kwargs: {query_kwargs}")
options = [item.name for item in self.class_object.query(**query_kwargs)]
# logger.debug(f"self.class_object: {self.class_object}")
if self.instance:
try:
inserter = options.pop(options.index(self.instance.name))
except ValueError:
inserter = self.instance.name
options.insert(0, inserter)
self.options.clear()
self.options.addItems(options)
self.options.setEditable(False)
self.options.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.options, 1, 0, 1, 1)
self.add_button = QPushButton("Add New")
self.layout.addWidget(self.add_button, 1, 1, 1, 1)
self.add_button.clicked.connect(self.add_new)
self.options.currentTextChanged.connect(self.update_data)
# logger.debug(f"Instance: {self.instance}")
self.update_data()
def update_data(self, initial: bool = False) -> None:
"""
Performs updating of widgets on first run and after options change.
Returns:
None
"""
# NOTE: Remove all old widgets.
deletes = [item for item in self.findChildren(EditProperty)] + \
[item for item in self.findChildren(EditRelationship)] + \
[item for item in self.findChildren(QDialogButtonBox)]
for item in deletes:
item.setParent(None)
# logger.debug(f"Current options text lower: {self.options.currentText().lower()}")
if self.add_edit == "edit" and initial:
# logger.debug(f"Querying with {self.options.currentText()}")
self.instance = self.class_object.query(name=self.options.currentText(), limit=1)
# logger.debug(f"Instance: {self.instance}")
if not self.instance:
self.instance = self.class_object()
# logger.debug(f"self.instance: {self.instance}")
fields = self.instance.omnigui_instance_dict
for key, field in fields.items():
try:
value = getattr(self.instance, key)
except AttributeError:
value = None
match field['class_attr'].property:
# NOTE: ColumnProperties will be directly edited.
case ColumnProperty():
# NOTE: field.property.expression.type gives db column type eg. STRING or TIMESTAMP
widget = EditProperty(self, key=key, column_type=field,
value=value)
# NOTE: RelationshipDeclareds will be given a list of existing related objects.
case _RelationshipDeclared():
if key != "submissions":
# NOTE: field.comparator.class_object.class_ gives the relationship class
widget = EditRelationship(self, key=key, class_object=field['class_attr'].comparator.entity.class_,
value=value)
else:
continue
case _:
continue
if widget:
self.layout.addWidget(widget, self.layout.rowCount(), 0, 1, 2)
# NOTE: Add OK|Cancel to bottom of dialog.
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
def parse_form(self) -> Any:
"""
Returns the instance associated with this window.
Returns:
Any: The instance with updated fields.
"""
results = [item.parse_form() for item in self.findChildren(EditProperty)]
for result in results:
# logger.debug(f"Incoming result: {result}")
setattr(self.instance, result['field'], result['value'])
# logger.debug(f"Set result: {getattr(self.instance, result['field'])}")
results = [item.parse_form() for item in self.findChildren(EditRelationship)]
for result in results:
logger.debug(f"Incoming result: {result}")
if not getattr(self.instance, result['field']):
setattr(self.instance, result['field'], result['value'])
logger.debug(f"Set result: {getattr(self.instance, result['field'])}")
logger.debug(f"Instance coming from parsed form: {self.instance.__dict__}")
return self.instance
def add_new(self):
new_instance = self.class_object()
self.instance = new_instance
self.update_options()
def add_to_json(self, caller_child=None):
try:
name = caller_child.objectName()
except AttributeError:
name = "No Caller"
jsonedit = JsonEditScreen(parent=self, key=name)
if jsonedit.exec():
data = jsonedit.parse_form()
logger.debug(f"Data: {pformat(data)}")
current_value = getattr(self.instance, name)
if isinstance(jsonedit.json_field, dict):
value = data
elif isinstance(jsonedit.json_field, list):
if isinstance(data, list):
value = current_value + data
else:
value = current_value + [data]
setattr(self.instance, name, value)
def toggle_textedit(self, caller_child=None):
already_exists = self.findChildren(LargeTextEdit)
if not already_exists:
try:
name = caller_child.objectName()
except AttributeError:
name = "No Caller"
logger.debug(f"Name: {name}, instance: {self.instance}")
textedit = LargeTextEdit(parent=self, key=name)
self.layout.addWidget(textedit, 1, self.layout.columnCount(), self.layout.rowCount() - 1, 1)
data = getattr(self.instance, name)
logger.debug(f"Data: {data}")
data = json.dumps(data, indent=4)
textedit.widget.setText(data)
else:
for item in already_exists:
item.setParent(None)
item.destroy()
class EditProperty(QWidget):
def __init__(self, parent: ManagerWindow, key: str, column_type: Any, value):
super().__init__(parent)
self.label = QLabel(key.title().replace("_", " "))
self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 1)
self.setObjectName(key)
logger.debug(f"Column type for {key}: {column_type['class_attr'].property.expression.type}")
match column_type['class_attr'].property.expression.type:
case String():
self.widget = QLineEdit(self)
self.widget.setText(value)
case INTEGER():
if isinstance(column_type['instance_attr'], bool):
self.widget = QCheckBox()
self.widget.setChecked(value)
else:
if value is None:
value = 0
self.widget = QSpinBox()
self.widget.setMaximum(999)
self.widget.setValue(value)
case FLOAT():
if not value:
value = 0.0
self.widget = QDoubleSpinBox()
self.widget.setMaximum(999.99)
self.widget.setValue(value)
case TIMESTAMP():
self.widget = QDateEdit(self)
self.widget.setDate(value)
case JSON():
self.widget = JsonEditButton(parent=self, key=key)
self.widget.viewButton.clicked.connect(lambda: self.parent().toggle_textedit(self.widget))
self.widget.addButton.clicked.connect(lambda: self.parent().add_to_json(self.widget))
case BLOB():
self.widget = QLabel("BLOB Under construction")
case _:
self.widget = None
self.layout.addWidget(self.widget, 0, 1, 1, 3)
self.setLayout(self.layout)
def parse_form(self):
match self.widget:
case QLineEdit():
value = self.widget.text()
case QDateEdit():
value = self.widget.date()
case QSpinBox() | QDoubleSpinBox():
value = self.widget.value()
case QCheckBox():
value = self.widget.isChecked()
case _:
value = None
return dict(field=self.objectName(), value=value)
class EditRelationship(QWidget):
def __init__(self, parent, key: str, class_object: Any, value):
super().__init__(parent)
self.class_object = class_object #: The class of interest
self.setParent(parent)
# logger.debug(f"Edit relationship class_object: {self.class_object}")
self.label = QLabel(key.title().replace("_", " "))
self.setObjectName(key) #: key is the name of the relationship this represents
self.relationship = getattr(self.parent().instance.__class__,
key) #: relationship object for type differentiation
# logger.debug(f"self.relationship: {self.relationship}")
# logger.debug(f"Relationship uses list: {self.relationship.property.uselist}")
# NOTE: value is a database object in this case.
# logger.debug(f"Data for edit relationship: {self.data}")
self.widget = QTableView()
self.add_button = QPushButton("Add New")
self.add_button.clicked.connect(self.add_new)
self.existing_button = QPushButton("Add Existing")
self.existing_button.clicked.connect(self.add_existing)
# self.existing_button.setEnabled(self.class_object.level == 1)
if not isinstance(value, list):
if value is not None:
value = [value]
else:
value = []
self.data = value
checked_manager, is_primary = check_object_in_manager(self.parent().manager, self.objectName())
if checked_manager:
logger.debug(f"Checked manager for {self.objectName()}: {checked_manager}")
logger.debug(f"Omni will inherit: {self.class_object.omni_inheritable} from {self.parent().class_object}")
if checked_manager is not None and not self.data and self.objectName() in self.parent().class_object.omni_inheritable:
logger.debug(f"Setting {checked_manager} in self.data")
self.data = [checked_manager]
if not self.relationship.property.uselist:
self.add_button.setEnabled(False)
self.existing_button.setEnabled(False)
if is_primary:
self.widget.setEnabled(False)
self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 5)
self.layout.addWidget(self.widget, 1, 0, 1, 8)
self.layout.addWidget(self.add_button, 0, 6, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
self.layout.addWidget(self.existing_button, 0, 7, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
self.setLayout(self.layout)
self.set_data()
def update_buttons(self):
if not self.relationship.property.uselist and len(self.data) >= 1:
logger.debug(f"Property {self.relationship} doesn't use list and data is of length: {len(self.data)}")
self.add_button.setEnabled(False)
self.existing_button.setEnabled(False)
else:
self.add_button.setEnabled(True)
self.existing_button.setEnabled(True)
def parse_row(self, x):
context = {item: x.sibling(x.row(), self.df.columns.get_loc(item)).data() for item in self.df.columns}
try:
object = self.class_object.query(**context)
except KeyError:
object = None
self.widget.doubleClicked.disconnect()
self.add_edit(instance=object)
def add_new(self, instance: Any = None):
# NOTE: if an existing instance is not being edited, create a new instance
if not instance:
instance = self.class_object()
manager = self.parent().manager
# logger.debug(f"Managers going into add new: {managers}")
dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], manager=manager, add_edit="add")
if dlg.exec():
new_instance = dlg.parse_form()
# NOTE: My custom __setattr__ should take care of any list problems.
self.parent().instance.__setattr__(self.objectName(), new_instance)
self.parent().update_data()
def add_existing(self):
dlg = SearchBox(self, object_type=self.class_object, returnable=True, extras=[])
if dlg.exec():
rows = dlg.return_selected_rows()
for row in rows:
# logger.debug(f"Querying with {row}")
instance = self.class_object.query(**row)
# NOTE: My custom __setattr__ should take care of any list problems.
self.parent().instance.__setattr__(self.objectName(), instance)
self.parent().update_data()
def set_data(self) -> None:
"""
sets data in model
"""
logger.debug(f"Self.data: {self.data}")
try:
records = [{k: v['instance_attr'] for k, v in item.omnigui_instance_dict.items()} for item in self.data]
except AttributeError:
records = []
# logger.debug(f"Records: {records}")
self.df = DataFrame.from_records(records)
try:
self.columns_of_interest = [dict(name=item, column=self.df.columns.get_loc(item)) for item in self.extras]
except (KeyError, AttributeError):
self.columns_of_interest = []
try:
self.df['id'] = self.df['id'].apply(str)
self.df['id'] = self.df['id'].str.zfill(4)
except KeyError as e:
logger.error(f"Could not alter id to string due to KeyError: {e}")
proxy_model = QSortFilterProxyModel()
proxy_model.setSourceModel(pandasModel(self.df))
self.widget.setModel(proxy_model)
self.widget.resizeColumnsToContents()
self.widget.resizeRowsToContents()
self.widget.setSortingEnabled(True)
self.widget.doubleClicked.connect(self.parse_row)
self.update_buttons()
def contextMenuEvent(self, event):
"""
Creates actions for right click menu events.
Args:
event (_type_): the item of interest
"""
if not self.widget.isEnabled():
logger.warning(f"{self.objectName()} is disabled.")
return
id = self.widget.selectionModel().currentIndex()
# NOTE: the overly complicated {column_name: row_value} dictionary construction
row_data = {self.df.columns[column]: self.widget.model().index(id.row(), column).data() for column in
range(self.widget.model().columnCount())}
logger.debug(f"Row data: {row_data}")
logger.debug(f"Attempting to grab {self.objectName()} from {self.parent().instance}")
object = getattr(self.parent().instance, self.objectName())
if isinstance(object, list):
object = next((item for item in object if item.check_all_attributes(attributes=row_data)), None)
logger.debug(f"Object of interest: {object}")
# logger.debug(object)
self.menu = QMenu(self)
try:
remove_action = QAction(f"Remove {object.name}", self)
except AttributeError:
remove_action = QAction(f"Remove object", self)
remove_action.triggered.connect(lambda: self.remove_item(object=object))
self.menu.addAction(remove_action)
try:
edit_action = QAction(f"Edit {object.name}", self)
except AttributeError:
edit_action = QAction(f"Edit object", self)
edit_action.triggered.connect(lambda: self.add_new(instance=object))
self.menu.addAction(edit_action)
self.menu.popup(QCursor.pos())
def remove_item(self, object):
logger.debug(f"Attempting to remove {object} from {self.parent().instance.__dict__}")
editor = getattr(self.parent().instance, self.objectName().lower())
logger.debug(f"Editor: {editor}")
if object == self.parent().manager:
logger.error(f"Can't remove manager object.")
return
try:
self.data.remove(object)
except (AttributeError, ValueError) as e:
logger.error(f"Could remove object from self.data due to: {e}")
self.data = []
try:
logger.debug(f"Using remove technique")
editor.remove(object)
except AttributeError as e:
logger.error(f"Remove failed using set to None for {self.objectName().lower()}.")
setattr(self.parent().instance, self.objectName().lower(), None)
except ValueError as e:
logger.error(f"Remove failed for {self.objectName().lower()} due to {e}.")
self.parent().instance.save()
self.set_data()
def parse_form(self):
return dict(field=self.objectName(), value=self.data)
class JsonEditButton(QWidget):
def __init__(self, parent, key: str):
super().__init__(parent)
self.setParent(parent)
self.setObjectName(key)
self.addButton = QPushButton("Add Entry", parent=self)
self.viewButton = QPushButton("View >>>", parent=self)
self.layout = QGridLayout()
self.layout.addWidget(self.addButton, 0, 0)
self.layout.addWidget(self.viewButton, 0, 1)
self.setLayout(self.layout)
class JsonEditScreen(QDialog):
def __init__(self, parent, key: str):
super().__init__(parent)
self.class_obj = parent.class_object
self.layout = QGridLayout()
self.setWindowTitle(key)
self.json_field = self.class_obj.json_edit_fields
match self.json_field:
case dict():
for key, value in self.json_field.items():
logger.debug(f"Key: {key}, Value: {value}")
row = self.layout.rowCount()
self.layout.addWidget(QLabel(key), row, 0)
match value:
case "int":
self.widget = QSpinBox()
case "str":
self.widget = QLineEdit()
case dict():
self.widget = DictionaryJsonSubEdit(parent=self, key=key, dic=value)
case _:
continue
self.widget.setObjectName(key)
self.layout.addWidget(self.widget, row, 1)
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
self.setLayout(self.layout)
def parse_form(self):
widgets = [item for item in self.findChildren(QWidget) if item.objectName() in self.json_field.keys()]
logger.debug(f"Widgets: {widgets}")
logger.debug(type(self.json_field))
if isinstance(self.json_field, dict):
output = {}
elif isinstance(self.json_field, list):
output = []
else:
raise ValueError(f"Inappropriate data type: {type(self.json_field)}")
for widget in widgets:
logger.debug(f"JsonEditScreen Widget: {widget}")
key = widget.objectName()
match widget:
case QSpinBox():
value = widget.value()
case QLineEdit():
value = widget.text()
case DictionaryJsonSubEdit():
value = widget.parse_form()
case _:
continue
if isinstance(self.json_field, dict):
output[key] = value
elif isinstance(self.json_field, list):
if isinstance(value, list):
output += value
else:
output.append(value)
else:
raise ValueError(f"Inappropriate data type: {type(self.json_field)}")
return output
class DictionaryJsonSubEdit(QWidget):
def __init__(self, parent, key, dic: dict):
super().__init__(parent)
self.layout = QHBoxLayout()
self.setObjectName(key)
self.data = dic
for key, value in self.data.items():
self.layout.addWidget(QLabel(key))
match value:
case "int":
self.widget = QSpinBox()
case "str":
self.widget = QLineEdit()
case dict():
self.widget = DictionaryJsonSubEdit(parent, key=key, dic=value)
self.widget.setObjectName(key)
self.layout.addWidget(self.widget)
self.setLayout(self.layout)
def parse_form(self):
widgets = [item for item in self.findChildren(QWidget) if item.objectName() in self.data.keys()]
logger.debug(f"Widgets: {widgets}")
output = {}
for widget in widgets:
logger.debug(f"DictionaryJsonSubEdit Widget: {widget}")
key = widget.objectName()
match widget:
case QSpinBox():
value = widget.value()
case QLineEdit():
value = widget.text()
case DictionaryJsonSubEdit():
value = widget.parse_form()
case _:
continue
output[key] = value
return output
class LargeTextEdit(QWidget):
def __init__(self, parent, key: str):
super().__init__(parent)
self.setParent(parent)
self.setObjectName(key)
self.widget = QTextEdit()
self.layout = QVBoxLayout()
self.layout.addWidget(self.widget)
self.setLayout(self.layout)

View File

@@ -358,7 +358,6 @@ class EditRelationship(QWidget):
obj = getattr(self.parent().omni_object, self.objectName())
if isinstance(obj, list):
logger.debug(f"This is a list")
# obj = obj[index]
try:
# NOTE: Okay, this will not work for editing, since by definition not all attributes will line up.
# NOTE: Set items to search by in the Omni object itself?
@@ -389,7 +388,7 @@ class EditRelationship(QWidget):
"""
sets data in model
"""
logger.debug(f"Self.data: {self.data}")
# logger.debug(f"Self.data: {self.data}")
try:
records = [item.to_dataframe_dict() for item in self.data]
except AttributeError:

View File

@@ -172,7 +172,7 @@ class SearchResults(QTableView):
self.extras = extras + [item for item in deepcopy(self.object_type.searchables)]
except AttributeError:
self.extras = extras
logger.debug(f"Extras: {self.extras}")
# logger.debug(f"Extras: {self.extras}")
def setData(self, df: DataFrame) -> None:
"""

View File

@@ -1,14 +1,11 @@
import logging
from pathlib import Path
from typing import List, Generator
from typing import List
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import (QDialog, QPushButton, QVBoxLayout,
QDialogButtonBox, QTextEdit, QGridLayout)
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QGridLayout
from backend.validators import PydSubmission
from tools import get_application_from_parent, jinja_template_loading
@@ -16,6 +13,7 @@ env = jinja_template_loading()
logger = logging.getLogger(f"submissions.{__name__}")
class SampleChecker(QDialog):
def __init__(self, parent, title:str, pyd: PydSubmission):
@@ -32,7 +30,6 @@ class SampleChecker(QDialog):
self.channel = QWebChannel()
self.channel.registerObject('backend', self)
# NOTE: Used to maintain javascript functions.
# self.webview.page().setWebChannel(self.channel)
template = env.get_template("sample_checker.html")
template_path = Path(template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
@@ -72,7 +69,3 @@ class SampleChecker(QDialog):
sample['color'] = "black"
output.append(sample)
return output

View File

@@ -71,7 +71,7 @@ class SubmissionFormContainer(QWidget):
self.setStyleSheet('background-color: light grey;')
self.setAcceptDrops(True)
# NOTE: if import_drag is emitted, importSubmission will fire
self.import_drag.connect(self.importSubmission)
self.import_drag.connect(lambda fname: self.import_submission_function(fname=fname))
def dragEnterEvent(self, event):
"""
@@ -90,17 +90,6 @@ class SubmissionFormContainer(QWidget):
self.app.last_dir = fname.parent
self.import_drag.emit(fname)
@report_result
def importSubmission(self, fname: Path | None = None):
"""
import submission from excel sheet into form
"""
self.app.raise_()
self.app.activateWindow()
report = Report()
self.import_submission_function(fname)
return report
@report_result
def import_submission_function(self, fname: Path | None = None) -> Report:
"""
@@ -112,6 +101,8 @@ class SubmissionFormContainer(QWidget):
Returns:
Report: Object to give results of import.
"""
self.app.raise_()
self.app.activateWindow()
logger.info(f"\n\nStarting Import...\n\n")
report = Report()
# NOTE: Clear any previous forms.
@@ -436,7 +427,7 @@ class SubmissionFormWidget(QWidget):
if field is not None:
info[field] = value
self.pyd.reagents = reagents
logger.debug(f"Reagents from form: {reagents}")
# logger.debug(f"Reagents from form: {reagents}")
for item in self.recover:
if hasattr(self, item):
value = getattr(self, item)
@@ -446,6 +437,7 @@ class SubmissionFormWidget(QWidget):
report.add_result(report)
return report
class InfoItem(QWidget):
def __init__(self, parent: QWidget, key: str, value: dict, submission_type: str | SubmissionType | None = None,
@@ -691,7 +683,6 @@ class SubmissionFormWidget(QWidget):
if new:
dlg = QuestionAsker(title=f"Add {lot}?",
message=f"Couldn't find reagent type {self.reagent.role}: {lot} in the database.\n\nWould you like to add it?")
if dlg.exec():
wanted_reagent = self.parent.parent().add_reagent(instance=wanted_reagent)
return wanted_reagent, report

View File

@@ -40,7 +40,7 @@ class Summary(InfoPane):
None
"""
super().update_data()
orgs = [self.org_select.itemText(i) for i in range(self.org_select.count()) if self.org_select.itemChecked(i)]
orgs = self.org_select.get_checked()
self.report_obj = ReportMaker(start_date=self.start_date, end_date=self.end_date, organizations=orgs)
self.webview.setHtml(self.report_obj.html)
if self.report_obj.subs:

View File

@@ -27,6 +27,7 @@ from sqlalchemy.exc import IntegrityError as sqlalcIntegrityError
from pytz import timezone as tz
from functools import wraps
timezone = tz("America/Winnipeg")
logger = logging.getLogger(f"submissions.{__name__}")
@@ -248,7 +249,6 @@ def timer(func):
func (__function__): incoming function
"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
@@ -257,7 +257,6 @@ def timer(func):
run_time = end_time - start_time
print(f"Finished {func.__name__}() in {run_time:.4f} secs")
return value
return wrapper
@@ -483,12 +482,10 @@ def setup_lookup(func):
elif v is not None:
sanitized_kwargs[k] = v
return func(*args, **sanitized_kwargs)
return wrapper
def check_object_in_manager(manager: list, object_name: object) -> Tuple[Any, bool]:
# for manager in managers:
if manager is None:
return None, False
# logger.debug(f"Manager: {manager}, aliases: {manager.aliases}, Key: {object_name}")
@@ -535,6 +532,7 @@ def get_application_from_parent(widget):
class Result(BaseModel, arbitrary_types_allowed=True):
owner: str = Field(default="", validate_default=True)
code: int = Field(default=0)
msg: str | Exception
@@ -639,7 +637,6 @@ def rreplace(s: str, old: str, new: str) -> str:
def list_sort_dict(input_dict: dict, sort_list: list) -> dict:
# sort_list.reverse()
sort_list = reversed(sort_list)
for item in sort_list:
try:
@@ -661,7 +658,10 @@ def remove_key_from_list_of_dicts(input_list: list, key: str) -> list:
list: List of updated dictionaries
"""
for item in input_list:
del item[key]
try:
del item[key]
except KeyError:
continue
return input_list
@@ -688,6 +688,7 @@ def super_splitter(ins_str: str, substring: str, idx: int) -> str:
try:
return ins_str.split(substring)[idx]
except IndexError:
logger.error(f"Index of split {idx} not found.")
return ins_str
@@ -767,7 +768,6 @@ def under_development(func):
Result(owner=func.__str__(), code=1, msg=error_msg,
status="warning"))
return report
return wrapper
@@ -856,7 +856,6 @@ def create_holidays_for_year(year: int | None = None) -> List[date]:
offset = -d.weekday() # weekday == 0 means Monday
output = d + timedelta(offset)
return output.date()
if not year:
year = date.today().year
# NOTE: Includes New Year's day for next year.
@@ -886,7 +885,7 @@ def check_dictionary_inclusion_equality(listo: List[dict] | dict, dicto: dict) -
Returns:
bool: True if dicto is equal to any dictionary in the list.
"""
logger.debug(f"Comparing: {listo} and {dicto}")
# logger.debug(f"Comparing: {listo} and {dicto}")
if isinstance(dicto, list) and isinstance(listo, list):
return listo == dicto
elif isinstance(dicto, dict) and isinstance(listo, dict):
@@ -957,7 +956,6 @@ class Settings(BaseSettings, extra="allow"):
settings_path = None
if settings_path is None:
# NOTE: Check user .config/submissions directory
# if CONFIGDIR.joinpath("config.yml").exists():
if cls.configdir.joinpath("config.yml").exists():
settings_path = cls.configdir.joinpath("config.yml")
# NOTE: Check user .submissions directory
@@ -969,8 +967,6 @@ class Settings(BaseSettings, extra="allow"):
settings_path = Path(sys._MEIPASS).joinpath("files", "config.yml")
else:
settings_path = project_path.joinpath('src', 'config.yml')
# with open(settings_path, "r") as dset:
# default_settings = yaml.load(dset, Loader=yaml.Loader)
else:
# NOTE: check if user defined path is directory
if settings_path.is_dir():
@@ -1285,3 +1281,4 @@ class Settings(BaseSettings, extra="allow"):
ctx = Settings()