diff --git a/src/wce_crm/gui.py b/src/wce_crm/gui.py index 55bda69..0bbc392 100644 --- a/src/wce_crm/gui.py +++ b/src/wce_crm/gui.py @@ -71,14 +71,15 @@ setup_logging(enable_stderr=True) DEBUG: Final[bool] = True DEBUG_SEARCH_WIDGET: Final[bool] = False -DEBUG_NO_DATABASE: Final[bool] = False +DEBUG_NO_DATABASE: Final[bool] = True +DEBUG_GET_SET: Final[bool] = True logger = BASE_LOGGER.getChild("wce") logger.setLevel(logging.DEBUG) logger_search_widget = logger.getChild("search_widget") logger_search_widget.setLevel(logging.DEBUG) logger_get_data = logger.getChild("get_data") -logger_get_data.setLevel(logging.INFO) +logger_get_data.setLevel(logging.DEBUG) logger_auto_form = logger.getChild("get_data_auto_form") logger_auto_form.setLevel(logging.DEBUG) @@ -379,7 +380,7 @@ class FormFieldType(enum.StrEnum): EXTENDED_DROPDOWN = enum.auto() DYNAMIC_LIST = enum.auto() DYNAMIC_DROPDOWN_NUMERIC = enum.auto() - DYNAMIC_DROPDOWN_BOOLEAN = enum.auto() + DYNAMIC_DROPDOWN_OPTION = enum.auto() TEXT_SEARCH = enum.auto() CUSTOM = enum.auto() TEXT_DATE = enum.auto() @@ -420,6 +421,7 @@ class FormField: custom_widget: str = "" init_label: str = dc.field(init=False) ignore_get_data: bool = False + trigger_value: str = "" def __post_init__( self, @@ -448,6 +450,11 @@ class FormField: if self.type in (FormFieldType.DROPDOWN, FormFieldType.EXTENDED_DROPDOWN): self.dropdown_options = tuple(DropdownOption(op[0], op[1]) for op in options) + if self.type is FormFieldType.DYNAMIC_DROPDOWN_OPTION and not self.trigger_value: + raise ValueError( + "Dynamic Dropdown Option Widget must have a defined option or decision value" + ) + if self.children: self.required = self.required or any((child.required for child in self.children)) for child in self.children: @@ -741,13 +748,22 @@ def _build_ui_recursively( } parent_layout.addRow(widget) - case ( - FormFieldType.DYNAMIC_DROPDOWN_NUMERIC - | FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN - ): - widget = DynamicDropdownWidget( + case FormFieldType.DYNAMIC_DROPDOWN_NUMERIC: + widget = DynamicDropdownWidgetNumeric( field.children, - field.type, + field.label, + prefix=f"{full_key}", + ) + widget_registry[full_key] = { + "widget": widget, + "form_field": field, + } + parent_layout.addRow(widget) + + case FormFieldType.DYNAMIC_DROPDOWN_OPTION: + widget = DynamicDropdownWidgetOption( + field.children, + field.trigger_value, field.label, prefix=f"{full_key}", ) @@ -831,14 +847,16 @@ def reset_form( widget.setCurrentIndex(0) else: widget.setCurrentIndex(-1) - elif isinstance(widget, DynamicListWidget): - # dynamic list widget manages its widgets by itself - widget.reset_form() - elif isinstance(widget, DynamicDropdownWidget): - # dynamic list widget manages its widgets by itself - widget.reset_form() - elif isinstance(widget, CustomWidget): - # dynamic list widget manages its widgets by itself + elif isinstance( + widget, + ( + DynamicListWidget, + DynamicDropdownWidgetNumeric, + DynamicDropdownWidgetOption, + CustomWidget, + ), + ): + # custom widget classes manage their widgets on their own widget.reset_form() widget.setStyleSheet("") @@ -975,11 +993,13 @@ def get_form_data( elif isinstance(widget, QComboBox): value = widget.currentData() elif isinstance(widget, DynamicListWidget): + # TODO add to other custom widgets # this should be a list: each dynamic list contains a list # of such dictionaries value = widget.get_form_data() - - elif isinstance(widget, (DynamicDropdownWidget, CustomWidget)): + elif isinstance( + widget, (DynamicDropdownWidgetNumeric, DynamicDropdownWidgetOption, CustomWidget) + ): # this is a special data structure with some assumptions of the widget's internals value = widget.get_form_data() @@ -1036,7 +1056,14 @@ def set_widget_value( assert isinstance(value, list) widget.set_form_data(value) - elif isinstance(widget, (DynamicDropdownWidget, Grunderfassung_SuchWidget)): + elif isinstance( + widget, + ( + DynamicDropdownWidgetNumeric, + DynamicDropdownWidgetOption, + Grunderfassung_SuchWidget, + ), + ): assert isinstance(value, dict) widget.set_form_data(value) @@ -1058,7 +1085,14 @@ def set_form_data( # key_path = key.split(COLUMN_SEP) # value = _get_nested(data, key_path) # value = data.get(key, None) - if isinstance(widget, (DynamicDropdownWidget, Grunderfassung_SuchWidget)): + if isinstance( + widget, + ( + DynamicDropdownWidgetNumeric, + DynamicDropdownWidgetOption, + Grunderfassung_SuchWidget, + ), + ): value = data else: value = data[key] @@ -1104,7 +1138,15 @@ def validate_form_data( if widget.currentData() is not None: continue error_post = True - elif isinstance(widget, (DynamicListWidget, DynamicDropdownWidget, CustomWidget)): + elif isinstance( + widget, + ( + DynamicListWidget, + DynamicDropdownWidgetNumeric, + DynamicDropdownWidgetOption, + CustomWidget, + ), + ): errors_widget = widget.validate_form_data() if not errors_widget: continue @@ -1641,10 +1683,15 @@ class AutoForm(QWidget): QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed ) self.main_layout.addWidget(self.test_button) - button = QPushButton("GET DATA") - button.setFixedHeight(35) - button.clicked.connect(self.get_form_data) - self.main_layout.addWidget(button) + + button_get = QPushButton("GET DATA") + button_get.setFixedHeight(35) + button_get.clicked.connect(self.get_form_data) + self.main_layout.addWidget(button_get) + button_set = QPushButton("SET DATA") + button_set.setFixedHeight(35) + button_set.clicked.connect(self.set_form_data) + self.main_layout.addWidget(button_set) id_field_layout = QHBoxLayout() id_field_layout.setContentsMargins(0, 0, 0, 0) @@ -1682,6 +1729,8 @@ class AutoForm(QWidget): # buttons (save and reset) self.add_buttons = self.cfg.add_buttons + + self.debug_form_data: dict[str, Any] = {} if self.add_buttons: self.layout_btn = QHBoxLayout() self.main_layout.addLayout(self.layout_btn) @@ -1850,12 +1899,19 @@ class AutoForm(QWidget): logger_auto_form.debug("\n\n>>>>>>> [AutoForm] Call get form data:") logger_auto_form.debug("Form Data:\n%s", pformat(form_data)) + if DEBUG_GET_SET: + self.debug_form_data = form_data + return form_data def set_form_data( self, data: dict[str, Any], ) -> None: + logger_auto_form.debug("\n\n>>>>>>> [AutoForm] Call set form data:") + if DEBUG_GET_SET: + data = self.debug_form_data + set_form_data(self.widget_registry, data) @@ -2015,12 +2071,7 @@ class DynamicListWidget(QWidget): set_form_data(current_sub_form.registry, sub_form_data) -class DynamicDropdownType(enum.IntEnum): - NUMERIC = enum.auto() - BOOLEAN = enum.auto() - - -class DynamicDropdownWidget(QWidget): +class DynamicDropdownWidgetNumeric(QWidget): """ A Widget, which can generate and manage an arbitrary number of sub forms with additional information on a combobox selection (combobox). @@ -2029,7 +2080,6 @@ class DynamicDropdownWidget(QWidget): def __init__( self, form_fields: Sequence[FormField], - type: FormFieldType, label_add_info: str = "Eintrag", prefix: str = "", ): @@ -2089,22 +2139,10 @@ class DynamicDropdownWidget(QWidget): ) -> None: current_count = len(self.sub_forms) value: int - match self.type: - case FormFieldType.DYNAMIC_DROPDOWN_NUMERIC: - if text == DROPDOWN_DEFAULT: - value = 0 - else: - value = int(text) - current_count = len(self.sub_forms) - - case FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN: - if text == "ja": - value = 1 - else: - value = 0 - - case _: - raise ValueError("Undefined DynamicDropdownWidget type") + if text == DROPDOWN_DEFAULT: + value = 0 + else: + value = int(text) if value > current_count: diff = value - current_count @@ -2124,8 +2162,7 @@ class DynamicDropdownWidget(QWidget): sub_form = SubForm(container, prefix_parent=self.prefix, index=number_form) form_field_def = copy.copy(self.assigned_form_field) - if self.type is FormFieldType.DYNAMIC_DROPDOWN_NUMERIC: - form_field_def.label = form_field_def.enhanced_label(f"{number_form}") + form_field_def.label = form_field_def.enhanced_label(f"{number_form}") sub_form.full_keys = _build_ui_recursively( schema=[form_field_def], @@ -2216,6 +2253,195 @@ class DynamicDropdownWidget(QWidget): set_form_data(sub_form.registry, sub_form_data) +class DynamicDropdownWidgetOption(QWidget): + """ + A Widget, which can generate and manage an arbitrary number of sub forms with additional + information on a combobox selection (combobox). + """ + + def __init__( + self, + form_fields: Sequence[FormField], + trigger_value: str, + label_add_info: str = "Eintrag", + prefix: str = "", + ): + super().__init__() + + # form_fields = form_fields.children + if len(form_fields) == 0 or len(form_fields) > 1: + raise ValueError( + "Dynamic Dropdown Widget must have only one child, which is a dropdown widget" + ) + + self.combobox_field = form_fields[0] + assigned_form_fields = self.combobox_field.children + if len(assigned_form_fields) < 1: + raise ValueError( + ( + "Option Dynamic Dropdown Widget dropdown element must have at least one " + "child field definition" + ) + ) + self.trigger_value = trigger_value + self.assigned_form_fields = assigned_form_fields + + self.label_add_info = label_add_info + self.prefix = prefix + self.widget_registry: WidgetRegistry = {} + + # layout for group component + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.form_layout = QFormLayout() + self.main_layout.addLayout(self.form_layout) + + self.full_keys = _build_ui_recursively( + [self.combobox_field], + self.form_layout, + self.widget_registry, + prefix=f"{self.prefix}", + ) + dropdown_widget_entry = tuple(self.widget_registry.values())[0] + dropdown_widget = dropdown_widget_entry["widget"] + assert isinstance(dropdown_widget, QComboBox) + self.dropdown_widget = dropdown_widget + + self.rows_container = QWidget() + self.rows_layout = QVBoxLayout(self.rows_container) + self.rows_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.addWidget(self.rows_container) + + self.sub_forms: list[SubForm] = [] + + self.dropdown_widget.currentTextChanged.connect(self._selection_changed_changed) + + def _selection_changed_changed( + self, + text: str, + ) -> None: + current_count = len(self.sub_forms) + value: int + + if text == self.trigger_value: + value = 1 + else: + value = 0 + + if value > current_count: + diff = value - current_count + for _ in range(diff): + self._add_row() + elif value < current_count: + diff = current_count - value + for _ in range(diff): + self._remove_row() + + def _add_row(self) -> None: + number_form = len(self.sub_forms) + 1 + container = QWidget() + container.setContentsMargins(0, 0, 0, 0) + form_layout = QFormLayout(container) + form_layout.setContentsMargins(10, 0, 0, 0) + sub_form = SubForm(container, prefix_parent=self.prefix, index=number_form) + + form_field_def = copy.copy(self.assigned_form_fields) + # form_field_def.label = form_field_def.enhanced_label(f"{number_form}") + + sub_form.full_keys = _build_ui_recursively( + schema=form_field_def, + parent_layout=form_layout, + widget_registry=sub_form.registry, + prefix=f"{self.prefix}", + ) + self.rows_layout.addWidget(container) + + self.sub_forms.append(sub_form) + self.update_sub_forms() + + def _remove_row(self) -> None: + last_form = self.sub_forms.pop() + box_to_remove = last_form.entry_box + + self.rows_layout.removeWidget(box_to_remove) + box_to_remove.deleteLater() + self.update_sub_forms() + + def update_sub_forms(self) -> None: + update_sub_forms( + self.widget_registry, + sub_forms=self.sub_forms, + ) + + def _get_combined_registry(self, sub_forms_only: bool = False) -> WidgetRegistry: + whole_registry: WidgetRegistry = {} + if not sub_forms_only: + whole_registry = self.widget_registry.copy() + for sub in self.sub_forms: + whole_registry.update(sub.registry) + + return whole_registry + + def reset_form(self) -> None: + # resets dynamic content when dropdown is set back to default value + self.dropdown_widget.setCurrentIndex(0) + + def validate_form_data(self) -> list[str]: + errors = validate_form_data(self.widget_registry) + for form in self.sub_forms: + errors.extend(validate_form_data(form.registry)) + + return errors + + def get_form_data(self) -> dict[str, Any]: + whole_registry = self._get_combined_registry() + form_data = get_form_data(whole_registry) + # for sub in self.sub_forms: + # form_data.update(get_form_data(sub.registry)) + logger_get_data.debug( + "Form data DynamicDropdownWidgetOption:\n%s", pformat(form_data) + ) + + return form_data + + def set_form_data( + self, + data: dict[str, Any], + ) -> None: + # delete all rows + while self.sub_forms: + self._remove_row() + + # fill in value of combobox field + assert len(self.full_keys) == 1 + num_subforms: int = -1 + data = data.copy() + for key in self.full_keys: + widget = self.widget_registry[key]["widget"] + value = data[key] + set_widget_value(widget, value) + # TODO need to get the correct index + if value is None or value != self.trigger_value: + num_subforms = 0 + else: + num_subforms = 1 + del data[key] + # assert key in whole_registry, "key not in Dynamic DD Option" + + whole_registry = self._get_combined_registry(sub_forms_only=True) + logger_get_data.debug("[DynamicDD-Option] Whole widget registry:\n") + pprint_registry(whole_registry) + + logger_get_data.debug(">>>>>>>>> Call set_form_data for DynamicDropdown") + logger_get_data.debug("Data before set of subforms:%s", pformat(data)) + + assert len(self.sub_forms) == num_subforms + if not self.sub_forms: + return + + set_form_data(whole_registry, data) + + class NoScrollFilter(QObject): """disables scrolling in fields""" @@ -3021,48 +3247,48 @@ FORM_FIELDS_LANGUAGES = [ FORM_FIELDS = [ - FormField( - "Ersteintrag Datum", - FormFieldType.TEXT_DATETIME, - required=False, - key="Metadaten_erstellung", - readonly=True, - ignore_get_data=True, - ), - FormField( - "Aktualisierung Datum", - FormFieldType.TEXT_DATETIME, - required=False, - key="Metadaten_aktualisierung", - readonly=True, - ignore_get_data=True, - ), - FormField( - "Aktualisierung Nutzer", - FormFieldType.TEXT, - required=False, - key="Metadaten_nutzer", - readonly=True, - ), - FormField( - "Fallnummer", - FormFieldType.TEXT, - required=True, - key="Grunderfassung_fallnummer", - ), - FormField( - "Notizen", - FormFieldType.LONGTEXT, - required=False, - key="Grunderfassung_notiz", - ), - FormField( - "Suche", - FormFieldType.CUSTOM, - custom_widget="grunderfassung_suche", - key="Partnersuche", - children=FORM_FIELDS_SEARCH_HEAD, - ), + # FormField( + # "Ersteintrag Datum", + # FormFieldType.TEXT_DATETIME, + # required=False, + # key="Metadaten_erstellung", + # readonly=True, + # ignore_get_data=True, + # ), + # FormField( + # "Aktualisierung Datum", + # FormFieldType.TEXT_DATETIME, + # required=False, + # key="Metadaten_aktualisierung", + # readonly=True, + # ignore_get_data=True, + # ), + # FormField( + # "Aktualisierung Nutzer", + # FormFieldType.TEXT, + # required=False, + # key="Metadaten_nutzer", + # readonly=True, + # ), + # FormField( + # "Fallnummer", + # FormFieldType.TEXT, + # required=True, + # key="Grunderfassung_fallnummer", + # ), + # FormField( + # "Notizen", + # FormFieldType.LONGTEXT, + # required=False, + # key="Grunderfassung_notiz", + # ), + # FormField( + # "Suche", + # FormFieldType.CUSTOM, + # custom_widget="grunderfassung_suche", + # key="Partnersuche", + # children=FORM_FIELDS_SEARCH_HEAD, + # ), FormField( "Status && Projektrelevanz", FormFieldType.GROUP, @@ -3070,8 +3296,9 @@ FORM_FIELDS = [ children=[ FormField( "Projektrelevanz", - FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN, + FormFieldType.DYNAMIC_DROPDOWN_OPTION, key="status", + trigger_value="ja", children=[ FormField( "Relevanz", @@ -3083,54 +3310,56 @@ FORM_FIELDS = [ FormField( "Förderperiode", FormFieldType.TEXT, key="foerderperiode" ), + FormField("Feld 2", FormFieldType.TEXT, key="feld_2"), + FormField("Feld 3", FormFieldType.TEXT, key="feld_3"), ], ), ], ), ], ), - FormField( - "Daten Kontaktperson", - FormFieldType.GROUP, - key="Kontaktperson", - children=FORM_FIELDS_CONTACT_PERSON, - ), - FormField( - "Stammdaten", - FormFieldType.GROUP, - key="Stammdaten", - children=FORM_FIELDS_MASTER_DATA, - ), - FormField( - "Weitere Informationen", - FormFieldType.GROUP, - key="WeitereInfos", - children=FORM_FIELDS_ADDITIONAL_DATA, - ), - FormField( - "Schulbildung", - FormFieldType.DYNAMIC_LIST, - children=FORM_FIELDS_SCHOOL, - key="Schulbildung", - ), - FormField( - "Studium/Ausbildung", - FormFieldType.DYNAMIC_LIST, - children=FORM_FIELDS_HIGHER_EDUCATION, - key="HoehereBildung", - ), - FormField( - "Arbeitserfahrung", - FormFieldType.DYNAMIC_LIST, - children=FORM_FIELDS_WORK_EXPERIENCE, - key="Arbeitserfahrung", - ), - FormField( - "Sprachkenntnisse", - FormFieldType.DYNAMIC_LIST, - children=FORM_FIELDS_LANGUAGES, - key="Sprachkenntnisse", - ), + # FormField( + # "Daten Kontaktperson", + # FormFieldType.GROUP, + # key="Kontaktperson", + # children=FORM_FIELDS_CONTACT_PERSON, + # ), + # FormField( + # "Stammdaten", + # FormFieldType.GROUP, + # key="Stammdaten", + # children=FORM_FIELDS_MASTER_DATA, + # ), + # FormField( + # "Weitere Informationen", + # FormFieldType.GROUP, + # key="WeitereInfos", + # children=FORM_FIELDS_ADDITIONAL_DATA, + # ), + # FormField( + # "Schulbildung", + # FormFieldType.DYNAMIC_LIST, + # children=FORM_FIELDS_SCHOOL, + # key="Schulbildung", + # ), + # FormField( + # "Studium/Ausbildung", + # FormFieldType.DYNAMIC_LIST, + # children=FORM_FIELDS_HIGHER_EDUCATION, + # key="HoehereBildung", + # ), + # FormField( + # "Arbeitserfahrung", + # FormFieldType.DYNAMIC_LIST, + # children=FORM_FIELDS_WORK_EXPERIENCE, + # key="Arbeitserfahrung", + # ), + # FormField( + # "Sprachkenntnisse", + # FormFieldType.DYNAMIC_LIST, + # children=FORM_FIELDS_LANGUAGES, + # key="Sprachkenntnisse", + # ), ] CONFIG_GRUNDERFASSUNG_UNTERNEHMEN: Final[AutoFormConfig] = AutoFormConfig(