generated from dopt-python/py311
adaptive dynamic dropdown and addition "Projektrelevanz"
This commit is contained in:
@@ -71,6 +71,7 @@ setup_logging(enable_stderr=True)
|
||||
|
||||
DEBUG: Final[bool] = True
|
||||
DEBUG_SEARCH_WIDGET: Final[bool] = False
|
||||
DEBUG_NO_DATABASE: Final[bool] = False
|
||||
|
||||
logger = BASE_LOGGER.getChild("wce")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
@@ -78,8 +79,8 @@ 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_auto_form = logger.getChild("get_data_auto_form")
|
||||
logger_get_data_auto_form.setLevel(logging.DEBUG)
|
||||
logger_auto_form = logger.getChild("get_data_auto_form")
|
||||
logger_auto_form.setLevel(logging.DEBUG)
|
||||
|
||||
QSS = """
|
||||
*[styleClass="stempel"] {
|
||||
@@ -377,7 +378,8 @@ class FormFieldType(enum.StrEnum):
|
||||
DROPDOWN = enum.auto()
|
||||
EXTENDED_DROPDOWN = enum.auto()
|
||||
DYNAMIC_LIST = enum.auto()
|
||||
DYNAMIC_DROPDOWN = enum.auto()
|
||||
DYNAMIC_DROPDOWN_NUMERIC = enum.auto()
|
||||
DYNAMIC_DROPDOWN_BOOLEAN = enum.auto()
|
||||
TEXT_SEARCH = enum.auto()
|
||||
CUSTOM = enum.auto()
|
||||
TEXT_DATE = enum.auto()
|
||||
@@ -447,7 +449,7 @@ class FormField:
|
||||
self.dropdown_options = tuple(DropdownOption(op[0], op[1]) for op in options)
|
||||
|
||||
if self.children:
|
||||
self.required = any((child.required for child in self.children))
|
||||
self.required = self.required or any((child.required for child in self.children))
|
||||
for child in self.children:
|
||||
child.parent = self
|
||||
|
||||
@@ -739,9 +741,13 @@ def _build_ui_recursively(
|
||||
}
|
||||
parent_layout.addRow(widget)
|
||||
|
||||
case FormFieldType.DYNAMIC_DROPDOWN:
|
||||
case (
|
||||
FormFieldType.DYNAMIC_DROPDOWN_NUMERIC
|
||||
| FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN
|
||||
):
|
||||
widget = DynamicDropdownWidget(
|
||||
field.children,
|
||||
field.type,
|
||||
field.label,
|
||||
prefix=f"{full_key}",
|
||||
)
|
||||
@@ -1136,7 +1142,7 @@ class Grunderfassung_Unternehmen(FlatBaseModel):
|
||||
Grunderfassung_notiz: str | None
|
||||
|
||||
Partnersuche: Grunderfassung_PartnerSuche
|
||||
Projektrelevanz: Grunderfassung_Projektrelevanz
|
||||
Projektrelevanz: Grunderfassung_ProjektrelevanzStatus
|
||||
Kontaktperson: Grunderfassung_Kontaktperson
|
||||
Stammdaten: Grunderfassung_Stammdaten
|
||||
WeitereInfos: Grunderfassung_WeitereInfos
|
||||
@@ -1154,10 +1160,17 @@ class Grunderfassung_PartnerSuche(BaseModel):
|
||||
kanal_aufmerksamkeit: str | None
|
||||
|
||||
|
||||
class Grunderfassung_Projektrelevanz(BaseModel):
|
||||
class Grunderfassung_ProjektrelevanzStatus(BaseModel):
|
||||
model_config = ConfigDict(str_strip_whitespace=True)
|
||||
|
||||
status: Grunderfassung_ProjektrelevanzStatus_Status
|
||||
|
||||
|
||||
class Grunderfassung_ProjektrelevanzStatus_Status(BaseModel):
|
||||
model_config = ConfigDict(str_strip_whitespace=True)
|
||||
|
||||
relevanz: bool
|
||||
foerderperiode: list[str | None] | None = None
|
||||
|
||||
@field_validator("relevanz", mode="before")
|
||||
@classmethod
|
||||
@@ -1563,6 +1576,8 @@ def search_widgets_by_key(
|
||||
|
||||
|
||||
class AutoForm(QWidget):
|
||||
"""a widget, which is managed by a code-defined field definition collection"""
|
||||
|
||||
save_clicked_form = Signal() # formular saved (data changed for front page)
|
||||
|
||||
def __init__(
|
||||
@@ -1609,6 +1624,11 @@ class AutoForm(QWidget):
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
if DEBUG:
|
||||
separator1 = QFrame()
|
||||
separator1.setFrameShape(QFrame.Shape.HLine)
|
||||
separator1.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.main_layout.addWidget(separator1)
|
||||
|
||||
self.test_button = QPushButton("Initialisiere Laden")
|
||||
self.test_button.clicked.connect(self.load_data)
|
||||
self.test_button.setFixedHeight(50)
|
||||
@@ -1632,6 +1652,12 @@ class AutoForm(QWidget):
|
||||
button_db_index.clicked.connect(self._set_db_index)
|
||||
self.main_layout.addWidget(button_db_index)
|
||||
|
||||
separator2 = QFrame()
|
||||
separator2.setFrameShape(QFrame.Shape.HLine)
|
||||
separator2.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
self.main_layout.addWidget(separator2)
|
||||
self.main_layout.addSpacing(5)
|
||||
|
||||
self.main_layout.addSpacing(10)
|
||||
|
||||
self.top_level_form_layout = QFormLayout()
|
||||
@@ -1697,29 +1723,32 @@ class AutoForm(QWidget):
|
||||
self,
|
||||
lookup_id: int | None = None,
|
||||
) -> None:
|
||||
# TODO change logic to database backend
|
||||
logger_get_data.info(">>>> LOAD CLICKED")
|
||||
if DEBUG_NO_DATABASE:
|
||||
return
|
||||
|
||||
logger_auto_form.info(">>>> LOAD CLICKED")
|
||||
if lookup_id is None or lookup_id == 0:
|
||||
lookup_id = self.current_id
|
||||
self.reset_form()
|
||||
|
||||
logger_get_data_auto_form.debug("Lookup ID: %d", lookup_id)
|
||||
logger_auto_form.debug("Lookup ID: %d", lookup_id)
|
||||
|
||||
if lookup_id > 0:
|
||||
logger_get_data_auto_form.debug("Load from DB:")
|
||||
logger_auto_form.debug("Load from DB:")
|
||||
loaded_data = self.cfg.data_get(lookup_id)
|
||||
# TODO remove loading from file (obsolete)
|
||||
else:
|
||||
logger_get_data_auto_form.debug("Load from pickled object:")
|
||||
logger_auto_form.debug("Load from pickled object:")
|
||||
loaded_data = load_pydantic_model_dict_db()
|
||||
|
||||
logger_get_data_auto_form.debug(
|
||||
logger_auto_form.debug(
|
||||
"Loaded data dict:\n%s Passing to Pydantic...", pformat(loaded_data)
|
||||
)
|
||||
model = Grunderfassung_Unternehmen(**loaded_data)
|
||||
logger_get_data_auto_form.debug("Loaded to Pydantic.")
|
||||
logger_get_data_auto_form.debug("Convert to GUI structure...")
|
||||
logger_auto_form.debug("Loaded to Pydantic.")
|
||||
logger_auto_form.debug("Convert to GUI structure...")
|
||||
form_data = model.to_gui()
|
||||
logger_get_data_auto_form.debug("Set form data...")
|
||||
logger_auto_form.debug("Set form data...")
|
||||
# logger_get_data_auto_form.debug("Form data:\n%s", pformat(form_data))
|
||||
self.set_form_data(form_data)
|
||||
self.current_id = lookup_id
|
||||
@@ -1739,17 +1768,18 @@ class AutoForm(QWidget):
|
||||
return
|
||||
|
||||
# no errors: data can be saved
|
||||
logger.info("Erfolg! Alle Daten sind valide.")
|
||||
logger.info("Get form data call...")
|
||||
logger_auto_form.info("Erfolg! Alle Daten sind valide.")
|
||||
logger_auto_form.info("Get form data call...")
|
||||
form_data = self.get_form_data()
|
||||
|
||||
logger.debug("\n\n------------>>>>>>>>> Get form data\n%s", pformat(form_data))
|
||||
logger.debug("------------>>>>>>>>> Call Pydantic")
|
||||
logger_auto_form.debug(
|
||||
"\n\n------------>>>>>>>>> Get form data\n%s", pformat(form_data)
|
||||
)
|
||||
|
||||
try:
|
||||
logger_auto_form.debug("------------>>>>>>>>> Call Pydantic")
|
||||
validated_data = self.cfg.model(**form_data)
|
||||
# # TODO add user?
|
||||
# logger.debug("%s", pformat(validated_data.model_dump()))
|
||||
except ValidationError as e:
|
||||
# catch errors and show them in GUI
|
||||
error_texts = []
|
||||
@@ -1773,23 +1803,25 @@ class AutoForm(QWidget):
|
||||
# tell user what went wrong
|
||||
QMessageBox.warning(self, "Eingabefehler", "\n".join(error_texts))
|
||||
else:
|
||||
if DEBUG_NO_DATABASE:
|
||||
return
|
||||
# !! this code is only called if the 'try' block was successful
|
||||
# save_pydantic_model_dict_db(validated_data)
|
||||
# TODO save data to database
|
||||
db_data = validated_data.to_db(exclude=self.cfg.ignored_keys)
|
||||
logger.debug(
|
||||
logger_auto_form.debug(
|
||||
("Form data with 'exlude' (must be saved in the database):\n%s"),
|
||||
pformat(db_data),
|
||||
)
|
||||
|
||||
if self.current_id < 0:
|
||||
logger.debug("Insert triggered")
|
||||
logger_auto_form.debug("Insert triggered")
|
||||
self.cfg.data_insert(db_data)
|
||||
else:
|
||||
logger.debug("Update triggered")
|
||||
logger_auto_form.debug("Update triggered")
|
||||
self.cfg.data_update(self.current_id, db_data)
|
||||
|
||||
logger_get_data.info("Data saved successfully")
|
||||
logger_auto_form.info("Data saved successfully")
|
||||
self.save_clicked_form.emit()
|
||||
self.reset_form()
|
||||
finally:
|
||||
@@ -1805,8 +1837,8 @@ class AutoForm(QWidget):
|
||||
|
||||
def get_form_data(self) -> dict[str, Any]:
|
||||
form_data = get_form_data(self.widget_registry)
|
||||
logger_get_data.debug("\n\n>>>>>>> [AutoForm] Call get form data:")
|
||||
logger_get_data.debug("Form Data:\n%s", pformat(form_data))
|
||||
logger_auto_form.debug("\n\n>>>>>>> [AutoForm] Call get form data:")
|
||||
logger_auto_form.debug("Form Data:\n%s", pformat(form_data))
|
||||
|
||||
return form_data
|
||||
|
||||
@@ -1819,7 +1851,7 @@ class AutoForm(QWidget):
|
||||
|
||||
class DynamicListWidget(QWidget):
|
||||
"""
|
||||
A Widget, which can generate and manage an arbitrary number of sub forms.
|
||||
a widget, which can generate and manage an arbitrary number of sub forms.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -1899,15 +1931,12 @@ class DynamicListWidget(QWidget):
|
||||
self.update_sub_forms()
|
||||
|
||||
def update_sub_forms(self):
|
||||
# TODO: check if needed
|
||||
update_sub_forms(
|
||||
self.widget_registry,
|
||||
sub_forms=self.sub_forms,
|
||||
base_label=self.base_label,
|
||||
)
|
||||
|
||||
# pprint_registry(self.widget_registry)
|
||||
|
||||
def reset_form(self) -> None:
|
||||
while self.sub_forms:
|
||||
self.remove_entry(self.sub_forms[0])
|
||||
@@ -1923,6 +1952,7 @@ class DynamicListWidget(QWidget):
|
||||
return errors
|
||||
|
||||
def get_form_data(self) -> list[dict[str, Any]]:
|
||||
# TODO code cleansing
|
||||
# raw_data = get_form_data(self.widget_registry)
|
||||
# form_data = get_form_data(self.widget_registry)
|
||||
# logger_get_data.debug(
|
||||
@@ -1975,15 +2005,21 @@ 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):
|
||||
"""
|
||||
A Widget, which can generate and manage an arbitrary number of sub forms with additional
|
||||
information on a combobox selection (integer in combobox).
|
||||
information on a combobox selection (combobox).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
form_fields: Sequence[FormField],
|
||||
type: FormFieldType,
|
||||
label_add_info: str = "Eintrag",
|
||||
prefix: str = "",
|
||||
):
|
||||
@@ -2004,7 +2040,7 @@ class DynamicDropdownWidget(QWidget):
|
||||
"child, which is a single field definition"
|
||||
)
|
||||
)
|
||||
|
||||
self.type = type
|
||||
self.assigned_form_field = assigned_form_fields[0]
|
||||
|
||||
self.label_add_info = label_add_info
|
||||
@@ -2035,27 +2071,38 @@ class DynamicDropdownWidget(QWidget):
|
||||
|
||||
self.sub_forms: list[SubForm] = []
|
||||
|
||||
self.dropdown_widget.currentTextChanged.connect(self.on_anzahl_changed)
|
||||
self.dropdown_widget.currentTextChanged.connect(self._selection_changed_changed)
|
||||
|
||||
def on_anzahl_changed(
|
||||
def _selection_changed_changed(
|
||||
self,
|
||||
text: str,
|
||||
) -> None:
|
||||
target_count: int
|
||||
if text == DROPDOWN_DEFAULT:
|
||||
target_count = 0
|
||||
else:
|
||||
target_count = int(text)
|
||||
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)
|
||||
|
||||
if target_count > current_count:
|
||||
differenz = target_count - current_count
|
||||
for _ in range(differenz):
|
||||
case FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN:
|
||||
if text == "ja":
|
||||
value = 1
|
||||
else:
|
||||
value = 0
|
||||
|
||||
case _:
|
||||
raise ValueError("Undefined DynamicDropdownWidget type")
|
||||
|
||||
if value > current_count:
|
||||
diff = value - current_count
|
||||
for _ in range(diff):
|
||||
self._add_row()
|
||||
|
||||
elif target_count < current_count:
|
||||
differenz = current_count - target_count
|
||||
for _ in range(differenz):
|
||||
elif value < current_count:
|
||||
diff = current_count - value
|
||||
for _ in range(diff):
|
||||
self._remove_row()
|
||||
|
||||
def _add_row(self) -> None:
|
||||
@@ -2067,7 +2114,8 @@ class DynamicDropdownWidget(QWidget):
|
||||
sub_form = SubForm(container, prefix_parent=self.prefix, index=number_form)
|
||||
|
||||
form_field_def = copy.copy(self.assigned_form_field)
|
||||
form_field_def.label = form_field_def.enhanced_label(f"{number_form}")
|
||||
if self.type is FormFieldType.DYNAMIC_DROPDOWN_NUMERIC:
|
||||
form_field_def.label = form_field_def.enhanced_label(f"{number_form}")
|
||||
|
||||
sub_form.full_keys = _build_ui_recursively(
|
||||
schema=[form_field_def],
|
||||
@@ -2265,21 +2313,6 @@ class NewEntrySelect_view(QWidget):
|
||||
layout.addWidget(btn_company)
|
||||
layout.addWidget(btn_person)
|
||||
|
||||
# self.info_label = QLabel("Zusatzinfos...")
|
||||
# self.info_label.setWordWrap(True)
|
||||
# self.info_label.setStyleSheet("font-size: 16px; color: #475569; margin-top: 10px;")
|
||||
# layout.addWidget(self.info_label)
|
||||
|
||||
# def update_content(self, data):
|
||||
# # Diese Methode füllt die Seite mit den echten Daten
|
||||
# self.title_label.setText(f"Details für: {data.get('c1', 'Unbekannt')}")
|
||||
# self.info_label.setText(
|
||||
# f"Beschreibung: {data.get('c2')}\n\n"
|
||||
# f"Abteilung: {data.get('c3', 'Keine Angabe')}\n"
|
||||
# f"Status: {data.get('c4')}\n"
|
||||
# f"Datum: {data.get('date')}"
|
||||
# )
|
||||
|
||||
|
||||
CUSTOM_WIDGETS: Final[dict[str, type[CustomWidget]]] = {
|
||||
"grunderfassung_suche": Grunderfassung_SuchWidget,
|
||||
@@ -2640,7 +2673,7 @@ FORM_FIELDS_MASTER_DATA = [
|
||||
),
|
||||
FormField(
|
||||
"Anzahl Kinder",
|
||||
FormFieldType.DYNAMIC_DROPDOWN,
|
||||
FormFieldType.DYNAMIC_DROPDOWN_NUMERIC,
|
||||
required=False,
|
||||
tooltip="* Wichtig zu erfragen aufgrund Lebensunterhaltssicherung",
|
||||
key="anzahl_kinder",
|
||||
@@ -3027,10 +3060,22 @@ FORM_FIELDS = [
|
||||
children=[
|
||||
FormField(
|
||||
"Projektrelevanz",
|
||||
FormFieldType.DROPDOWN,
|
||||
key="relevanz",
|
||||
required=True,
|
||||
options=[("ja", None), ("nein", None)],
|
||||
FormFieldType.DYNAMIC_DROPDOWN_BOOLEAN,
|
||||
key="status",
|
||||
children=[
|
||||
FormField(
|
||||
"Relevanz",
|
||||
FormFieldType.DROPDOWN,
|
||||
required=True,
|
||||
options=[("ja", None), ("nein", None)],
|
||||
key="relevanz",
|
||||
children=[
|
||||
FormField(
|
||||
"Förderperiode", FormFieldType.TEXT, key="foerderperiode"
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -3235,16 +3280,23 @@ class MainWindow(QMainWindow):
|
||||
new_btn.setFixedWidth(100)
|
||||
new_btn.setFixedHeight(40)
|
||||
new_btn.clicked.connect(self.show_new_entry_select)
|
||||
vert_layout.addWidget(new_btn)
|
||||
|
||||
# TODO remove
|
||||
update_btn = QPushButton("UPDATE")
|
||||
update_btn.setFixedWidth(100)
|
||||
update_btn.setFixedHeight(40)
|
||||
update_btn.clicked.connect(self.update_grid)
|
||||
clear_btn = QPushButton("CLEAR")
|
||||
clear_btn.setFixedWidth(100)
|
||||
clear_btn.setFixedHeight(40)
|
||||
clear_btn.clicked.connect(self._clear_layout)
|
||||
if DEBUG:
|
||||
update_btn = QPushButton("UPDATE")
|
||||
update_btn.setFixedWidth(100)
|
||||
update_btn.setFixedHeight(40)
|
||||
update_btn.clicked.connect(self.update_grid)
|
||||
clear_btn = QPushButton("CLEAR")
|
||||
clear_btn.setFixedWidth(100)
|
||||
clear_btn.setFixedHeight(40)
|
||||
clear_btn.clicked.connect(self._clear_layout)
|
||||
vert_layout.addWidget(update_btn)
|
||||
vert_layout.addWidget(clear_btn)
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.Shape.HLine)
|
||||
separator.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
vert_layout.addWidget(separator)
|
||||
|
||||
# container for table or grid
|
||||
container = QWidget()
|
||||
@@ -3257,9 +3309,6 @@ class MainWindow(QMainWindow):
|
||||
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
scroll_area.setWidget(container)
|
||||
|
||||
vert_layout.addWidget(new_btn)
|
||||
vert_layout.addWidget(update_btn)
|
||||
vert_layout.addWidget(clear_btn)
|
||||
vert_layout.addSpacing(20)
|
||||
vert_layout.addWidget(scroll_area)
|
||||
# springs left and right of container to center the it
|
||||
|
||||
@@ -360,12 +360,11 @@ grunderfassung_unternehmen: sql.Table = Table(
|
||||
Column("Kontaktperson__KP_name_partner", sql.Text, nullable=True),
|
||||
Column("Kontaktperson__KP_titel", sql.Text, nullable=True),
|
||||
Column("Kontaktperson__KP_vorname", sql.Text, nullable=True),
|
||||
# Column("Metadaten_aktualisierung", sql.Text, nullable=True),
|
||||
# Column("Metadaten_erstellung", sql.Text, nullable=True),
|
||||
Column("Partnersuche__kanal_aufmerksamkeit", sql.Text, nullable=True),
|
||||
Column("Partnersuche__person_suche", sql.Integer, nullable=True),
|
||||
Column("Partnersuche__un_suche", sql.Integer, nullable=True),
|
||||
Column("Projektrelevanz__relevanz", sql.Boolean, nullable=True),
|
||||
Column("Projektrelevanz__status__relevanz", sql.Boolean, nullable=True),
|
||||
Column("Projektrelevanz__status__foerderperiode", sql.Text, nullable=True),
|
||||
Column("Schulbildung", sql.Text, nullable=True),
|
||||
Column("Sprachkenntnisse", sql.Text, nullable=True),
|
||||
Column("Stammdaten__PLZ", sql.Text, nullable=True),
|
||||
|
||||
Reference in New Issue
Block a user