nested validation and error messages

This commit is contained in:
2026-05-13 11:52:20 +02:00
parent 8b3eb63f62
commit a7accaa20c
2 changed files with 126 additions and 80 deletions

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import dataclasses as dc
import enum
import re
import sys
import time
import uuid
@@ -319,8 +320,10 @@ class FormField:
elif self.type is FormFieldType.DROPDOWN:
self.dropdown_options = tuple(DropdownOption(op[0], op[1]) for op in options)
for child in self.children:
child.parent = self
if self.children:
self.required = any((child.required for child in self.children))
for child in self.children:
child.parent = self
class WidgetRegistryEntry(TypedDict):
@@ -700,7 +703,7 @@ FORM_FIELDS_SCHOOL = [
FormField(
"Abschluss",
FormFieldType.TEXT,
required=False,
required=True,
),
FormField(
"Abschlussgrad laut Dokument",
@@ -1157,6 +1160,104 @@ def _insert_nested(
target_dict[key_path[-1]] = value
def get_form_data(
widget_registry: WidgetRegistry,
) -> dict[str, Any]:
raw_data = {}
for key, registry_entry in widget_registry.items():
value: Any | None = None
widget = registry_entry["widget"]
if isinstance(widget, QLineEdit):
value = widget.text()
elif isinstance(widget, QPlainTextEdit):
value = widget.toPlainText()
elif isinstance(widget, QDateEdit):
qt_date = widget.date()
value = qt_date.toPython()
elif isinstance(widget, QComboBox):
value = widget.currentData()
elif isinstance(widget, DynamicListWidget):
# this should be a list: each dynamic list contains a list
# of such dictionaries
form_data = widget.get_form_data()
value = [val for val in form_data.values()]
_insert_nested(raw_data, key.split("."), value)
return raw_data
DYNAMIC_LIST_KEY_PATTERN = re.compile(r"-\[(\d+)\]")
def validate_form_data(
widget_registry: WidgetRegistry,
) -> list[str]:
errors: list[str] = []
for key, registry_entry in widget_registry.items():
error_post: bool = False
widget = registry_entry["widget"]
form_field = registry_entry["form_field"]
if not form_field.readonly:
widget.setStyleSheet("")
if not form_field.required:
continue
dynamic_list_num: str = ""
if (
form_field.parent is not None
and form_field.parent.type is FormFieldType.DYNAMIC_LIST
):
# get also number of group for enhanced error messages
matches = DYNAMIC_LIST_KEY_PATTERN.search(key)
if matches:
dynamic_list_num = matches.group(1)
if isinstance(widget, (QLineEdit, QDateEdit)):
if widget.text().strip():
continue
error_post = True
elif isinstance(widget, QPlainTextEdit):
if widget.toPlainText().strip():
continue
error_post = True
elif isinstance(widget, QComboBox):
if widget.currentData() is not None:
continue
error_post = True
elif isinstance(widget, DynamicListWidget):
errors_widget = widget.validate_form_data()
if not errors_widget:
continue
errors.extend(errors_widget)
if not error_post:
continue
error = form_field.label.replace("*", "").replace(":", "")
if form_field.parent is not None:
parent_label = form_field.parent.label.replace("*", "").replace(":", "")
if dynamic_list_num:
error = f"{parent_label}{f'{parent_label} {dynamic_list_num}'}{error}"
else:
error = f"{parent_label}{error}"
error = error.replace("&&", "&")
errors.append(error)
# optical feedback to highlight erroneous cells
widget.setStyleSheet("""
border: 1px solid #ef4444;
background-color: #ffe9e9;
padding: 4px;
border-radius: 4px;
""")
return errors
class AutoForm(QWidget):
def __init__(
self,
@@ -1249,45 +1350,13 @@ class AutoForm(QWidget):
def on_save_clicked(self) -> None:
self._disable_save()
errors: list[str] = []
for registry_entry in self.widget_registry.values():
widget = registry_entry["widget"]
form_field = registry_entry["form_field"]
if not form_field.readonly:
widget.setStyleSheet("")
if not form_field.required:
continue
if isinstance(widget, (QLineEdit, QDateEdit)):
if widget.text().strip():
continue
elif isinstance(widget, QPlainTextEdit):
if widget.toPlainText().strip():
continue
elif isinstance(widget, QComboBox):
if widget.currentData() is not None:
continue
error = form_field.label.replace("*", "").replace(":", "")
if form_field.parent is not None:
error = f"{form_field.parent.label}: {error}"
errors.append(error)
# optical feedback to highlight erroneous cells
widget.setStyleSheet("""
border: 1px solid #ef4444;
background-color: #ffe9e9;
padding: 4px;
border-radius: 4px;
""")
errors = validate_form_data(self.widget_registry)
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)
"Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n "
+ "\n ".join(errors)
)
QMessageBox.warning(self, "Fehlende Angaben", error_text)
self._enable_save()
@@ -1310,30 +1379,7 @@ class AutoForm(QWidget):
reset_form(self.widget_registry)
def get_form_data(self) -> dict[str, Any]:
# raise NotImplementedError()
raw_data = {}
for key, registry_entry in self.widget_registry.items():
value: Any | None = None
widget = registry_entry["widget"]
if isinstance(widget, QLineEdit):
value = widget.text()
elif isinstance(widget, QPlainTextEdit):
value = widget.toPlainText()
elif isinstance(widget, QDateEdit):
qt_date = widget.date()
value = qt_date.toPython()
elif isinstance(widget, QComboBox):
value = widget.currentData()
elif isinstance(widget, DynamicListWidget):
# TODO add method
# value = widget.get_data()
# this should be a list: each dynamic list contains a list
# of such dictionaries
value = "test"
_insert_nested(raw_data, key.split("."), value)
raw_data = get_form_data(self.widget_registry)
return raw_data
@@ -1442,10 +1488,6 @@ class DynamicListWidget(QWidget):
self.inner_layout.insertWidget(self.inner_layout.count() - 1, entry_box)
self.update_sub_forms()
# print("------------>>>>>>>>> Added entry, length widget registry:")
# pprint(len(self.widget_registry))
# pprint(list(self.widget_registry.keys()))
def remove_entry(
self,
subform_to_remove: SubForm,
@@ -1454,9 +1496,6 @@ class DynamicListWidget(QWidget):
subform_to_remove.entry_box.deleteLater()
self.sub_forms.remove(subform_to_remove)
self.update_sub_forms()
# print("------------>>>>>>>>> Removed entry, length widget registry:")
# pprint(len(self.widget_registry))
# pprint(list(self.widget_registry.keys()))
def update_sub_forms(self):
for index, sub_form in enumerate(self.sub_forms, start=1):
@@ -1470,22 +1509,17 @@ class DynamicListWidget(QWidget):
def reset_form(self) -> None:
reset_form(self.widget_registry)
def validate_form_data(self) -> list[str]:
return validate_form_data(self.widget_registry)
def load_form_data(self) -> None:
# TODO add way to load data when initialised (probably with click)
...
def get_form_data(self):
raise NotImplementedError()
raw_data = get_form_data(self.widget_registry)
results = []
for form in self.sub_forms:
# Hier bauen wir ein Dictionary pro Sub-Formular
entry_data = {
"strasse": form["widgets"]["strasse"].text(),
"ort": form["widgets"]["ort"].text(),
}
results.append(entry_data)
return results
return raw_data
class ClickableCell(QFrame):
@@ -1604,7 +1638,6 @@ class NewEntrySelect_view(QWidget):
btn_company.setFixedWidth(300)
btn_company.setFixedHeight(40)
btn_company.clicked.connect(lambda: self.company_requested.emit())
# btn_company.clicked.connect(lambda: print("Unternehmen gewählt"))
btn_person = QPushButton("Individualperson →")
btn_person.setFixedWidth(300)

View File

@@ -1,6 +1,7 @@
# %%
import dataclasses as dc
import enum
import re
from PySide6.QtCore import QDate, Qt
@@ -27,6 +28,18 @@ class FormField:
self.label += "*"
# %%
DYNAMIC_LIST_KEY_PATTERN = r"-\[(\d+)\]"
key = "Schulbildung-[12].7b8da0f7-7a0e-4f71-878a-85616099e849"
matches = re.search(DYNAMIC_LIST_KEY_PATTERN, key)
# %%
matches
# %%
matches.group(1)
# %%
t_str = "asd.yxcxc.dfgjj.aasdsdsdsd.sdsdsdsd"
splitted = t_str.split(".")