import re from dataclasses import dataclass from typing import Awaitable, Callable from fastapi import HTTPException from ..constants import FLAG_OFF, FLAG_ON URL_RE = re.compile(r"^https?://[^\s/]+") @dataclass(frozen=True) class Field: key: str label: str kind: str = "text" required: bool = False secret: bool = False placeholder: str = "" default: str = "" choices: tuple[tuple[str, str], ...] = () hint: str = "" transform: Callable[[str], str] | None = None @property def name(self) -> str: return self.key.replace(".", "_") @dataclass(frozen=True) class Action: label: str path: str @dataclass(frozen=True) class Section: slug: str title: str fields: tuple[Field, ...] validate: Callable[[dict], list[str]] test: Callable[[dict], Awaitable[str]] | None = None optional: bool = False enabled_key: str | None = None actions: tuple[Action, ...] = () context_keys: tuple[str, ...] = () notice: str = "" extra_template: str = "" extra_context: Callable[[object], dict] | None = None def section_done(store, section: Section) -> bool: if section.enabled_key: return store.get(section.enabled_key) == FLAG_ON return all(store.get(f.key) for f in section.fields if f.required) def checkbox_default(field: Field) -> str: return field.default or FLAG_ON def collect(form, section: Section) -> dict[str, str | None]: submitted: dict[str, str | None] = {} for field in section.fields: if field.kind == "checkbox": submitted[field.key] = FLAG_ON if form.get(field.name) else FLAG_OFF continue raw = str(form.get(field.name, "")).strip() if field.transform is not None: raw = field.transform(raw) submitted[field.key] = (raw or None) if field.secret else raw return submitted def effective(store, submitted: dict[str, str | None]) -> dict[str, str]: return {key: (value if value is not None else store.get(key) or "") for key, value in submitted.items()} def check(section: Section, values: dict[str, str]) -> list[str]: missing = [f"{f.label} fehlt." for f in section.fields if f.required and not values.get(f.key)] return missing or section.validate(values) def form_values(section: Section, values: dict[str, str]) -> dict: shown = {} for field in section.fields: if field.secret: continue if field.kind == "checkbox": shown[field.name] = values.get(field.key, checkbox_default(field)) == FLAG_ON else: shown[field.name] = values.get(field.key, field.default) return shown def stored_values(store, section: Section) -> dict[str, str]: return {f.key: store.get(f.key, checkbox_default(f) if f.kind == "checkbox" else f.default) or "" for f in section.fields if not f.secret} def secret_flags(store, section: Section) -> dict[str, bool]: return {f.name: store.is_set(f.key) for f in section.fields if f.secret} def find_section(sections: dict[str, Section], slug: str) -> Section: section = sections.get(slug) if section is None: raise HTTPException(404, "Unbekannter Bereich") return section