Files
kundenplattform/templating.py
T
adminandClaude Opus 5.5 ccf7b547a7 Org-Ressourcen: Seite Organisation > Ressourcen und Admin-Reiter
Kunden-Admins pflegen Mail, Nextcloud, ownCloud, WebDAV, SMB und Web-Proxy
ihrer Organisation (Formulare aus den Typ-Definitionen des Servers, keine
Zugangsdaten), Wirkung fuer die ganze Org, OEs oder Gruppen. Unterseite
erscheint nur fuer freigeschaltete Organisationen; root pflegt immer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:02:51 +02:00

128 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import hashlib
from pathlib import Path
from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
def _static_version() -> str:
"""
Cache-Busting für /static/*-Dateien (11.09.2026, live gefunden: eine
Änderung an table-tools.js kam beim Nutzer nicht an, weil der Browser
die alte Version unter derselben URL gecacht hatte - keine
Anfrage-URL änderte sich, also gab es für den Browser keinen Grund,
neu zu laden). Hängt vom tatsächlichen Dateiinhalt ab (nicht der
Änderungszeit - robuster gegenüber Deploy-Eigenheiten, die mtimes
verändern können, ohne dass sich der Inhalt ändert), einmalig beim
Prozessstart berechnet.
"""
static_dir = Path(__file__).parent / "static"
hasher = hashlib.sha256()
for datei in sorted(static_dir.glob("*")):
if datei.is_file():
hasher.update(datei.read_bytes())
return hasher.hexdigest()[:12]
templates.env.globals["static_version"] = _static_version()
def datum(wert, mit_zeit: bool = True) -> str:
"""
GUI-Runde 09.10.2026: ISO-Zeitstempel ("2026-10-09T11:20:53.460997+02:00")
als deutsches Datum in Ortszeit anzeigen ("09.10.2026, 11:20"). Leere
Werte werden zu "–", nicht parsebare unverändert durchgereicht.
"""
from datetime import datetime
from zoneinfo import ZoneInfo
if not wert:
return "–"
try:
zeitpunkt = wert if isinstance(wert, datetime) else datetime.fromisoformat(str(wert))
except ValueError:
return str(wert)
if zeitpunkt.tzinfo is not None:
zeitpunkt = zeitpunkt.astimezone(ZoneInfo("Europe/Berlin"))
return zeitpunkt.strftime("%d.%m.%Y, %H:%M" if mit_zeit else "%d.%m.%Y")
templates.env.filters["datum"] = datum
def ring_segmente(teile: list[dict], radius: float = 54, staerke: float = 16) -> list[dict]:
"""
GUI-Runde 09.10.2026 (Punkt 6): Ringdiagramm-Segmente als SVG-Pfade für
das Makro in _diagramme.html - Jinja hat keine Winkelfunktionen. teile:
[{name, anzahl}], Einträge mit 0 entfallen. Mittelpunkt (0, 0), Start
oben, im Uhrzeigersinn. Liefert je Segment name, anzahl, prozent, pfad.
"""
import math
teile = [t for t in teile if t.get("anzahl")]
gesamt = sum(t["anzahl"] for t in teile)
if not gesamt:
return []
aussen, innen = radius, radius - staerke
winkel = -math.pi / 2
ergebnis = []
for t in teile:
anteil = t["anzahl"] / gesamt
ende = winkel + anteil * 2 * math.pi
if anteil >= 0.9999:
# Voller Ring: zwei Halbkreise je Kante, sonst fallen Start und Ende zusammen.
pfad = (
f"M 0 {-aussen} A {aussen} {aussen} 0 1 1 0 {aussen} A {aussen} {aussen} 0 1 1 0 {-aussen} Z "
f"M 0 {-innen} A {innen} {innen} 0 1 0 0 {innen} A {innen} {innen} 0 1 0 0 {-innen} Z"
)
else:
gross = 1 if ende - winkel > math.pi else 0
p = lambda r, w: f"{r * math.cos(w):.2f} {r * math.sin(w):.2f}"
pfad = (
f"M {p(aussen, winkel)} A {aussen} {aussen} 0 {gross} 1 {p(aussen, ende)} "
f"L {p(innen, ende)} A {innen} {innen} 0 {gross} 0 {p(innen, winkel)} Z"
)
ergebnis.append({
"name": t["name"],
"anzahl": t["anzahl"],
"prozent": round(anteil * 100),
"pfad": pfad,
})
winkel = ende
return ergebnis
templates.env.globals["ring_segmente"] = ring_segmente
# Org-Ressourcen (15.10.2026): die Kunden-Unterseite "Ressourcen" erscheint
# nur, wenn der Server sie für die Organisation freigeschaltet hat
# (ORG_RESSOURCEN_NUR_VORAB). Kurz zwischengespeichert, damit nicht jede
# Seite der Organisation-Navigation eine zusätzliche Anfrage auslöst.
_ressourcen_freigabe_cache: dict = {}
def org_ressourcen_freigeschaltet(organization_id) -> bool:
import time
from ressourcen_formular import ressourcen_freigeschaltet
schluessel = str(organization_id)
eintrag = _ressourcen_freigabe_cache.get(schluessel)
if eintrag and time.monotonic() - eintrag[1] < 60:
return eintrag[0]
try:
wert = ressourcen_freigeschaltet(schluessel)
except Exception:
wert = False
_ressourcen_freigabe_cache[schluessel] = (wert, time.monotonic())
return wert
templates.env.globals["org_ressourcen_freigeschaltet"] = org_ressourcen_freigeschaltet