generic typing of field definitions and value getter function for single widgets

This commit is contained in:
2026-07-03 13:00:56 +02:00
parent deeb038956
commit afa8fb351d
2 changed files with 149 additions and 77 deletions
+7 -2
View File
@@ -4,12 +4,17 @@ import dataclasses as dc
import enum
import uuid
from collections.abc import Sequence
from typing import Any, Final
from typing import TYPE_CHECKING, Any, Final, Generic, TypeVar
import babel
from wce_crm.custom_widget_registry import CUSTOM_WIDGET_NAMES
if TYPE_CHECKING:
from PySide6.QtWidgets import QWidget
T = TypeVar("T", bound="QWidget")
class FormFieldType(enum.StrEnum):
GROUP = enum.auto()
@@ -31,7 +36,7 @@ class FormFieldType(enum.StrEnum):
@dc.dataclass(slots=True)
class FormField:
class FormField(Generic[T]):
label: str
type: FormFieldType
children: Sequence[FormField] = dc.field(default_factory=list)
+118 -51
View File
@@ -12,7 +12,17 @@ from collections import defaultdict
from collections.abc import Container, Iterable, Sequence
from pathlib import Path
from pprint import pformat
from typing import TYPE_CHECKING, Any, Final, Protocol, TypeAlias, TypedDict, TypeVar, cast
from typing import (
TYPE_CHECKING,
Any,
Final,
Literal,
Protocol,
TypeAlias,
TypedDict,
TypeVar,
cast,
)
from typing_extensions import override
from dopt_basics import datetime as dopt_dt
@@ -92,6 +102,7 @@ if TYPE_CHECKING:
K = TypeVar("K")
V = TypeVar("V")
T = TypeVar("T", bound="QWidget")
DEBUG: bool = True
@@ -274,7 +285,7 @@ class CustomWidget(QWidget):
def _add_widget_to_layout(
form_field: FormField,
form_field: FormField[T],
widget: QWidget,
target_layout: QLayout,
widget_only: bool = False,
@@ -299,10 +310,10 @@ def _add_widget_to_layout(
def build_and_add_widget(
form_field: FormField,
form_field: FormField[T],
parent_layout: QLayout,
prefix: str = "",
) -> tuple[str, QWidget]:
) -> tuple[str, T]:
no_scroll_filter = NoScrollFilter(parent_layout)
if prefix and form_field.key:
full_key = f"{prefix}{COLUMN_SEP}{form_field.key}" if prefix else form_field.key
@@ -311,7 +322,6 @@ def build_and_add_widget(
else:
full_key = form_field.key
widget: QWidget
layout_widget_only: bool = False
install_no_scroll_filter: bool = True
@@ -496,7 +506,7 @@ def build_and_add_widget(
_add_widget_to_layout(form_field, widget, parent_layout, widget_only=layout_widget_only)
return full_key, widget
return full_key, cast(T, widget)
def _build_ui_recursively(
@@ -711,13 +721,59 @@ def get_form_data(
ignored_keys.append(key)
continue
if form_field.type is FormFieldType.TEXT_DATE:
value = get_widget_value(widget, "date")
elif form_field.type is FormFieldType.TEXT_DATETIME:
value = get_widget_value(widget, "datetime")
else:
value = get_widget_value(widget)
# elif isinstance(widget, QDateEdit):
# qt_date = widget.date()
# value = qt_date.toPython()
# elif isinstance(widget, FlexibleDateInput):
# value = widget.get_pydate()
# elif isinstance(widget, FlexibleDateTimeInput):
# value = widget.get_pydatetime()
# elif isinstance(widget, QComboBox):
# value = widget.currentData()
# elif isinstance(
# widget,
# (
# DynamicListWidget,
# DynamicDropdownWidgetNumeric,
# DynamicDropdownWidgetOption,
# CustomWidget,
# ),
# ):
# # this is a special data structure with some assumptions of the widget's internals
# # DynamicListWidget: this is a list -> each dynamic list contains a list
# # of such dictionaries (handled below)
# value = widget.get_form_data()
if isinstance(value, dict):
raw_data.update(value)
else:
raw_data[key] = value
logger_get_data.debug("[base:get_form_data]>>>>> RAW DATA:\n%s", pformat(raw_data))
return raw_data
def get_widget_value(
widget: QWidget,
text_type: Literal["default", "date", "datetime"] = "default",
) -> Any:
value: Any | None = None
if isinstance(widget, QLineEdit):
data = widget.text()
if data != "":
value = data
if form_field.type is FormFieldType.TEXT_DATE:
if text_type == "date":
value = datetime.datetime.strptime(value, DATE_FMT).date()
elif form_field.type is FormFieldType.TEXT_DATETIME:
elif text_type == "datetime":
value = datetime.datetime.strptime(value, DATETIME_FMT)
elif isinstance(widget, QPlainTextEdit):
data = widget.toPlainText()
@@ -746,14 +802,7 @@ def get_form_data(
# of such dictionaries (handled below)
value = widget.get_form_data()
if isinstance(value, dict):
raw_data.update(value)
else:
raw_data[key] = value
logger_get_data.debug("[base:get_form_data]>>>>> RAW DATA:\n%s", pformat(raw_data))
return raw_data
return value
def set_widget_value(
@@ -3301,17 +3350,19 @@ class Session:
@dc.dataclass(slots=True)
class Page_Consulting_ConsultingSession_State:
user_id: UserId # must be set with the session (always known)
user_name: str # must be set with the session (always known)
cons_session_id: int | None = None # known and not known entries
timestamp: datetime.datetime = dc.field(
nutzer_id: UserId # must be set with the session (always known)
nutzer_name: str # must be set with the session (always known)
beratung_id: int | None = None # known and not known entries
zeitstempel: datetime.datetime = dc.field(
default_factory=lambda: datetime.datetime.now(tz=datetime.UTC)
)
partner: str = ""
interaction_scheme: str = "" # set string like in other dropdowns or use integer (enum)
topic: str = "" # TODO should be linked to something, target unclear (need specification by customer)
notes: str = ""
response: str = ""
ansprechpartner: str | None = None
interaction_scheme: str | None = None
topic: str | None = (
None # TODO should be linked to something, target unclear (need specification by customer)
)
anmerkungen: str | None = None
rueckmeldung: str | None = None
@dc.dataclass(slots=True)
@@ -3614,23 +3665,38 @@ class Page_Consulting_ConsultingSession(QWidget):
layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
ROW_HEIGHT = 70
self.user_field = QLineEdit(
placeholderText="wird über aktiven Nutzer bei Anlage gesetzt"
user_field_def = FormField[QLineEdit](
"",
FormFieldType.TEXT,
readonly=True,
placeholder="wird über aktiven Nutzer bei Anlage gesetzt",
)
self.user_field.setReadOnly(True)
self.user_field.setProperty("styleClass", "stempel")
_, self.user_field = build_and_add_widget(user_field_def, layout)
# ?? date or datetime?
# self.timestamp = FlexibleDateInput()
# self.timestamp.set_pydate(datetime.datetime.now())
self.timestamp = FlexibleDateTimeInput()
timestamp_def = FormField[FlexibleDateTimeInput]("", FormFieldType.DATETIME_FLEXIBLE)
_, self.timestamp = build_and_add_widget(timestamp_def, layout)
self.timestamp.set_pydatetime(datetime.datetime.now())
self.contact = QLineEdit(placeholderText="Freitext")
self.contact_type = QLineEdit(placeholderText="sollte Dropdown sein") # Dropdown
self.crm_link = QLineEdit(placeholderText="CRM Links")
contact_def = FormField[QLineEdit]("", FormFieldType.TEXT, placeholder="Freitext")
_, self.contact = build_and_add_widget(contact_def, layout)
self.plain_text = QPlainTextEdit()
self.response = QPlainTextEdit()
contact_type_def = FormField[QComboBox](
"",
FormFieldType.DROPDOWN,
placeholder="sollte Dropdown sein",
options=[("Persönlich", None), ("Telefon", None), ("Mail", None)],
)
_, self.contact_type = build_and_add_widget(contact_type_def, layout)
crm_link_def = FormField[QLineEdit]("", FormFieldType.TEXT, placeholder="CRM Links")
_, self.crm_link = build_and_add_widget(crm_link_def, layout)
plain_text_def = FormField[QPlainTextEdit]("", FormFieldType.LONGTEXT)
_, self.plain_text = build_and_add_widget(plain_text_def, layout)
response_def = FormField[QPlainTextEdit]("", FormFieldType.LONGTEXT)
_, self.response = build_and_add_widget(response_def, layout)
self.btn_delete = QPushButton("")
self.btn_delete.setFlat(True) # Entfernt den blockigen Rahmen
@@ -3639,6 +3705,7 @@ class Page_Consulting_ConsultingSession(QWidget):
self.btn_delete.setStyleSheet("color: #cc0000; font-weight: bold;")
self.btn_delete.clicked.connect(lambda: self.delete_request.emit(self))
self.btn_delete.setFixedWidth(30)
layout.addWidget(self.btn_delete)
widgets = (
self.user_field,
@@ -3652,7 +3719,7 @@ class Page_Consulting_ConsultingSession(QWidget):
)
for widget in widgets:
widget.setFixedHeight(ROW_HEIGHT)
layout.addWidget(widget)
# layout.addWidget(widget)
# column stretching: must be matched with header
num_widgets = len(widgets)
@@ -3676,22 +3743,22 @@ class Page_Consulting_ConsultingSession(QWidget):
logger_page_consulting.debug(
"[Page Consulting -- Consulting Session]: State = %s", self.STATE
)
set_widget_value(self.user_field, self.STATE.user_name)
set_widget_value(self.timestamp, self.STATE.timestamp.astimezone(TIMEZONE_CEST))
set_widget_value(self.contact, self.STATE.partner)
set_widget_value(self.user_field, self.STATE.nutzer_name)
set_widget_value(self.timestamp, self.STATE.zeitstempel.astimezone(TIMEZONE_CEST))
set_widget_value(self.contact, self.STATE.ansprechpartner)
set_widget_value(self.contact_type, self.STATE.interaction_scheme)
set_widget_value(self.crm_link, self.STATE.topic)
set_widget_value(self.plain_text, self.STATE.notes)
set_widget_value(self.response, self.STATE.response)
set_widget_value(self.plain_text, self.STATE.anmerkungen)
set_widget_value(self.response, self.STATE.rueckmeldung)
def _sync_gui_to_state(self) -> None:
# TODO: get timestamp from custom field self.STATE.timestamp
self.STATE.timestamp = self.timestamp.get_pydatetime()
self.STATE.partner = self.contact.text()
self.STATE.interaction_scheme = self.contact_type.text()
self.STATE.topic = self.crm_link.text() # TODO: change to correct link
self.STATE.notes = self.plain_text.toPlainText()
self.STATE.response = self.response.toPlainText()
self.STATE.zeitstempel = self.timestamp.get_pydatetime()
self.STATE.ansprechpartner = get_widget_value(self.contact)
self.STATE.interaction_scheme = get_widget_value(self.contact_type)
self.STATE.topic = get_widget_value(self.crm_link) # TODO: change to correct link
self.STATE.anmerkungen = get_widget_value(self.plain_text)
self.STATE.rueckmeldung = get_widget_value(self.response)
def get_data(self) -> Page_Consulting_ConsultingSession_State:
self._sync_gui_to_state()
@@ -3873,8 +3940,8 @@ class Page_Consulting_Table(QWidget):
) -> None:
if row_state is None:
row_state = Page_Consulting_ConsultingSession_State(
user_id=self.SITE_STATE.session.user_id,
user_name=self.SITE_STATE.session.user_name,
nutzer_id=self.SITE_STATE.session.user_id,
nutzer_name=self.SITE_STATE.session.user_name,
)
row = Page_Consulting_ConsultingSession(site_state=self.SITE_STATE, state=row_state)