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

@@ -230,7 +230,7 @@ class App(QMainWindow):
self.table_widget.sub_wid.setData(page=self.table_widget.pager.page_anchor, page_size=page_size)
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():
new_org = dlg.parse_form()
new_org.save()
@@ -238,9 +238,11 @@ class App(QMainWindow):
@under_development
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():
print(dlg.parse_form())
output = dlg.parse_form()
assert isinstance(output, KitType)
output.save()
class AddSubForm(QWidget):

View File

@@ -1,27 +1,23 @@
"""
Provides a screen for managing all attributes of a database object.
"""
from copy import deepcopy
import json, logging
from pprint import pformat
from typing import Any, List
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
QDoubleSpinBox, QSpinBox, QCheckBox, QTextEdit, QVBoxLayout, QHBoxLayout
)
from sqlalchemy import String, TIMESTAMP, FLOAT, INTEGER, JSON, BLOB
from sqlalchemy.orm import InstrumentedAttribute
from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.orm.relationships import _RelationshipDeclared
from pandas import DataFrame
from backend import db
import logging
from tools import check_object_in_managers
from .omni_add_edit import AddEdit
from .omni_search import SearchBox
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.
"""
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)
self.object_type = self.original_type = object_type
self.add_edit = add_edit
self.instance = None
# self.managers = deepcopy(managers)
self.managers = managers
try:
self.managers.add(self.parent().instance)
@@ -63,9 +64,12 @@ class ManagerWindow(QDialog):
self.layout.addWidget(self.sub_class, 0, 0)
else:
self.sub_class = None
self.options = QComboBox(self)
self.options.setObjectName("options")
self.update_options()
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.object_type.__name__} - Managers: {self.managers}")
@@ -96,15 +100,15 @@ class ManagerWindow(QDialog):
self.options.setEditable(False)
self.options.setMinimumWidth(self.minimumWidth())
self.layout.addWidget(self.options, 1, 0, 1, 1)
if len(options) > 0:
self.add_button = QPushButton("Add New")
self.layout.addWidget(self.add_button, 1, 1, 1, 1)
self.add_button.clicked.connect(self.add_new)
# if len(options) > 0:
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) -> None:
def update_data(self, initial:bool=False) -> None:
"""
Performs updating of widgets on first run and after options change.
@@ -119,15 +123,19 @@ class ManagerWindow(QDialog):
item.setParent(None)
# logger.debug(f"Current options text lower: {self.options.currentText().lower()}")
# 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()}")
self.instance = self.object_type.query(name=self.options.currentText(), limit=1)
# logger.debug(f"Instance: {self.instance}")
if not self.instance:
self.instance = self.object_type()
# logger.debug(f"self.instance: {self.instance}")
fields = {k: v for k, v in self.instance.omnigui_instance_dict.items() if
isinstance(v['class_attr'], InstrumentedAttribute) and k != "id"}
fields = self.instance.omnigui_instance_dict
for key, field in fields.items():
try:
value = getattr(self.instance, key)
@@ -154,11 +162,6 @@ class ManagerWindow(QDialog):
# NOTE: Add OK|Cancel to bottom of dialog.
self.layout.addWidget(self.buttonBox, self.layout.rowCount(), 0, 1, 2)
def add_new_relation(self, field: str):
pass
def parse_form(self) -> Any:
"""
Returns the instance associated with this window.
@@ -182,18 +185,48 @@ class ManagerWindow(QDialog):
return self.instance
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()
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):
@@ -203,7 +236,7 @@ class EditProperty(QWidget):
self.layout = QGridLayout()
self.layout.addWidget(self.label, 0, 0, 1, 1)
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:
case String():
self.widget = QLineEdit(self)
@@ -214,12 +247,13 @@ class EditProperty(QWidget):
self.widget.setChecked(value)
else:
if value is None:
value = 1
value = 0
self.widget = QSpinBox()
self.widget.setMaximum(999)
self.widget.setValue(value)
case FLOAT():
if not value:
value = 1.0
value = 0.0
self.widget = QDoubleSpinBox()
self.widget.setMaximum(999.99)
self.widget.setValue(value)
@@ -227,7 +261,9 @@ class EditProperty(QWidget):
self.widget = QDateEdit(self)
self.widget.setDate(value)
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():
self.widget = QLabel("BLOB Under construction")
case _:
@@ -259,37 +295,54 @@ class EditRelationship(QWidget):
# logger.debug(f"Edit relationship entity: {self.entity}")
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)
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}")
self.data = value
# NOTE: value is a database object in this case.
# logger.debug(f"Data for edit relationship: {self.data}")
self.widget = QTableView()
self.set_data()
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.entity.level == 1)
if not self.relationship.property.uselist and len(self.data) >= 1:
self.add_button.setEnabled(False)
self.existing_button.setEnabled(False)
checked_manager = check_object_in_managers(self.parent().managers, self.objectName())
logger.debug(f"Checked manager for check: {checked_manager}")
if not isinstance(value, list):
if value is not None:
value = [value]
else:
value = []
self.data = value
# self.update_buttons()
checked_manager, is_primary = check_object_in_managers(self.parent().managers, self.objectName())
if checked_manager:
self.widget.setEnabled(False)
self.add_button.setEnabled(False)
self.existing_button.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.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()
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.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:
object = self.entity.query(**context)
except KeyError:
@@ -303,9 +356,10 @@ class EditRelationship(QWidget):
instance = self.entity()
managers = self.parent().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():
new_instance = dlg.parse_form()
self.parent().__setattr__(self.objectName(), new_instance)
# logger.debug(f"New instance before transformation attempt: {new_instance}")
# try:
# new_instance = new_instance.to_sql()
@@ -326,19 +380,9 @@ class EditRelationship(QWidget):
for row in rows:
# logger.debug(f"Querying with {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)
# self.parent().instance.save()
self.parent().update_data()
# yield instance
def set_choices(self) -> None:
pass
@@ -347,19 +391,7 @@ class EditRelationship(QWidget):
"""
sets data in model
"""
# logger.debug(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}")
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:
@@ -382,6 +414,7 @@ class EditRelationship(QWidget):
self.widget.resizeRowsToContents()
self.widget.setSortingEnabled(True)
self.widget.doubleClicked.connect(self.parse_row)
self.update_buttons()
def contextMenuEvent(self, event):
"""
@@ -390,7 +423,7 @@ class EditRelationship(QWidget):
Args:
event (_type_): the item of interest
"""
print(self.widget.isEnabled())
# print(self.widget.isEnabled())
if not self.widget.isEnabled():
logger.warning(f"{self.objectName()} is disabled.")
return
@@ -398,24 +431,184 @@ class EditRelationship(QWidget):
# 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())}
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):
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)
self.menu = QMenu(self)
try:
action = QAction(f"Remove {object.name}", self)
remove_action = QAction(f"Remove {object.name}", self)
except AttributeError:
action = QAction(f"Remove object", self)
action.triggered.connect(lambda: self.remove_item(object=object))
self.menu.addAction(action)
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())
editor.remove(object)
self.parent().instance.save()
self.parent().update_data()
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)
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.parent().update_data()
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.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])
with open(template_path.joinpath("css", "styles.css"), "r") as f:
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.webview.setHtml(self.html)