Switching to save instance

This commit is contained in:
lwark
2025-02-04 09:17:01 -06:00
parent f19706649c
commit 05421cc1d4
6 changed files with 376 additions and 123 deletions

View File

@@ -6,11 +6,14 @@ import sys, logging
from pandas import DataFrame from pandas import DataFrame
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import Column, INTEGER, String, JSON from sqlalchemy import Column, INTEGER, String, JSON
from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute from sqlalchemy.orm import DeclarativeMeta, declarative_base, Query, Session, InstrumentedAttribute, ColumnProperty
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.exc import ArgumentError, InvalidRequestError from sqlalchemy.exc import ArgumentError, InvalidRequestError
from typing import Any, List from typing import Any, List
from pathlib import Path from pathlib import Path
from sqlalchemy.orm.relationships import _RelationshipDeclared
from tools import report_result, list_sort_dict from tools import report_result, list_sort_dict
# NOTE: Load testing environment # NOTE: Load testing environment
@@ -48,7 +51,7 @@ class BaseClass(Base):
__table_args__ = {'extend_existing': True} #: Will only add new columns __table_args__ = {'extend_existing': True} #: Will only add new columns
singles = ['id'] singles = ['id']
omni_removes = ['submissions', "omnigui_class_dict", "omnigui_instance_dict"] omni_removes = ["id", 'submissions', "omnigui_class_dict", "omnigui_instance_dict"]
omni_sort = ["name"] omni_sort = ["name"]
@classproperty @classproperty
@@ -327,16 +330,55 @@ class BaseClass(Base):
query_kwargs = {relationship_instance.query_alias: relationship_instance} query_kwargs = {relationship_instance.query_alias: relationship_instance}
return cls.query(**query_kwargs) return cls.query(**query_kwargs)
def check_all_attributes(self, attributes: dict):
logger.debug(f"Incoming attributes: {attributes}")
for key, value in attributes.items():
# print(getattr(self.__class__, key).property)
if value.lower() == "none":
value = None
self_value = getattr(self, key)
class_attr = getattr(self.__class__, key)
match class_attr.property:
case ColumnProperty():
match class_attr.type:
case INTEGER():
if value.lower() == "true":
value = 1
elif value.lower() == "false":
value = 0
else:
value = int(value)
case FLOAT():
value = float(value)
case _RelationshipDeclared():
try:
self_value = self_value.name
except AttributeError:
pass
if class_attr.property.uselist:
self_value = self_value.__str__()
logger.debug(f"Checking self_value {self_value} of type {type(self_value)} against attribute {value} of type {type(value)}")
if self_value != value:
output = False
logger.debug(f"Value {key} is False, returning.")
return output
return True
def __setattr__(self, key, value): def __setattr__(self, key, value):
logger.debug(f"Attempting to set {key} to {pformat(value)}")
try: try:
field_type = getattr(self.__class__, key) field_type = getattr(self.__class__, key)
except AttributeError: except AttributeError:
return super().__setattr__(key, value) return super().__setattr__(key, value)
try: if isinstance(field_type, InstrumentedAttribute):
check = field_type.property.uselist logger.debug(f"{key} is an InstrumentedAttribute.")
except AttributeError: match field_type.property:
check = False case ColumnProperty():
if check: logger.debug(f"Setting ColumnProperty to {value}")
return super().__setattr__(key, value)
case _RelationshipDeclared():
logger.debug(f"Setting _RelationshipDeclared to {value}")
if field_type.property.uselist:
logger.debug(f"Setting with uselist") logger.debug(f"Setting with uselist")
if self.__getattribute__(key) is not None: if self.__getattribute__(key) is not None:
if isinstance(value, list): if isinstance(value, list):
@@ -345,6 +387,7 @@ class BaseClass(Base):
value = self.__getattribute__(key) + [value] value = self.__getattribute__(key) + [value]
else: else:
value = [value] value = [value]
return super().__setattr__(key, value)
else: else:
if isinstance(value, list): if isinstance(value, list):
if len(value) == 1: if len(value) == 1:
@@ -352,8 +395,8 @@ class BaseClass(Base):
else: else:
raise ValueError("Object is too long to parse a single value.") raise ValueError("Object is too long to parse a single value.")
return super().__setattr__(key, value) return super().__setattr__(key, value)
else:
super().__setattr__(key, value)
class ConfigItem(BaseClass): class ConfigItem(BaseClass):

View File

@@ -10,7 +10,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator, timezone from tools import check_authorization, setup_lookup, Report, Result, check_regex_match, yaml_regex_creator, timezone
from typing import List, Literal, Generator, Any, Tuple from typing import List, Literal, Generator, Any, Tuple, Dict, AnyStr
from pandas import ExcelFile from pandas import ExcelFile
from pathlib import Path from pathlib import Path
from . import Base, BaseClass, Organization, LogMixin from . import Base, BaseClass, Organization, LogMixin
@@ -861,7 +861,7 @@ class SubmissionType(BaseClass):
@classproperty @classproperty
def omni_removes(cls): def omni_removes(cls):
return super().omni_removes + ["template_file", "defaults", "instances"] return super().omni_removes + ["defaults", "instances"]
@classproperty @classproperty
def basic_template(cls) -> bytes: def basic_template(cls) -> bytes:
@@ -1167,7 +1167,10 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
@property @property
def name(self): def name(self):
try:
return f"{self.submission_type.name} -> {self.kit_type.name}" return f"{self.submission_type.name} -> {self.kit_type.name}"
except AttributeError:
return "Blank SubmissionTypeKitTypeAssociation"
@classmethod @classmethod
@setup_lookup @setup_lookup
@@ -1378,6 +1381,16 @@ class KitTypeReagentRoleAssociation(BaseClass):
dicto['required']['instance_attr'] = bool(dicto['required']['instance_attr']) dicto['required']['instance_attr'] = bool(dicto['required']['instance_attr'])
return dicto return dicto
@classproperty
def json_edit_fields(cls) -> dict:
dicto = dict(
sheet="str",
expiry=dict(column="int", row="int"),
lot=dict(column="int", row="int"),
name=dict(column="int", row="int")
)
return dicto
class SubmissionReagentAssociation(BaseClass): class SubmissionReagentAssociation(BaseClass):
""" """
@@ -1932,7 +1945,6 @@ class Process(BaseClass):
if value not in field: if value not in field:
field.append(value) field.append(value)
@classmethod @classmethod
@setup_lookup @setup_lookup
def query(cls, def query(cls,

View File

@@ -230,7 +230,7 @@ class App(QMainWindow):
self.table_widget.sub_wid.setData(page=self.table_widget.pager.page_anchor, page_size=page_size) self.table_widget.sub_wid.setData(page=self.table_widget.pager.page_anchor, page_size=page_size)
def manage_orgs(self): def manage_orgs(self):
dlg = ManagerWindow(parent=self, object_type=Organization, extras=[]) dlg = ManagerWindow(parent=self, object_type=Organization, extras=[], add_edit='edit', managers=set())
if dlg.exec(): if dlg.exec():
new_org = dlg.parse_form() new_org = dlg.parse_form()
new_org.save() new_org.save()
@@ -238,9 +238,11 @@ class App(QMainWindow):
@under_development @under_development
def manage_kits(self, *args, **kwargs): def manage_kits(self, *args, **kwargs):
dlg = ManagerWindow(parent=self, object_type=KitType, extras=[]) dlg = ManagerWindow(parent=self, object_type=KitType, extras=[], add_edit='edit', managers=set())
if dlg.exec(): if dlg.exec():
print(dlg.parse_form()) output = dlg.parse_form()
assert isinstance(output, KitType)
output.save()
class AddSubForm(QWidget): class AddSubForm(QWidget):

View File

@@ -1,27 +1,23 @@
""" """
Provides a screen for managing all attributes of a database object. Provides a screen for managing all attributes of a database object.
""" """
from copy import deepcopy import json, logging
from pprint import pformat from pprint import pformat
from typing import Any, List from typing import Any, List, Literal
from PyQt6.QtCore import QSortFilterProxyModel, Qt from PyQt6.QtCore import QSortFilterProxyModel, Qt
from PyQt6.QtGui import QAction, QCursor from PyQt6.QtGui import QAction, QCursor
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QLabel, QDialog, QLabel, QDialog,
QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit, QMenu, QTableView, QWidget, QLineEdit, QGridLayout, QComboBox, QPushButton, QDialogButtonBox, QDateEdit, QMenu,
QDoubleSpinBox, QSpinBox, QCheckBox QDoubleSpinBox, QSpinBox, QCheckBox, QTextEdit, QVBoxLayout, QHBoxLayout
) )
from sqlalchemy import String, TIMESTAMP, FLOAT, INTEGER, JSON, BLOB from sqlalchemy import String, TIMESTAMP, FLOAT, INTEGER, JSON, BLOB
from sqlalchemy.orm import InstrumentedAttribute from sqlalchemy.orm import InstrumentedAttribute
from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.orm.properties import ColumnProperty from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.orm.relationships import _RelationshipDeclared from sqlalchemy.orm.relationships import _RelationshipDeclared
from pandas import DataFrame from pandas import DataFrame
from backend import db from backend import db
import logging
from tools import check_object_in_managers from tools import check_object_in_managers
from .omni_add_edit import AddEdit
from .omni_search import SearchBox from .omni_search import SearchBox
from frontend.widgets.submission_table import pandasModel from frontend.widgets.submission_table import pandasModel
@@ -33,11 +29,16 @@ class ManagerWindow(QDialog):
Initially this is a window to manage Organization Contacts, but hope to abstract it more later. 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], managers: set = set(), **kwargs): def __init__(self, parent,
object_type: Any,
extras: List[str],
managers: set = set(),
add_edit: Literal['add', 'edit'] = 'edit',
**kwargs):
super().__init__(parent) super().__init__(parent)
self.object_type = self.original_type = object_type self.object_type = self.original_type = object_type
self.add_edit = add_edit
self.instance = None self.instance = None
# self.managers = deepcopy(managers)
self.managers = managers self.managers = managers
try: try:
self.managers.add(self.parent().instance) self.managers.add(self.parent().instance)
@@ -63,9 +64,12 @@ class ManagerWindow(QDialog):
self.layout.addWidget(self.sub_class, 0, 0) self.layout.addWidget(self.sub_class, 0, 0)
else: else:
self.sub_class = None self.sub_class = None
if self.add_edit == "edit":
self.options = QComboBox(self) self.options = QComboBox(self)
self.options.setObjectName("options") self.options.setObjectName("options")
self.update_options() self.update_options()
else:
self.update_data(initial=True)
self.setLayout(self.layout) self.setLayout(self.layout)
self.setWindowTitle(f"Manage {self.object_type.__name__} - Managers: {self.managers}") self.setWindowTitle(f"Manage {self.object_type.__name__} - Managers: {self.managers}")
@@ -96,7 +100,7 @@ class ManagerWindow(QDialog):
self.options.setEditable(False) self.options.setEditable(False)
self.options.setMinimumWidth(self.minimumWidth()) self.options.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.options, 1, 0, 1, 1) self.layout.addWidget(self.options, 1, 0, 1, 1)
if len(options) > 0: # if len(options) > 0:
self.add_button = QPushButton("Add New") self.add_button = QPushButton("Add New")
self.layout.addWidget(self.add_button, 1, 1, 1, 1) self.layout.addWidget(self.add_button, 1, 1, 1, 1)
self.add_button.clicked.connect(self.add_new) self.add_button.clicked.connect(self.add_new)
@@ -104,7 +108,7 @@ class ManagerWindow(QDialog):
# logger.debug(f"Instance: {self.instance}") # logger.debug(f"Instance: {self.instance}")
self.update_data() self.update_data()
def update_data(self) -> None: def update_data(self, initial:bool=False) -> None:
""" """
Performs updating of widgets on first run and after options change. Performs updating of widgets on first run and after options change.
@@ -119,15 +123,19 @@ class ManagerWindow(QDialog):
item.setParent(None) item.setParent(None)
# logger.debug(f"Current options text lower: {self.options.currentText().lower()}") # logger.debug(f"Current options text lower: {self.options.currentText().lower()}")
# NOTE: Find the instance this manager will update # NOTE: Find the instance this manager will update
if "blank" not in self.options.currentText().lower() and self.options.currentText() != "": # try:
# check = "blank" not in self.options.currentText().lower() and self.options.currentText() != ""
# except AttributeError:
# check = False
# if check:
if self.add_edit == "edit" and initial:
# logger.debug(f"Querying with {self.options.currentText()}") # logger.debug(f"Querying with {self.options.currentText()}")
self.instance = self.object_type.query(name=self.options.currentText(), limit=1) self.instance = self.object_type.query(name=self.options.currentText(), limit=1)
# logger.debug(f"Instance: {self.instance}") # logger.debug(f"Instance: {self.instance}")
if not self.instance: if not self.instance:
self.instance = self.object_type() self.instance = self.object_type()
# logger.debug(f"self.instance: {self.instance}") # logger.debug(f"self.instance: {self.instance}")
fields = {k: v for k, v in self.instance.omnigui_instance_dict.items() if fields = self.instance.omnigui_instance_dict
isinstance(v['class_attr'], InstrumentedAttribute) and k != "id"}
for key, field in fields.items(): for key, field in fields.items():
try: try:
value = getattr(self.instance, key) value = getattr(self.instance, key)
@@ -154,11 +162,6 @@ class ManagerWindow(QDialog):
# NOTE: Add OK|Cancel to bottom of dialog. # NOTE: Add OK|Cancel to bottom of dialog.
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2) self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
def add_new_relation(self, field: str):
pass
def parse_form(self) -> Any: def parse_form(self) -> Any:
""" """
Returns the instance associated with this window. Returns the instance associated with this window.
@@ -182,18 +185,48 @@ class ManagerWindow(QDialog):
return self.instance return self.instance
def add_new(self): def add_new(self):
# dlg = AddEdit(parent=self, instance=self.object_type(), managers=self.managers)
# if dlg.exec():
# new_pyd = dlg.parse_form()
# new_instance = new_pyd.to_sql()
# # new_instance.save()
# logger.debug(f"New instance: {new_instance}")
# self.instance = new_instance
# self.update_options()
new_instance = self.object_type() new_instance = self.object_type()
self.instance = new_instance self.instance = new_instance
self.update_options() 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): class EditProperty(QWidget):
@@ -203,7 +236,7 @@ class EditProperty(QWidget):
self.layout = QGridLayout() self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 1) self.layout.addWidget(self.label, 0, 0, 1, 1)
self.setObjectName(key) self.setObjectName(key)
# logger.debug(f"Column type: {column_type}") logger.debug(f"Column type for {key}: {column_type['class_attr'].property.expression.type}")
match column_type['class_attr'].property.expression.type: match column_type['class_attr'].property.expression.type:
case String(): case String():
self.widget = QLineEdit(self) self.widget = QLineEdit(self)
@@ -214,12 +247,13 @@ class EditProperty(QWidget):
self.widget.setChecked(value) self.widget.setChecked(value)
else: else:
if value is None: if value is None:
value = 1 value = 0
self.widget = QSpinBox() self.widget = QSpinBox()
self.widget.setMaximum(999)
self.widget.setValue(value) self.widget.setValue(value)
case FLOAT(): case FLOAT():
if not value: if not value:
value = 1.0 value = 0.0
self.widget = QDoubleSpinBox() self.widget = QDoubleSpinBox()
self.widget.setMaximum(999.99) self.widget.setMaximum(999.99)
self.widget.setValue(value) self.widget.setValue(value)
@@ -227,7 +261,9 @@ class EditProperty(QWidget):
self.widget = QDateEdit(self) self.widget = QDateEdit(self)
self.widget.setDate(value) self.widget.setDate(value)
case JSON(): case JSON():
self.widget = QLabel("JSON Under construction") 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(): case BLOB():
self.widget = QLabel("BLOB Under construction") self.widget = QLabel("BLOB Under construction")
case _: case _:
@@ -259,37 +295,54 @@ class EditRelationship(QWidget):
# logger.debug(f"Edit relationship entity: {self.entity}") # logger.debug(f"Edit relationship entity: {self.entity}")
self.label = QLabel(key.title().replace("_", " ")) self.label = QLabel(key.title().replace("_", " "))
self.setObjectName(key) #: key is the name of the relationship this represents self.setObjectName(key) #: key is the name of the relationship this represents
self.relationship = getattr(self.parent().instance.__class__, key) self.relationship = getattr(self.parent().instance.__class__, key) #: relationship object for type differentiation
# logger.debug(f"self.relationship: {self.relationship}") # logger.debug(f"self.relationship: {self.relationship}")
# logger.debug(f"Relationship uses list: {self.relationship.property.uselist}") # logger.debug(f"Relationship uses list: {self.relationship.property.uselist}")
self.data = value # NOTE: value is a database object in this case.
# logger.debug(f"Data for edit relationship: {self.data}") # logger.debug(f"Data for edit relationship: {self.data}")
self.widget = QTableView() self.widget = QTableView()
self.set_data()
self.add_button = QPushButton("Add New") self.add_button = QPushButton("Add New")
self.add_button.clicked.connect(self.add_new) self.add_button.clicked.connect(self.add_new)
self.existing_button = QPushButton("Add Existing") self.existing_button = QPushButton("Add Existing")
self.existing_button.clicked.connect(self.add_existing) self.existing_button.clicked.connect(self.add_existing)
# self.existing_button.setEnabled(self.entity.level == 1) # self.existing_button.setEnabled(self.entity.level == 1)
if not self.relationship.property.uselist and len(self.data) >= 1: if not isinstance(value, list):
self.add_button.setEnabled(False) if value is not None:
self.existing_button.setEnabled(False) value = [value]
checked_manager = check_object_in_managers(self.parent().managers, self.objectName()) else:
logger.debug(f"Checked manager for check: {checked_manager}") value = []
self.data = value
# self.update_buttons()
checked_manager, is_primary = check_object_in_managers(self.parent().managers, self.objectName())
if checked_manager: if checked_manager:
self.widget.setEnabled(False) logger.debug(f"Checked manager for {self.objectName()}: {checked_manager}")
if checked_manager is not None and not self.data:
self.data = [checked_manager]
if not self.relationship.property.uselist:
self.add_button.setEnabled(False) self.add_button.setEnabled(False)
self.existing_button.setEnabled(False) self.existing_button.setEnabled(False)
if is_primary:
self.widget.setEnabled(False)
self.layout = QGridLayout() self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 5) self.layout.addWidget(self.label, 0, 0, 1, 5)
self.layout.addWidget(self.widget, 1, 0, 1, 8) 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.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.layout.addWidget(self.existing_button, 0, 7, 1, 1, alignment=Qt.AlignmentFlag.AlignRight)
self.setLayout(self.layout) self.setLayout(self.layout)
# self.set_data() 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): def parse_row(self, x):
context = {item: x.sibling(x.row(), self.data.columns.get_loc(item)).data() for item in self.data.columns} context = {item: x.sibling(x.row(), self.df.columns.get_loc(item)).data() for item in self.df.columns}
try: try:
object = self.entity.query(**context) object = self.entity.query(**context)
except KeyError: except KeyError:
@@ -303,9 +356,10 @@ class EditRelationship(QWidget):
instance = self.entity() instance = self.entity()
managers = self.parent().managers managers = self.parent().managers
# logger.debug(f"Managers going into add new: {managers}") # logger.debug(f"Managers going into add new: {managers}")
dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], managers=managers) dlg = ManagerWindow(self.parent(), object_type=instance.__class__, extras=[], managers=managers, add_edit="add")
if dlg.exec(): if dlg.exec():
new_instance = dlg.parse_form() new_instance = dlg.parse_form()
self.parent().__setattr__(self.objectName(), new_instance)
# logger.debug(f"New instance before transformation attempt: {new_instance}") # logger.debug(f"New instance before transformation attempt: {new_instance}")
# try: # try:
# new_instance = new_instance.to_sql() # new_instance = new_instance.to_sql()
@@ -326,19 +380,9 @@ class EditRelationship(QWidget):
for row in rows: for row in rows:
# logger.debug(f"Querying with {row}") # logger.debug(f"Querying with {row}")
instance = self.entity.query(**row) instance = self.entity.query(**row)
# logger.debug(f"Queried instance: {instance}")
# logger.debug(f"Checking field type: {self.objectName()}")
# addition = getattr(self.parent().instance, self.objectName())
# logger.debug(f"Instance object: {addition}")
# NOTE: Saving currently disabled
# if self.relationship.property.uselist:
# addition.append(instance)
# else:
# addition = instance
setattr(self.parent().instance, self.objectName(), instance) setattr(self.parent().instance, self.objectName(), instance)
# self.parent().instance.save() # self.parent().instance.save()
self.parent().update_data() self.parent().update_data()
# yield instance
def set_choices(self) -> None: def set_choices(self) -> None:
pass pass
@@ -347,19 +391,7 @@ class EditRelationship(QWidget):
""" """
sets data in model sets data in model
""" """
# logger.debug(self.data) logger.debug(f"Self.data: {self.data}")
if not isinstance(self.data, list):
if self.data is not None:
self.data = [self.data]
else:
self.data = []
checked_manager = check_object_in_managers(self.parent().managers, self.objectName())
# logger.debug(f"Returned checked_manager: {checked_manager}")
if checked_manager is not None:
if not self.data:
self.data = [checked_manager]
# setattr(self.parent().instance, self.objectName(), checked_manager)
# logger.debug(f"Data: {self.data}")
try: try:
records = [{k: v['instance_attr'] for k, v in item.omnigui_instance_dict.items()} for item in self.data] records = [{k: v['instance_attr'] for k, v in item.omnigui_instance_dict.items()} for item in self.data]
except AttributeError: except AttributeError:
@@ -382,6 +414,7 @@ class EditRelationship(QWidget):
self.widget.resizeRowsToContents() self.widget.resizeRowsToContents()
self.widget.setSortingEnabled(True) self.widget.setSortingEnabled(True)
self.widget.doubleClicked.connect(self.parse_row) self.widget.doubleClicked.connect(self.parse_row)
self.update_buttons()
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
""" """
@@ -390,7 +423,7 @@ class EditRelationship(QWidget):
Args: Args:
event (_type_): the item of interest event (_type_): the item of interest
""" """
print(self.widget.isEnabled()) # print(self.widget.isEnabled())
if not self.widget.isEnabled(): if not self.widget.isEnabled():
logger.warning(f"{self.objectName()} is disabled.") logger.warning(f"{self.objectName()} is disabled.")
return return
@@ -398,24 +431,184 @@ class EditRelationship(QWidget):
# NOTE: the overly complicated {column_name: row_value} dictionary construction # 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 row_data = {self.df.columns[column]: self.widget.model().index(id.row(), column).data() for column in
range(self.widget.model().columnCount())} range(self.widget.model().columnCount())}
object = self.entity.query(**row_data) 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): if isinstance(object, list):
object = object[0] 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) # logger.debug(object)
self.menu = QMenu(self) self.menu = QMenu(self)
try: try:
action = QAction(f"Remove {object.name}", self) remove_action = QAction(f"Remove {object.name}", self)
except AttributeError: except AttributeError:
action = QAction(f"Remove object", self) remove_action = QAction(f"Remove object", self)
action.triggered.connect(lambda: self.remove_item(object=object)) remove_action.triggered.connect(lambda: self.remove_item(object=object))
self.menu.addAction(action) 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()) self.menu.popup(QCursor.pos())
def remove_item(self, object): 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()) editor = getattr(self.parent().instance, self.objectName().lower())
logger.debug(f"Editor: {editor}")
if object in self.parent().managers:
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) editor.remove(object)
self.parent().instance.save() except AttributeError as e:
self.parent().update_data() 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.parent().update_data()
self.set_data()
def parse_form(self): def parse_form(self):
return dict(field=self.objectName(), value=self.data) 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.object_type
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)}")
# output[key] = value
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

@@ -175,6 +175,7 @@ class SubmissionDetails(QDialog):
template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0]) template_path = Path(self.template.environment.loader.__getattribute__("searchpath")[0])
with open(template_path.joinpath("css", "styles.css"), "r") as f: with open(template_path.joinpath("css", "styles.css"), "r") as f:
css = f.read() css = f.read()
# logger.debug(f"Base dictionary of submission {self.rsl_plate_num}: {pformat(self.base_dict)}")
self.html = self.template.render(sub=self.base_dict, 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) self.webview.setHtml(self.html)

View File

@@ -148,6 +148,7 @@ def check_key_or_attr(key: str, interest: dict | object, check_none: bool = Fals
return False return False
def check_not_nan(cell_contents) -> bool: def check_not_nan(cell_contents) -> bool:
""" """
Check to ensure excel sheet cell contents are not blank. Check to ensure excel sheet cell contents are not blank.
@@ -816,22 +817,22 @@ def setup_lookup(func):
return wrapper return wrapper
def check_object_in_managers(managers: list, object_name: object): def check_object_in_managers(managers: list, object_name: object) -> Tuple[Any, bool]:
for manager in managers: for manager in managers:
logger.debug(f"Manager: {manager}, Key: {object_name}") logger.debug(f"Manager: {manager}, aliases: {manager.aliases}, Key: {object_name}")
if object_name in manager.aliases: if object_name in manager.aliases:
return manager return manager, True
relationships = [getattr(manager.__class__, item) for item in dir(manager.__class__) relationships = [getattr(manager.__class__, item) for item in dir(manager.__class__)
if isinstance(getattr(manager.__class__, item), InstrumentedAttribute)] if isinstance(getattr(manager.__class__, item), InstrumentedAttribute)]
relationships = [item for item in relationships if isinstance(item.property, _RelationshipDeclared)] relationships = [item for item in relationships if isinstance(item.property, _RelationshipDeclared)]
for relationship in relationships: for relationship in relationships:
if relationship.key == object_name: if relationship.key == object_name and "association" not in relationship.key:
logger.debug(f"Checking {relationship.key}") logger.debug(f"Checking {relationship.key}")
try: try:
rel_obj = getattr(manager, relationship.key) rel_obj = getattr(manager, relationship.key)
if rel_obj is not None: if rel_obj is not None:
logger.debug(f"Returning {rel_obj}") logger.debug(f"Returning {rel_obj}")
return rel_obj return rel_obj, False
except AttributeError: except AttributeError:
pass pass
if "association" in relationship.key: if "association" in relationship.key:
@@ -841,10 +842,10 @@ def check_object_in_managers(managers: list, object_name: object):
if getattr(item, object_name) is not None), None) if getattr(item, object_name) is not None), None)
if rel_obj is not None: if rel_obj is not None:
logger.debug(f"Returning {rel_obj}") logger.debug(f"Returning {rel_obj}")
return rel_obj return rel_obj, False
except AttributeError: except AttributeError:
pass pass
return None return None, None
def get_application_from_parent(widget): def get_application_from_parent(widget):
@@ -1163,6 +1164,7 @@ def is_list_etc(object):
check = False check = False
return check return check
def create_holidays_for_year(year: int | None = None) -> List[date]: def create_holidays_for_year(year: int | None = None) -> List[date]:
def find_nth_monday(year, month, occurence: int | None = None, day: int | None = None): def find_nth_monday(year, month, occurence: int | None = None, day: int | None = None):
if not occurence: if not occurence: