# %% import dataclasses as dc import datetime import enum import json import re from collections.abc import Sequence from pprint import pprint from typing import Any import babel from pydantic import BaseModel, model_validator from PySide6.QtCore import QDate, Qt # %% gui_rohdaten = { "projekt_name": "Mars Rover", "meilensteine": [ {"titel": "Triebwerk Test", "finanzen__betrag": 5000.0, "finanzen__waehrung": "EUR"}, {"titel": "Software Beta", "finanzen__betrag": 1200.0, "finanzen__waehrung": "EUR"}, ], } class FlatBaseModel(BaseModel): @classmethod def _unflatten_dict(cls, flat_dict: dict) -> dict: """Hilfsmethode: Macht aus {'a__b': 1} wieder {'a': {'b': 1}}""" result = {} for key, value in flat_dict.items(): if "__" in key: parts = key.split("__") aktuell = result for part in parts[:-1]: if part not in aktuell or not isinstance(aktuell[part], dict): aktuell[part] = {} aktuell = aktuell[part] aktuell[parts[-1]] = value else: result[key] = value return result @model_validator(mode="before") @classmethod def __unflatten_input(cls, data: Any) -> Any: """Eingangskontrolle: Verarbeitet flache DB/GUI-Daten zu Pydantic-Strukturen.""" if not isinstance(data, dict): return data # 1. Haupt-Struktur wiederherstellen unflattened = cls._unflatten_dict(data) pprint(unflattened) # 2. Schleife über die Felder, um JSON-Listen von Sub-Modellen zu entpacken for key, value in unflattened.items(): if isinstance(value, str) and value.startswith("[") and value.endswith("]"): try: parsed = json.loads(value) if isinstance(parsed, list): # Falls die Liste Dictionaries enthält, ent-flachen wir diese einzeln unflattened[key] = [ cls._unflatten_dict(item) if isinstance(item, dict) else item for item in parsed ] else: unflattened[key] = parsed except json.JSONDecodeError: pass if isinstance(value, (list)): unflattened[key] = [ cls._unflatten_dict(item) if isinstance(item, dict) else item for item in value ] pprint(unflattened) return unflattened def to_db(self) -> dict[str, Any]: """Ausgang für die DB: Flach, Listen sind JSON-Strings.""" nested = super().model_dump() return self.__flatten_dict(nested, serialize_lists=True) def to_gui(self) -> dict[str, Any]: """Ausgang für die GUI: Flach, aber Listen bleiben Python-Listen für Widgets.""" nested = super().model_dump() return self.__flatten_dict(nested, serialize_lists=False) @classmethod def __flatten_dict( cls, nested_dict: dict, parent_key: str = "", serialize_lists: bool = True ) -> dict: """Rekursiver Alleskönner zum Abflachen von Strukturen.""" items = [] for k, v in nested_dict.items(): new_key = f"{parent_key}__{k}" if parent_key else k if isinstance(v, dict): items.extend(cls.__flatten_dict(v, new_key, serialize_lists).items()) elif isinstance(v, list): processed_list = [] for item in v: if isinstance(item, dict): # WICHTIG: Das Dict in der Liste wird isoliert abgeflacht (ohne parent_key), # da es ein eigenständiges Zeilen-Objekt im DynamicListWidget bleibt! processed_list.append( cls.__flatten_dict(item, serialize_lists=serialize_lists) ) else: processed_list.append(item) if serialize_lists: items.append((new_key, json.dumps(processed_list))) else: items.append((new_key, processed_list)) else: items.append((new_key, v)) return dict(items) # %% class BudgetDetails(BaseModel): betrag: float waehrung: str = "EUR" class Meilenstein(BaseModel): titel: str finanzen: BudgetDetails # <--- Verschachtelung innerhalb der Liste! class ProjektModell(FlatBaseModel): projekt_name: str meilensteine: list[Meilenstein] # <--- Die Liste von Modellen projekt = ProjektModell(**gui_rohdaten) # %% projekt.meilensteine # %% target_dict = { "t1": "test", "t2": "test2", "t3": ["t3-1", "t3-2", "t3-3"], "t4": { "t4-1": "t4-1", "t4-2": "t4-2", }, "t5": [ {"sub1": "sub1-1"}, {"sub2": "sub2-1"}, {"sub3": "sub3-1"}, ], } target_dict = { "Stammdaten": { "Stammdaten_PLZ": "4523", "Stammdaten_anrede_anschrift": "Sehr geehrter Herr", "Stammdaten_anzahl_kinder": {"alter": [3, 7, 10, 14], "anzahl": 4}, "Stammdaten_aufenthaltsort": "Ausland EU/EWR", "Stammdaten_bundesland": None, "Stammdaten_email": "max.mustermann@test.at", "Stammdaten_familienstand": "ledig", "Stammdaten_festnetznummer": "123456789", "Stammdaten_geburtsdatum": datetime.date(1990, 4, 11), "Stammdaten_hausnummer": "12", "Stammdaten_herkunftsland": "AT", "Stammdaten_land": None, "Stammdaten_mobilfunknummer": "123456789", "Stammdaten_name": "Mustermann", "Stammdaten_ort": "Ort in Österreich", "Stammdaten_rueckkehrer": False, "Stammdaten_staatsangehoerigkeit": "AT", "Stammdaten_strasse": "Teststraße", "Stammdaten_titel": "Test", "Stammdaten_vorname": "Max", }, "Schulbildung": [ {"Schulbildung-[1]__SB_abschluss": None, "Schulbildung-[1]__SB_abschlussgrad": None}, {"Schulbildung-[2]__SB_abschluss": None, "Schulbildung-[2]__SB_abschlussgrad": None}, ], } def flatten_for_db( nested_dict: dict, parent_key: str = "", sep: str = ".", ) -> dict[str, Any]: items = [] for k, v in nested_dict.items(): new_key = f"{parent_key}{sep}{k}" if parent_key else k if isinstance(v, dict): items.extend(flatten_for_db(v, new_key, sep=sep).items()) elif isinstance(v, list): items.append((new_key, json.dumps(v))) else: items.append((new_key, v)) return dict(items) def unflatten_from_db( flat_dict: dict, sep: str = ".", ) -> dict[str, Any]: result = {} for key, value in flat_dict.items(): if isinstance(value, str) and (value.startswith("[") and value.endswith("]")): try: value = json.loads(value) except json.JSONDecodeError: pass parts = key.split(sep) aktuell = result for part in parts[:-1]: if part not in aktuell: aktuell[part] = {} aktuell = aktuell[part] aktuell[parts[-1]] = value return result flatted = flatten_for_db(target_dict, sep="__") pprint(flatted) unflatted = unflatten_from_db(flatted, sep="__") print("\n\n") pprint(unflatted) # %% flatted # %% string = """ Metallerzeugung & -bearbeitung; Elektro, Energie, Chemie; IT & Software; Kunststoff, Papier, Textil; Logistik, Verkehr, Transport; Handwerk, Bau, Grüne Berufe; Gesundheit & Pflege; Tourismus & Gastronomie; Handel; Bildung & Soziales; Entwicklung, Planung, Qualität; Administration, Finanzen, Verwaltung; Marketing, Design, Vertrieb; Einkauf, Lager, Wartung; Sonstige; Keine Schwerpunkte, branchenübergreifende Rekrutierung """ parts = string.strip().split(";") [part.strip() for part in parts] # %% DYNAMIC_LIST_KEY_PATTERN = re.compile(r"-\[(\d+)\]") dynamic_content = { "Stammdaten_anzahl_kinder-[0]": {"Stammdaten_anzahl_kinder": "5"}, "Stammdaten_anzahl_kinder-[1]": {"Stammdaten_alter_kinder": "23213"}, "Stammdaten_anzahl_kinder-[2]": {"Stammdaten_alter_kinder": "123123"}, "Stammdaten_anzahl_kinder-[3]": {"Stammdaten_alter_kinder": "123213"}, "Stammdaten_anzahl_kinder-[4]": {"Stammdaten_alter_kinder": "123123"}, "Stammdaten_anzahl_kinder-[5]": {"Stammdaten_alter_kinder": "123123"}, } def find_dynamic_content(content: dict[str, Any]) -> dict[str, Any] | None: found = None for key in dynamic_content.keys(): if DYNAMIC_LIST_KEY_PATTERN.search(key): # found an match: this is dynamic content dictionary print("found") found = dynamic_content break return found # %% new_content = { "Stammdaten": { "Stammdaten_PLZ": "", "Stammdaten_anrede_anschrift": "asdasdas", "Stammdaten_anzahl_kinder": [ { "Stammdaten_anzahl_kinder-[0]": {"Stammdaten_anzahl_kinder": "5"}, "Stammdaten_anzahl_kinder-[1]": {"Stammdaten_alter_kinder": "23213"}, "Stammdaten_anzahl_kinder-[2]": {"Stammdaten_alter_kinder": "123123"}, "Stammdaten_anzahl_kinder-[3]": {"Stammdaten_alter_kinder": "123213"}, "Stammdaten_anzahl_kinder-[4]": {"Stammdaten_alter_kinder": "123123"}, "Stammdaten_anzahl_kinder-[5]": {"Stammdaten_alter_kinder": "123123"}, } ], } } new_content = { "Stammdaten": { "Stammdaten_anzahl_kinder-[0]": {"Stammdaten_anzahl_kinder": "5"}, "Stammdaten_anzahl_kinder-[1]": {"Stammdaten_alter_kinder": None}, "Stammdaten_anzahl_kinder-[2]": {"Stammdaten_alter_kinder": None}, "Stammdaten_anzahl_kinder-[3]": {"Stammdaten_alter_kinder": None}, "Stammdaten_anzahl_kinder-[4]": {"Stammdaten_alter_kinder": None}, "Stammdaten_anzahl_kinder-[5]": {"Stammdaten_alter_kinder": None}, } } # object Stammdaten_Anzahl_Kinder: Stammdaten_anzahl_kinder: int, Stammdaten_alter_kinder: list[int] def get_leafs(data): if isinstance(data, dict): for value in data.values(): yield from get_leafs(value) elif isinstance(data, (list, tuple, set)): for item in data: yield from get_leafs(item) else: yield data def get_leaf_dicts(data): if isinstance(data, dict): has_inner_dicts = False for value in data.values(): for inner_dict in get_leaf_dicts(value): has_inner_dicts = True yield inner_dict if not has_inner_dicts: yield data elif isinstance(data, (list, tuple, set)): for item in data: yield from get_leaf_dicts(item) # %% for x in get_leafs(new_content): print(x) # %% export_dict = {} children_values: list[str] | None = None for idx, data_dict in enumerate(get_leaf_dicts(new_content)): if idx == 0: export_dict.update(data_dict) else: for key in data_dict: if key not in export_dict: children_values = export_dict.setdefault(key, []) assert children_values is not None children_values.append(data_dict[key]) export_dict # %% class Stammdaten_AnzahlKinder(BaseModel): Stammdaten_anzahl_kinder: int | None Stammdaten_alter_kinder: list[int | None] # %% Stammdaten_AnzahlKinder(**export_dict) # %% find_dynamic_content(dynamic_content) # %% @dc.dataclass(slots=True) class CountryList: iso_to_country: dict[str, str] for_dropdown: Sequence[tuple[str, str]] def get_country_list_german() -> CountryList: locale = babel.Locale("de", "DE") countries: list[tuple[str, str]] = [] iso_to_country: dict[str, str] = {} for iso_code, country_name in locale.territories.items(): if len(iso_code) == 2 and not iso_code.isdigit(): countries.append((country_name, iso_code)) iso_to_country[iso_code] = country_name countries.sort(key=lambda x: x[0]) return CountryList( iso_to_country=iso_to_country, for_dropdown=tuple(countries), ) # %% 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) # %% class COUNTRY(enum.IntEnum): DE = 1 FR = 2 CM = 3 class COUNTRY2(enum.Enum): DE = 1 FR = 2 CM = 3 def give_value(t): print(f"Wert ist: {t}") give_value(COUNTRY.DE) give_value(COUNTRY2.DE) # %% COUNTRY(10) # %% COUNTRY.DE # %% t_str = "asd.yxcxc.dfgjj.aasdsdsdsd.sdsdsdsd" splitted = t_str.split(".") part, rest = splitted[0], splitted[1:] part # %% ".".join([part] + rest) # %% class FormFieldType(enum.StrEnum): TEXT = enum.auto() LONGTEXT = enum.auto() DATE = enum.auto() DATETIME = enum.auto() @dc.dataclass(slots=True) class FormField: key: str label: str type: FormFieldType required: bool def __post_init__(self) -> None: self.label = self.label.strip() if not self.label.endswith(":"): self.label += ":" if self.required: self.label += "*" # %% FormField("name", "Projektbeschreibung", FormFieldType.LONGTEXT, required=True) # %% FormField("name", "Projektbeschreibung:", FormFieldType.LONGTEXT, required=True) # %% FormField("name", "Projektbeschreibung", FormFieldType.LONGTEXT, required=False) # %% FormField("name", "Projektbeschreibung:", FormFieldType.LONGTEXT, required=False) # %% addr.export() # %% set_date = QDate.fromString("26.07.2026", "dd.MM.yyyy") # %% Qt.Tet