""" Dashboard-Layout v1 (Nutzer-Lab): Validierung und Standard-Layout. Erlaubte Widget-IDs und Standard-Reihenfolge: widget_catalog.WIDGET_CATALOG """ from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, Field, model_validator from widget_catalog import ALLOWED_WIDGET_IDS, WIDGET_CATALOG # Abwärtskompatibel (Tests importieren weiterhin aus diesem Modul) __all__ = [ "ALLOWED_WIDGET_IDS", "DashboardLayoutPayload", "DashboardWidgetEntry", "coalesce_effective_layout", "default_layout_dict", ] def default_layout_dict() -> dict[str, Any]: return { "version": 1, "widgets": [{"id": e["id"], "enabled": True} for e in WIDGET_CATALOG], } 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()