generated from dopt-python/py311
nested validation and error messages
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import dataclasses as dc
|
import dataclasses as dc
|
||||||
import enum
|
import enum
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
@@ -319,8 +320,10 @@ class FormField:
|
|||||||
elif self.type is FormFieldType.DROPDOWN:
|
elif self.type is FormFieldType.DROPDOWN:
|
||||||
self.dropdown_options = tuple(DropdownOption(op[0], op[1]) for op in options)
|
self.dropdown_options = tuple(DropdownOption(op[0], op[1]) for op in options)
|
||||||
|
|
||||||
for child in self.children:
|
if self.children:
|
||||||
child.parent = self
|
self.required = any((child.required for child in self.children))
|
||||||
|
for child in self.children:
|
||||||
|
child.parent = self
|
||||||
|
|
||||||
|
|
||||||
class WidgetRegistryEntry(TypedDict):
|
class WidgetRegistryEntry(TypedDict):
|
||||||
@@ -700,7 +703,7 @@ FORM_FIELDS_SCHOOL = [
|
|||||||
FormField(
|
FormField(
|
||||||
"Abschluss",
|
"Abschluss",
|
||||||
FormFieldType.TEXT,
|
FormFieldType.TEXT,
|
||||||
required=False,
|
required=True,
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
"Abschlussgrad laut Dokument",
|
"Abschlussgrad laut Dokument",
|
||||||
@@ -1157,6 +1160,104 @@ def _insert_nested(
|
|||||||
target_dict[key_path[-1]] = value
|
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):
|
class AutoForm(QWidget):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -1249,45 +1350,13 @@ class AutoForm(QWidget):
|
|||||||
|
|
||||||
def on_save_clicked(self) -> None:
|
def on_save_clicked(self) -> None:
|
||||||
self._disable_save()
|
self._disable_save()
|
||||||
errors: list[str] = []
|
errors = validate_form_data(self.widget_registry)
|
||||||
|
|
||||||
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;
|
|
||||||
""")
|
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
# errors: abort saving and show pop up window
|
# errors: abort saving and show pop up window
|
||||||
error_text = (
|
error_text = (
|
||||||
"Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n- "
|
"Bitte füllen Sie die folgenden Pflichtfelder aus:\n\n▸ "
|
||||||
+ "\n- ".join(errors)
|
+ "\n▸ ".join(errors)
|
||||||
)
|
)
|
||||||
QMessageBox.warning(self, "Fehlende Angaben", error_text)
|
QMessageBox.warning(self, "Fehlende Angaben", error_text)
|
||||||
self._enable_save()
|
self._enable_save()
|
||||||
@@ -1310,30 +1379,7 @@ class AutoForm(QWidget):
|
|||||||
reset_form(self.widget_registry)
|
reset_form(self.widget_registry)
|
||||||
|
|
||||||
def get_form_data(self) -> dict[str, Any]:
|
def get_form_data(self) -> dict[str, Any]:
|
||||||
# raise NotImplementedError()
|
raw_data = get_form_data(self.widget_registry)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return raw_data
|
return raw_data
|
||||||
|
|
||||||
@@ -1442,10 +1488,6 @@ class DynamicListWidget(QWidget):
|
|||||||
self.inner_layout.insertWidget(self.inner_layout.count() - 1, entry_box)
|
self.inner_layout.insertWidget(self.inner_layout.count() - 1, entry_box)
|
||||||
self.update_sub_forms()
|
self.update_sub_forms()
|
||||||
|
|
||||||
# print("------------>>>>>>>>> Added entry, length widget registry:")
|
|
||||||
# pprint(len(self.widget_registry))
|
|
||||||
# pprint(list(self.widget_registry.keys()))
|
|
||||||
|
|
||||||
def remove_entry(
|
def remove_entry(
|
||||||
self,
|
self,
|
||||||
subform_to_remove: SubForm,
|
subform_to_remove: SubForm,
|
||||||
@@ -1454,9 +1496,6 @@ class DynamicListWidget(QWidget):
|
|||||||
subform_to_remove.entry_box.deleteLater()
|
subform_to_remove.entry_box.deleteLater()
|
||||||
self.sub_forms.remove(subform_to_remove)
|
self.sub_forms.remove(subform_to_remove)
|
||||||
self.update_sub_forms()
|
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):
|
def update_sub_forms(self):
|
||||||
for index, sub_form in enumerate(self.sub_forms, start=1):
|
for index, sub_form in enumerate(self.sub_forms, start=1):
|
||||||
@@ -1470,22 +1509,17 @@ class DynamicListWidget(QWidget):
|
|||||||
def reset_form(self) -> None:
|
def reset_form(self) -> None:
|
||||||
reset_form(self.widget_registry)
|
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:
|
def load_form_data(self) -> None:
|
||||||
# TODO add way to load data when initialised (probably with click)
|
# TODO add way to load data when initialised (probably with click)
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_form_data(self):
|
def get_form_data(self):
|
||||||
raise NotImplementedError()
|
raw_data = get_form_data(self.widget_registry)
|
||||||
|
|
||||||
results = []
|
return raw_data
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class ClickableCell(QFrame):
|
class ClickableCell(QFrame):
|
||||||
@@ -1604,7 +1638,6 @@ class NewEntrySelect_view(QWidget):
|
|||||||
btn_company.setFixedWidth(300)
|
btn_company.setFixedWidth(300)
|
||||||
btn_company.setFixedHeight(40)
|
btn_company.setFixedHeight(40)
|
||||||
btn_company.clicked.connect(lambda: self.company_requested.emit())
|
btn_company.clicked.connect(lambda: self.company_requested.emit())
|
||||||
# btn_company.clicked.connect(lambda: print("Unternehmen gewählt"))
|
|
||||||
|
|
||||||
btn_person = QPushButton("Individualperson →")
|
btn_person = QPushButton("Individualperson →")
|
||||||
btn_person.setFixedWidth(300)
|
btn_person.setFixedWidth(300)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# %%
|
# %%
|
||||||
import dataclasses as dc
|
import dataclasses as dc
|
||||||
import enum
|
import enum
|
||||||
|
import re
|
||||||
|
|
||||||
from PySide6.QtCore import QDate, Qt
|
from PySide6.QtCore import QDate, Qt
|
||||||
|
|
||||||
@@ -27,6 +28,18 @@ class FormField:
|
|||||||
self.label += "*"
|
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"
|
t_str = "asd.yxcxc.dfgjj.aasdsdsdsd.sdsdsdsd"
|
||||||
splitted = t_str.split(".")
|
splitted = t_str.split(".")
|
||||||
|
|||||||
Reference in New Issue
Block a user