diff --git a/src/submissions/backend/db/models/__init__.py b/src/submissions/backend/db/models/__init__.py index 8d13460..d5e44d5 100644 --- a/src/submissions/backend/db/models/__init__.py +++ b/src/submissions/backend/db/models/__init__.py @@ -6,11 +6,14 @@ import sys, logging from pandas import DataFrame from pydantic import BaseModel 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.exc import ArgumentError, InvalidRequestError from typing import Any, List from pathlib import Path + +from sqlalchemy.orm.relationships import _RelationshipDeclared + from tools import report_result, list_sort_dict # NOTE: Load testing environment @@ -48,7 +51,7 @@ class BaseClass(Base): __table_args__ = {'extend_existing': True} #: Will only add new columns singles = ['id'] - omni_removes = ['submissions', "omnigui_class_dict", "omnigui_instance_dict"] + omni_removes = ["id", 'submissions', "omnigui_class_dict", "omnigui_instance_dict"] omni_sort = ["name"] @classproperty @@ -198,7 +201,7 @@ class BaseClass(Base): try: records = [obj.to_sub_dict(**kwargs) for obj in objects] except AttributeError: - records = [{k:v['instance_attr'] for k, v in obj.omnigui_instance_dict.items()} for obj in objects] + records = [{k: v['instance_attr'] for k, v in obj.omnigui_instance_dict.items()} for obj in objects] return DataFrame.from_records(records) @classmethod @@ -324,36 +327,76 @@ class BaseClass(Base): @classmethod def relevant_relationships(cls, relationship_instance): - query_kwargs = {relationship_instance.query_alias:relationship_instance} + query_kwargs = {relationship_instance.query_alias: relationship_instance} 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): + logger.debug(f"Attempting to set {key} to {pformat(value)}") try: field_type = getattr(self.__class__, key) except AttributeError: return super().__setattr__(key, value) - try: - check = field_type.property.uselist - except AttributeError: - check = False - if check: - logger.debug(f"Setting with uselist") - if self.__getattribute__(key) is not None: - if isinstance(value, list): - value = self.__getattribute__(key) + value - else: - value = self.__getattribute__(key) + [value] - else: - value = [value] + if isinstance(field_type, InstrumentedAttribute): + logger.debug(f"{key} is an InstrumentedAttribute.") + match field_type.property: + case ColumnProperty(): + 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") + if self.__getattribute__(key) is not None: + if isinstance(value, list): + value = self.__getattribute__(key) + value + else: + value = self.__getattribute__(key) + [value] + else: + value = [value] + return super().__setattr__(key, value) + else: + if isinstance(value, list): + if len(value) == 1: + value = value[0] + else: + raise ValueError("Object is too long to parse a single value.") + return super().__setattr__(key, value) else: - if isinstance(value, list): - if len(value) == 1: - value = value[0] - else: - raise ValueError("Object is too long to parse a single value.") - return super().__setattr__(key, value) - - + super().__setattr__(key, value) class ConfigItem(BaseClass): diff --git a/src/submissions/backend/db/models/kits.py b/src/submissions/backend/db/models/kits.py index cd074cc..ef705ca 100644 --- a/src/submissions/backend/db/models/kits.py +++ b/src/submissions/backend/db/models/kits.py @@ -10,7 +10,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.hybrid import hybrid_property from datetime import date, datetime, timedelta 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 pathlib import Path from . import Base, BaseClass, Organization, LogMixin @@ -861,7 +861,7 @@ class SubmissionType(BaseClass): @classproperty def omni_removes(cls): - return super().omni_removes + ["template_file", "defaults", "instances"] + return super().omni_removes + ["defaults", "instances"] @classproperty def basic_template(cls) -> bytes: @@ -1167,7 +1167,10 @@ class SubmissionTypeKitTypeAssociation(BaseClass): @property def name(self): - return f"{self.submission_type.name} -> {self.kit_type.name}" + try: + return f"{self.submission_type.name} -> {self.kit_type.name}" + except AttributeError: + return "Blank SubmissionTypeKitTypeAssociation" @classmethod @setup_lookup @@ -1378,6 +1381,16 @@ class KitTypeReagentRoleAssociation(BaseClass): dicto['required']['instance_attr'] = bool(dicto['required']['instance_attr']) 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): """ @@ -1932,7 +1945,6 @@ class Process(BaseClass): if value not in field: field.append(value) - @classmethod @setup_lookup def query(cls, diff --git a/src/submissions/frontend/widgets/app.py b/src/submissions/frontend/widgets/app.py index 9bd1913..3c3e9e5 100644 --- a/src/submissions/frontend/widgets/app.py +++ b/src/submissions/frontend/widgets/app.py @@ -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): diff --git a/src/submissions/frontend/widgets/omni_manager.py b/src/submissions/frontend/widgets/omni_manager.py index 8315402..f7eeac3 100644 --- a/src/submissions/frontend/widgets/omni_manager.py +++ b/src/submissions/frontend/widgets/omni_manager.py @@ -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) diff --git a/src/submissions/frontend/widgets/submission_details.py b/src/submissions/frontend/widgets/submission_details.py index d5291c3..3619e73 100644 --- a/src/submissions/frontend/widgets/submission_details.py +++ b/src/submissions/frontend/widgets/submission_details.py @@ -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) diff --git a/src/submissions/tools/__init__.py b/src/submissions/tools/__init__.py index ad89a7a..62d98ba 100644 --- a/src/submissions/tools/__init__.py +++ b/src/submissions/tools/__init__.py @@ -148,6 +148,7 @@ def check_key_or_attr(key: str, interest: dict | object, check_none: bool = Fals return False + def check_not_nan(cell_contents) -> bool: """ Check to ensure excel sheet cell contents are not blank. @@ -816,22 +817,22 @@ def setup_lookup(func): 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: - 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: - return manager + return manager, True relationships = [getattr(manager.__class__, item) for item in dir(manager.__class__) if isinstance(getattr(manager.__class__, item), InstrumentedAttribute)] relationships = [item for item in relationships if isinstance(item.property, _RelationshipDeclared)] 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}") try: rel_obj = getattr(manager, relationship.key) if rel_obj is not None: logger.debug(f"Returning {rel_obj}") - return rel_obj + return rel_obj, False except AttributeError: pass 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 rel_obj is not None: logger.debug(f"Returning {rel_obj}") - return rel_obj + return rel_obj, False except AttributeError: pass - return None + return None, None def get_application_from_parent(widget): @@ -1163,6 +1164,7 @@ def is_list_etc(object): check = False return check + 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): if not occurence: