Speiseplan-Kacheln (zukünftige Tage), Footer mit Credit (No tracking, Markus F.J. Busche)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-15 11:18:50 +01:00
parent 5cf75a79bc
commit 073ec27fc7
2 changed files with 109 additions and 6 deletions

30
app.py
View File

@@ -8,7 +8,7 @@ import logging
import os
import threading
import time
from datetime import datetime
from datetime import date, datetime
from zoneinfo import ZoneInfo
from flask import Flask, Response, render_template, request
@@ -29,20 +29,23 @@ app = Flask(__name__, template_folder=_template_dir)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
_log = logging.getLogger(__name__)
# Cache: zuletzt gültige iCal-Bytes; Zeitpunkt der letzten Aktualisierung
# Cache: zuletzt gültige iCal-Bytes; strukturierte Daten (Datum → Gerichte); Zeitpunkt der letzten Aktualisierung
_ical_cache: bytes = empty_ical_bytes()
_by_date_cache: dict[date, list[str]] | None = None
_last_refresh_at: datetime | None = None
_cache_lock = threading.Lock()
_TIMEZONE = ZoneInfo("Europe/Berlin")
_WEEKDAY_NAMES = ("Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag")
def _do_refresh() -> None:
"""Refresh ausführen und bei Erfolg Cache aktualisieren."""
global _ical_cache, _last_refresh_at
global _ical_cache, _by_date_cache, _last_refresh_at
result = refresh_speiseplan(KANTINE_BASE_URL)
if result is not None:
_, ical_bytes = result
by_date, ical_bytes = result
with _cache_lock:
_ical_cache = ical_bytes
_by_date_cache = by_date
_last_refresh_at = datetime.now(_TIMEZONE)
_log.info("Speiseplan-Refresh erfolgreich, Cache aktualisiert.")
else:
@@ -105,17 +108,34 @@ def _format_last_refresh() -> str | None:
return t.strftime("%d.%m.%Y, %H:%M") + " Uhr"
def _upcoming_days() -> list[tuple[str, list[str]]]:
"""Heute und zukünftige Tage aus dem Cache, sortiert, mit formatiertem Datum (z. B. Montag, 03.02.2026)."""
with _cache_lock:
by_date = _by_date_cache
if not by_date:
return []
today = datetime.now(_TIMEZONE).date()
upcoming = [(d, dishes) for d, dishes in by_date.items() if d >= today]
upcoming.sort(key=lambda x: x[0])
return [
(f"{_WEEKDAY_NAMES[d.weekday()]}, {d.strftime('%d.%m.%Y')}", dishes)
for d, dishes in upcoming
]
@app.route("/")
def index():
"""Startseite mit Anleitung zur iCal-Einbettung (Google und andere)."""
"""Startseite mit Anleitung zur iCal-Einbettung (Google und andere) und Speiseplan-Kacheln."""
base = PUBLIC_URL or request.url_root.rstrip("/")
calendar_url = f"{base}/calendar.ics"
last_refresh_str = _format_last_refresh()
upcoming_days = _upcoming_days()
try:
return render_template(
"index.html",
calendar_url=calendar_url,
last_refresh_str=last_refresh_str,
upcoming_days=upcoming_days,
)
except Exception as e:
_log.exception("Template index.html: %s", e)