""" Dashboard-Layout v1 (Nutzer-Lab): Validierung und Standard-Layout. Single Source für erlaubte Widget-IDs (sync mit Frontend widgetRegistry). """ from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field, model_validator ALLOWED_WIDGET_IDS: frozenset[str] = frozenset( { "welcome", "quick_capture", "kpi_board", "body_overview", "activity_overview", } ) def default_layout_dict() -> dict[str, Any]: return { "version": 1, "widgets": [ {"id": "welcome", "enabled": True}, {"id": "quick_capture", "enabled": True}, {"id": "kpi_board", "enabled": True}, {"id": "body_overview", "enabled": True}, {"id": "activity_overview", "enabled": True}, ], } class DashboardWidgetEntry(BaseModel): id: str = Field(min_length=1, max_length=64) enabled: bool = True class DashboardLayoutPayload(BaseModel): version: Literal[1] = 1 widgets: list[DashboardWidgetEntry] = Field(min_length=1, max_length=32) @model_validator(mode="after") def _validate_widgets(self) -> DashboardLayoutPayload: ids = [w.id for w in self.widgets] if len(ids) != len(set(ids)): raise ValueError("Doppelte widget id") bad = [i for i in ids if i not in ALLOWED_WIDGET_IDS] if bad: raise ValueError(f"Unbekannte Widget-IDs: {bad}") if not any(w.enabled for w in self.widgets): raise ValueError("Mindestens ein Widget muss aktiv sein") return self def to_stored_dict(self) -> dict[str, Any]: return { "version": self.version, "widgets": [{"id": w.id, "enabled": w.enabled} for w in self.widgets], } def coalesce_effective_layout(raw: Any) -> tuple[bool, dict[str, Any]]: """ Returns (has_custom, effective_layout). has_custom=True nur wenn DB-Wert vorhanden und gültig (v1). """ if raw is None: return False, default_layout_dict() parsed_obj: Any = raw if isinstance(raw, str): import json try: parsed_obj = json.loads(raw) except json.JSONDecodeError: return False, default_layout_dict() if not isinstance(parsed_obj, dict): return False, default_layout_dict() try: parsed = DashboardLayoutPayload.model_validate( { "version": parsed_obj.get("version", 1), "widgets": parsed_obj.get("widgets", []), } ) return True, parsed.to_stored_dict() except Exception: return False, default_layout_dict()