From 9cfe53509fd4e5c46f6464dd98fd2491260f22d8 Mon Sep 17 00:00:00 2001 From: foefl Date: Thu, 9 Jul 2026 13:27:52 +0200 Subject: [PATCH] refactoring --- src/wce_crm/_remove.py | 26 +++ src/wce_crm/backend/backend.py | 6 +- src/wce_crm/data_models.py | 37 ++++ src/wce_crm/form_defs.py | 2 + src/wce_crm/gui.py | 378 +++++++++++++++++++-------------- 5 files changed, 292 insertions(+), 157 deletions(-) create mode 100644 src/wce_crm/_remove.py diff --git a/src/wce_crm/_remove.py b/src/wce_crm/_remove.py new file mode 100644 index 0000000..523c3e1 --- /dev/null +++ b/src/wce_crm/_remove.py @@ -0,0 +1,26 @@ +def _get_leafs(data): + if isinstance(data, dict): + for value in data.values(): + yield from _get_leafs(value) + elif isinstance(data, (list, tuple, set)): + for item in data: + yield from _get_leafs(item) + else: + yield data + + +def _get_leaf_dicts(data): + if isinstance(data, dict): + has_inner_dicts = False + + for value in data.values(): + for inner_dict in _get_leaf_dicts(value): + has_inner_dicts = True + yield inner_dict + + if not has_inner_dicts: + yield data + + elif isinstance(data, (list, tuple, set)): + for item in data: + yield from _get_leaf_dicts(item) diff --git a/src/wce_crm/backend/backend.py b/src/wce_crm/backend/backend.py index d382a6b..6c5dead 100644 --- a/src/wce_crm/backend/backend.py +++ b/src/wce_crm/backend/backend.py @@ -147,7 +147,7 @@ def initrec_company_get_initial_recording( row = ret.fetchone() assert row, "row was not obtained" - return row._asdict() + return row._asdict() # type: ignore def initrec_company_delete_initial_recording( @@ -196,7 +196,7 @@ def initrec_person_update_initial_recording( def initrec_person_get_initial_recording( id_: RecId, ) -> dict[str, Any]: - logger.debug("[Call backend] get_initial_recording") + logger.debug("[Call backend] get_initial_recording person") stmt = db.grunderfassung_personen.select().where( db.grunderfassung_personen.c.pers_id == id_ ) @@ -209,7 +209,7 @@ def initrec_person_get_initial_recording( row = ret.fetchone() assert row, "row was not obtained" - return row._asdict() + return row._asdict() # type: ignore def initrec_person_delete_initial_recording( diff --git a/src/wce_crm/data_models.py b/src/wce_crm/data_models.py index 88efdbd..e5ab2b9 100644 --- a/src/wce_crm/data_models.py +++ b/src/wce_crm/data_models.py @@ -13,10 +13,47 @@ from pydantic import ( field_validator, model_validator, ) +from pydantic_core import ErrorDetails ValidAge = Annotated[int, Field(ge=0, le=99)] +# // validation translation +GERMAN_ERROR_MESSAGES: Final[dict[str, str]] = { + "missing": "Dieses Feld ist ein Pflichtfeld.", + "int_parsing": "Bitte eine gültige Ganzzahl eingeben.", + "float_parsing": "Bitte eine gültige Dezimalzahl eingeben.", + "string_too_short": "Der Text ist zu kurz. Er muss mindestens {min_length} Zeichen lang sein.", + "string_too_long": "Der Text ist zu lang. Er darf maximal {max_length} Zeichen lang sein.", + "greater_than": "Der Wert muss größer als {gt} sein.", +} + + +def translate_pydantic_errors( + errors: list[ErrorDetails], +) -> list[ErrorDetails]: + translated_errors: list[ErrorDetails] = [] + + for error in errors: + err_type = error.get("type", None) + + if err_type is None or err_type not in GERMAN_ERROR_MESSAGES: + error["msg"] = "[fehleden Übersetzung, nur Englisch]: " + error["msg"] + translated_errors.append(error) + continue + + msg_template = GERMAN_ERROR_MESSAGES[err_type] + ctx = error.get("ctx", {}) + try: + error["msg"] = msg_template.format(**ctx) + except KeyError: + error["msg"] = msg_template + + translated_errors.append(error) + + return translated_errors + + def _parse_json(value: Any) -> str: if isinstance(value, datetime.date): return value.isoformat() diff --git a/src/wce_crm/form_defs.py b/src/wce_crm/form_defs.py index c50b8d8..765f6e4 100644 --- a/src/wce_crm/form_defs.py +++ b/src/wce_crm/form_defs.py @@ -911,6 +911,7 @@ INITREC_COMP = [ required=False, key="Metadaten_nutzer", readonly=True, + ignore_get_data=True, ), FormField( "Fallnummer", @@ -956,6 +957,7 @@ INITREC_PERSON = [ required=False, key="Metadaten_nutzer", readonly=True, + ignore_get_data=True, ), FormField( "Wiedereintrittsdatum", diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 66475df..0316cef 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -4,13 +4,11 @@ import copy import dataclasses as dc import datetime import enum -import pickle import re import sys import traceback from collections import defaultdict from collections.abc import Container, Iterable, Sequence -from pathlib import Path from pprint import pformat from typing import ( TYPE_CHECKING, @@ -26,7 +24,6 @@ from typing import ( ) from typing_extensions import override -from dopt_basics import datetime as dopt_dt from pydantic import ( ValidationError, ) @@ -36,7 +33,6 @@ from PySide6.QtCore import ( QEvent, QObject, Qt, - QTime, QTimer, QtMsgType, Signal, @@ -84,6 +80,7 @@ from wce_crm.data_models import ( FlatBaseModel, Grunderfassung_Personen, Grunderfassung_Unternehmen, + translate_pydantic_errors, ) from wce_crm.form_defs import ( INITREC_COMP, @@ -166,36 +163,6 @@ def unmerge_dict_to_list( return [dict(zip(keys, row)) for row in zip(*value_lists)] -# TODO check removal or move to basics package -def _get_leafs(data): - if isinstance(data, dict): - for value in data.values(): - yield from _get_leafs(value) - elif isinstance(data, (list, tuple, set)): - for item in data: - yield from _get_leafs(item) - else: - yield data - - -# TODO check removal or move to basics package -def _get_leaf_dicts(data): - if isinstance(data, dict): - has_inner_dicts = False - - for value in data.values(): - for inner_dict in _get_leaf_dicts(value): - has_inner_dicts = True - yield inner_dict - - if not has_inner_dicts: - yield data - - elif isinstance(data, (list, tuple, set)): - for item in data: - yield from _get_leaf_dicts(item) - - def pprint_registry(widget_registry: WidgetRegistry) -> None: print("\n\n>>> Widget registry:") for key, entry in widget_registry.items(): @@ -665,8 +632,6 @@ def _build_ui_recursively( else: full_key = field.key - widget: QWidget - match field.type: case FormFieldType.GROUP: group_box = QGroupBox(field.label) @@ -686,11 +651,6 @@ def _build_ui_recursively( widget_registry=widget_registry, prefix=prefix, ) - # TODO remove - # widget_registry[full_key] = { - # "widget": widget, - # "form_field": field, - # } keys.append(full_key) return keys @@ -722,7 +682,7 @@ def reset_form( form_field = registry_entry["form_field"] # TODO check if this behaviour is correct in other contexts, deactivated because of - # TODO company search widget + # company search widget # if form_field.readonly: # continue @@ -1410,6 +1370,7 @@ class AutoForm(QWidget): ) -> None: super().__init__() self.STATE = state + self.SESSION_USER_KEY: Final[str] = "Metadaten_nutzer" self.cfg = state.cfg self.setStyleSheet(""" QGroupBox { @@ -1515,40 +1476,21 @@ class AutoForm(QWidget): self.debug_form_data: dict[str, Any] = {} if self.add_buttons: - self.layout_btn = QHBoxLayout() - self.main_layout.addLayout(self.layout_btn) - # save - self.save_btn_txt_enabled = "Speichern (Strg + S)" - self.save_btn_txt_disabled = "Wird gespeichert..." - self.save_btn = QPushButton(self.save_btn_txt_enabled) - self.save_btn.setShortcut("Ctrl+S") - self.save_btn.setFixedHeight(50) - self.save_btn.setSizePolicy( - QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed - ) + self.edit_buttons = EditButtons(add_reset_btn=True) + reset_btn = self.edit_buttons.reset_btn + assert reset_btn + self.save_btn = self.edit_buttons.save_btn + self.reset_btn = reset_btn + self.delete_btn = self.edit_buttons.delete_btn + self.save_btn.clicked.connect(self.save_data) - self.layout_btn.addWidget(self.save_btn) - # reset - self.reset_btn = QPushButton("Zurücksetzen (Strg + Z)") - self.reset_btn.setShortcut("Ctrl+Z") - self.reset_btn.setFixedHeight(50) - self.reset_btn.setSizePolicy( - QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed - ) self.reset_btn.clicked.connect(self.reset_form) - self.layout_btn.addWidget(self.reset_btn) - # delete - self.delete_btn = QPushButton("Eintrag löschen (Strg + L)") - self.delete_btn.setShortcut("Ctrl+L") - self.delete_btn.setFixedHeight(50) - self.delete_btn.setSizePolicy( - QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed - ) self.delete_btn.clicked.connect(self._delete_data) - self.layout_btn.addWidget(self.delete_btn) + + self.main_layout.addWidget(self.edit_buttons) logger_auto_form.debug( - "Initialised Auto Form Widget. Registry:%s", + "[Auto-Form] Initialised Auto Form Widget. Registry:%s", pformat_registry(self.widget_registry), ) @@ -1561,11 +1503,11 @@ class AutoForm(QWidget): except ValueError: index = None self.STATE.rec_id = index - logger_gui.debug("Set index to %d, new state: %s", index, self.STATE) + logger_gui.debug("[Auto-Form] Set index to %s, new state: %s", index, self.STATE) def _disable_save(self) -> None: self.save_btn.setEnabled(False) - self.save_btn.setText(self.save_btn_txt_disabled) + self.save_btn.setText(self.edit_buttons.save_btn_txt_disabled) def _enable_save( self, @@ -1573,7 +1515,7 @@ class AutoForm(QWidget): ) -> None: QTimer.singleShot(timeout, lambda: self.save_btn.setEnabled(True)) QTimer.singleShot(timeout + 1, lambda: self.save_btn.setShortcut("Ctrl+S")) - self.save_btn.setText(self.save_btn_txt_enabled) + self.save_btn.setText(self.edit_buttons.save_btn_txt_enabled) def _activate_delete(self) -> None: if self.STATE.rec_id is not None: @@ -1589,35 +1531,32 @@ class AutoForm(QWidget): def _load_data( self, - lookup_id: int | None = None, + lookup_id: int | None, ) -> None: if DEBUG_NO_DATABASE: return - logger_auto_form.info(">>>> LOAD CLICKED") - if lookup_id is None: - # only debugging - lookup_id = self.STATE.rec_id + logger_auto_form.info("[Auto-Form] Load data method...") self.reset_form() - logger_auto_form.debug("Lookup ID: %d", lookup_id) + logger_auto_form.debug("[Auto-Form] Lookup ID: %s", lookup_id) - if lookup_id is not None and lookup_id > 0: + if lookup_id is not None: logger_auto_form.debug("Load from DB:") loaded_data = self.cfg.data_get(lookup_id) else: - logger_auto_form.error("Loading: Lookup ID 0 or NULL. Do nothing!") + logger_auto_form.debug("[Auto-Form] Loading: Lookup ID NULL. Do nothing!") return logger_auto_form.debug( - "Loaded data dict:\n%s Passing to Pydantic...", pformat(loaded_data) + "[Auto-Form] Loaded data dict:\n%s Passing to Pydantic...", pformat(loaded_data) ) model = self.cfg.model(**loaded_data) - logger_auto_form.debug("Loaded to Pydantic.") - logger_auto_form.debug("Convert to GUI structure...") + logger_auto_form.debug("[Auto-Form] Loaded to Pydantic.") + logger_auto_form.debug("[Auto-Form] Convert to GUI structure...") form_data = model.to_gui() - logger_auto_form.debug("Set form data...") - logger_auto_form.debug("Form data:\n%s", pformat(form_data)) + logger_auto_form.debug("[Auto-Form] Set form data...") + logger_auto_form.debug("[Auto-Form] Form data:\n%s", pformat(form_data)) self._set_form_data(form_data) self.STATE.rec_id = lookup_id self._activate_delete() @@ -1625,12 +1564,13 @@ class AutoForm(QWidget): def reset_form(self) -> None: reset_form(self.widget_registry) self.STATE.rec_id = None + self.STATE.form_data = None self._activate_delete() def _get_form_data(self) -> dict[str, Any]: form_data = get_form_data(self.widget_registry) - logger_auto_form.debug("\n\n>>>>>>> [AutoForm] Call get form data:") - logger_auto_form.debug("Form Data:\n%s", pformat(form_data)) + logger_auto_form.debug("[Auto-Form] Call get form data:") + logger_auto_form.debug("[Auto-Form] Form Data:\n%s", pformat(form_data)) if DEBUG_GET_SET: self.debug_form_data = form_data @@ -1641,7 +1581,7 @@ class AutoForm(QWidget): self, data: dict[str, Any], ) -> None: - logger_auto_form.debug("\n\n>>>>>>> [AutoForm] Call set form data:") + logger_auto_form.debug("[Auto-Form] Call set form data...") if DEBUG_GET_SET: data = self.debug_form_data @@ -1651,7 +1591,10 @@ class AutoForm(QWidget): self._load_data(self.STATE.rec_id) def _sync_GUI_to_state(self) -> None: - self.STATE.form_data = self._get_form_data() + form_data = self._get_form_data() + # TODO change usage of user ID (needs changes in model and backend) + form_data[self.SESSION_USER_KEY] = self.STATE.session.user_name + self.STATE.form_data = form_data def validate(self) -> list[str]: return validate_form_data(self.widget_registry) @@ -1661,8 +1604,6 @@ class AutoForm(QWidget): new_state: AutoForm_State, ) -> None: set_page_state(self.STATE, new_state) - # self.STATE.rec_id = new_state.rec_id - # self.STATE.form_data = new_state.form_data self._sync_state_to_GUI() def get_state(self) -> AutoForm_State: @@ -1675,50 +1616,29 @@ class AutoForm(QWidget): errors = self.validate() if errors: - # errors: abort saving and show pop up window - error_text = ( - "Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n▸ " - + "\n▸ ".join(errors) + logger_auto_form.error( + "[Auto-Form] Error during GUI validation phase:\n%s", pformat(errors) ) - QMessageBox.warning(self, "Fehlende Angaben", error_text) + GUI_validation_error_handling(errors) self._enable_save() return - # no errors: data can be saved - logger_auto_form.info("Erfolg! Alle Daten sind valide.") - logger_auto_form.info("Get form data call...") - form_data = self._get_form_data() + logger_auto_form.info("[Auto-Form] Success! All data is valid") + logger_auto_form.info("[Auto-Form] Get form data call...") + state = self.get_state() + form_data = state.form_data + assert form_data, "form data not set" - logger_auto_form.debug( - "\n\n------------>>>>>>>>> Get form data\n%s", pformat(form_data) - ) + logger_auto_form.debug("[Auto-Form] Get form data\n%s", pformat(form_data)) try: - logger_auto_form.debug("------------>>>>>>>>> Call Pydantic") + logger_auto_form.debug("[Auto-Form] Call Pydantic...") validated_data = self.cfg.model(**form_data) - # # TODO add user? except ValidationError as e: - # catch errors and show them in GUI - error_texts = [] - - # Pydantic detailed error list - for error in e.errors(): - logger_gui.error("Error during validation phase:\n%s", pformat(error)) - - error_field = str(error["loc"][0]) - reason = error["msg"] - - error_texts.append(f"- {error_field}: {reason}") - - # TODO: Is this needed and feasible? - # formatting red - # if fehlerhaftes_feld in self.widgets: - # self.widgets[fehlerhaftes_feld].setStyleSheet( - # "border: 1px solid red; background: #fef2f2;" - # ) - - # tell user what went wrong - QMessageBox.warning(self, "Eingabefehler", "\n".join(error_texts)) + logger_auto_form.error( + "[Auto-Form] Error during Pydantic validation phase:\n%s", pformat(e.errors()) + ) + pydantic_validation_error_handling(e) else: if DEBUG_NO_DATABASE: return @@ -1726,20 +1646,20 @@ class AutoForm(QWidget): # save data to database db_data = validated_data.to_db(exclude=self.cfg.ignored_keys) logger_auto_form.debug( - ("Form data with 'exlude' (must be saved in the database):\n%s"), + "[Auto-Form] Form data with 'exlude' (must be saved in the database):\n%s", pformat(db_data), ) if self.STATE.rec_id is None: - logger_auto_form.debug("Insert triggered") + logger_auto_form.debug("[Auto-Form] Insert triggered...") init_rec_id = self.cfg.data_insert(db_data) assert isinstance(init_rec_id, int) self.STATE.rec_id = init_rec_id else: - logger_auto_form.debug("Update triggered") + logger_auto_form.debug("[Auto-Form] Update triggered...") self.cfg.data_update(self.STATE.rec_id, db_data) - logger_auto_form.info("Data saved successfully") + logger_auto_form.info("[Auto-Form] Data saved successfully") self.update_triggered.emit() self._activate_delete() finally: @@ -3688,19 +3608,6 @@ class Page_Consulting(QWidget): # state management self.STATE = Page_Consulting_State(session=session) - # self.current_id: InitRecId = -1 - # self.COMPANY_FETAURES_MASTER_DATA: dict[str, str] = { - # "ma_unternehmensname": "Name:", - # "ma_branche": "Branche:", - # "ma_strasse": "Straße:", - # "ma_hausnummer": "Hausnummer:", - # "ma_plz": "PLZ:", - # "ma_ort": "Ort:", - # "ma_website": "Webseite:", - # "ma_mail": "E-Mail:", - # "ma_telefonnummer": "Telefon:", - # } - # Hauptlayout der Seite outer_layout = QHBoxLayout(self) vert_layout = QVBoxLayout() @@ -3907,8 +3814,6 @@ class Page_Consulting(QWidget): self, new_state: Page_Consulting_State, ) -> None: - # TODO add correct loading behaviour - # TODO add validation (correct type with necessary information) logger_page_consulting.debug("[Consulting] Loading with request:\n%s", new_state) set_page_state(self.STATE, new_state) # child modules @@ -4696,19 +4601,184 @@ def global_exception_handler(exc_type, exc_value, exc_traceback): # message to user # check if QApplication exists otherwise crashing of crash handler possible) if QApplication.instance(): - msg_box = QMessageBox() - msg_box.setIcon(QMessageBox.Icon.Critical) - msg_box.setWindowTitle("Kritischer Fehler") - msg_box.setText( - "Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert." + msg_box = get_error_message_box( + QMessageBox.Icon.Critical, + "Kritischer Fehler", + "Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert.", + error_msg, ) - # details for user or screenshots - msg_box.setDetailedText(error_msg) + # msg_box.setIcon(QMessageBox.Icon.Critical) + # msg_box.setWindowTitle("Kritischer Fehler") + # msg_box.setText( + # "Ein unerwarteter Fehler ist aufgetreten. Die Details wurden protokolliert." + # ) + # # details for user or screenshots + # msg_box.setDetailedText(error_msg) msg_box.exec() sys.exit(1) +def get_error_message_box( + msg_type: QMessageBox.Icon, + title: str, + message: str, + detailed_text: str | None = None, +) -> QMessageBox: + msg_box = QMessageBox() + msg_box.setIcon(msg_type) + msg_box.setWindowTitle(title) + msg_box.setText(message) + if detailed_text is not None: + msg_box.setDetailedText(detailed_text) + + return msg_box + + +def pydantic_validation_error_handling( + val_error: ValidationError, +) -> None: + error_texts: list[str] = [] + translated_errors = translate_pydantic_errors(val_error.errors()) + + for error in translated_errors: + error_field = str(error["loc"][0]) + reason = error["msg"] + path = " → ".join(error["loc"][:-1]) # type: ignore + + error_texts.append(f"- {error_field}: {reason}, (Pfad: {path})") + + msg_box = get_error_message_box( + QMessageBox.Icon.Warning, + "Fehler bei der Validierung der Eingabedaten", + ( + "Bei der Validierung der Eingaben ist ein Fehler aufgetreten. Details " + "sind unten angefügt." + ), + "\n".join(error_texts), + ) + msg_box.exec() + + +def GUI_validation_error_handling( + errors: list[str], +) -> None: + error_text = "Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n▸ " + "\n▸ ".join( + errors + ) + msg_box = get_error_message_box( + QMessageBox.Icon.Warning, + "Fehlende oder fehlerhafte Angaben", + error_text, + ) + msg_box.exec() + + +# @dc.dataclass(slots=True) +# class EditButton: +# btn: QWidget +# txt_enabled: str +# txt_disabled: str + + +# @dc.dataclass(slots=True, kw_only=True) +# class EditButtonLayout: +# layout: QLayout +# save: EditButton +# delete: EditButton +# reset: EditButton | None + + +class EditButtons(QWidget): + def __init__( + self, + add_reset_btn: bool, + ) -> None: + super().__init__() + + layout_btn = QHBoxLayout(self) + layout_btn.setContentsMargins(0, 0, 0, 0) + + # main_layout.addLayout(layout_btn) # TODO add in component + # save + self.save_btn_txt_enabled = "Speichern (Strg + S)" + self.save_btn_txt_disabled = "Wird gespeichert..." + self.save_btn = QPushButton(self.save_btn_txt_enabled) + self.save_btn.setShortcut("Ctrl+S") + self.save_btn.setFixedHeight(50) + self.save_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + # save_btn.clicked.connect(save_data) # TODO add in component + layout_btn.addWidget(self.save_btn) + + # reset + self.reset_button: QPushButton | None = None + if add_reset_btn: + self.reset_btn = QPushButton("Zurücksetzen (Strg + Z)") + self.reset_btn.setShortcut("Ctrl+Z") + self.reset_btn.setFixedHeight(50) + self.reset_btn.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + # reset_btn.clicked.connect(reset_form) # TODO add in component + layout_btn.addWidget(self.reset_btn) + + # delete + self.delete_btn = QPushButton("Eintrag löschen (Strg + L)") + self.delete_btn.setShortcut("Ctrl+L") + self.delete_btn.setFixedHeight(50) + self.delete_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + # delete_btn.clicked.connect(self._delete_data) # TODO add in component + layout_btn.addWidget(self.delete_btn) + + +# def get_edit_buttons( +# add_reset_btn: bool, +# ) -> EditButtonLayout: +# layout_btn = QHBoxLayout() +# # main_layout.addLayout(layout_btn) # TODO add in component +# # save +# save_btn_txt_enabled = "Speichern (Strg + S)" +# save_btn_txt_disabled = "Wird gespeichert..." +# save_btn = QPushButton(save_btn_txt_enabled) +# save_btn.setShortcut("Ctrl+S") +# save_btn.setFixedHeight(50) +# save_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) +# # save_btn.clicked.connect(save_data) # TODO add in component +# edit_button_save = EditButton(save_btn, "Speichern (Strg + S)", "Wird gespeichert...") +# layout_btn.addWidget(save_btn) + +# # reset +# edit_button_reset: EditButton | None = None +# if add_reset_btn: +# reset_btn = QPushButton("Zurücksetzen (Strg + Z)") +# reset_btn.setShortcut("Ctrl+Z") +# reset_btn.setFixedHeight(50) +# reset_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) +# # reset_btn.clicked.connect(reset_form) # TODO add in component +# edit_button_reset = EditButton( +# save_btn, "Zurücksetzen (Strg + Z)", "Zurücksetzen (Strg + Z)" +# ) +# layout_btn.addWidget(reset_btn) + +# # delete +# delete_btn = QPushButton("Eintrag löschen (Strg + L)") +# delete_btn.setShortcut("Ctrl+L") +# delete_btn.setFixedHeight(50) +# delete_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) +# edit_button_delete = EditButton( +# save_btn, "Eintrag löschen (Strg + L)", "Eintrag löschen (Strg + L)" +# ) +# # delete_btn.clicked.connect(self._delete_data) # TODO add in component +# layout_btn.addWidget(delete_btn) + +# return EditButtonLayout( +# layout=layout_btn, +# save=edit_button_save, +# delete=edit_button_delete, +# reset=edit_button_reset, +# ) + + def qt_message_handler(mode, context, message): """forwards internal Qt C++ warnings to Python loggers""" if mode == QtMsgType.QtInfoMsg: