moderation preview of pictures and audio files
Sonarqube Scanner / Prepare environment (push) Successful in 9s
Sonarqube Scanner / Build and test (push) Successful in 26s
Sonarqube Scanner / SonarQube analysis (push) Successful in 1m11s
Sonarqube Scanner / Deploy to Server (push) Successful in 8s

This commit is contained in:
Florian Zumpe
2026-10-03 15:48:04 +02:00
parent 94793b530d
commit 4b8bdcad5f
7 changed files with 209 additions and 8 deletions
+6 -1
View File
@@ -21,7 +21,12 @@ Die API-Basisadresse ist standardmäßig `https://wegwichtel.de`. Alle Skripte u
`moderate_routes.py` greift direkt auf die bestehende SQLite-Datenbank zu und
benötigt keine API-Verbindung. Ohne Parameter zeigt es alle Strecken mit ID,
Name, Status und Freigabezeitpunkt an:
Name, Status und Freigabezeitpunkt an. Darunter stehen die Stationen tabellarisch
mit ID, Platz, Beschreibung, Erstelldatum und dauerhaften Links zu Bildern und Tonaufnahmen.
Die Anzeige öffnet die Datenbank ausschließlich lesend. Einrichtung und
passwortgeschützter Webzugang sind in
[MODERATION-MEDIEN.md](../../MODERATION-MEDIEN.md) beschrieben; die spätere
Ersteller-Zuordnung ist in [TODO-MODERATION.md](../../TODO-MODERATION.md) vorgemerkt:
```bash
python3 tools/python/moderate_routes.py
+62 -7
View File
@@ -9,12 +9,13 @@ import re
import shlex
import sqlite3
import sys
from urllib.parse import urlsplit
PROJECT_ROOT = Path(__file__).resolve().parents[2]
def database_path(project_root):
"""Liest die .env; bereits gesetzte Umgebungsvariablen haben Vorrang."""
def environment_settings(project_root):
"""Liest die .env, ohne die Prozessumgebung zu verändern."""
settings = {}
env_file = project_root / ".env"
try:
@@ -32,6 +33,12 @@ def database_path(project_root):
settings[key.strip()] = " ".join(shlex.split(value, comments=True))
except ValueError as error:
raise ValueError(f"Ungültige .env-Anführungszeichen in Zeile {number}.") from error
return settings
def database_path(project_root):
"""Bereits gesetzte Umgebungsvariablen haben Vorrang vor der .env."""
settings = environment_settings(project_root)
data_dir = os.environ.get("DATA_DIR", settings.get("DATA_DIR", "")) or "data"
return (project_root / data_dir / "wegwichtel.sqlite").resolve()
@@ -53,15 +60,61 @@ def arguments(argv):
return parser.parse_args(argv)
def list_routes(database):
def review_settings(project_root):
settings = environment_settings(project_root)
url = os.environ.get("MODERATION_REVIEW_URL", settings.get("MODERATION_REVIEW_URL", ""))
parsed = urlsplit(url)
if url and (parsed.scheme != "https" or not parsed.netloc or parsed.fragment or parsed.query
or parsed.username or parsed.password):
raise ValueError("MODERATION_REVIEW_URL muss eine HTTPS-Adresse ohne Zugangsdaten, Query oder Fragment sein.")
return url.rstrip("/")
def table_text(value):
plain = re.sub(r'[\x00-\x1f\x7f-\x9f]', ' ', str(value or ""))
return " ".join(plain.split())
def print_table(headers, rows):
cells = [[table_text(value) for value in row] for row in [headers, *rows]]
widths = [max(len(row[index]) for row in cells) for index in range(len(headers))]
for row in cells:
print(" | ".join(value.ljust(width) for value, width in zip(row, widths)))
def show_pois(database, identifier, review_url, active):
rows = database.execute("""SELECT id, sequence, title, description, created_at, audio_path
FROM pois WHERE route_id=? ORDER BY sequence, id""", (identifier,)).fetchall()
print("Stationen:")
if not rows:
print("Keine Stationen vorhanden.")
return
details = []
for number, poi in enumerate(rows, 1):
access = []
if review_url and active:
base = f"{review_url}/routes/{identifier}/pois/{poi['id']}"
images = database.execute("SELECT id FROM poi_images WHERE poi_id=? ORDER BY sequence, id",
(poi["id"],)).fetchall()
access.extend(f"{base}/pictures/{image['id']}" for image in images)
if poi["audio_path"]:
access.append(f"{base}/audio")
description = poi["description"] or poi["title"]
details.append([poi["id"], number, description, poi["created_at"] or "–", " ; ".join(access) or "–"])
print_table(["ID", "Platz", "Beschreibung", "Erstellt am (UTC)", "Bilder / Ton (Anmeldung)"], details)
def list_routes(database, review_url=""):
rows = database.execute("SELECT id, name, status, activated_at FROM routes ORDER BY id").fetchall()
if not rows:
print("Keine Strecken vorhanden.")
return
print("ID\tStatus\tFreigegeben am (UTC)\tName")
for row in rows:
name = row["name"].replace("\n", " ").replace("\r", " ").replace("\t", " ")
name = table_text(row["name"])
print(f'{row["id"]}\t{row["status"]}\t{row["activated_at"] or "nicht freigegeben"}\t{name}')
show_pois(database, row["id"], review_url, row["status"] == "active")
print()
def moderate(database, identifier, enable):
@@ -84,12 +137,14 @@ def main(argv=None, project_root=None):
args = arguments(argv)
try:
path = database_path(project_root or PROJECT_ROOT)
mode = "ro" if args.enable is None and args.disable is None else "rw"
listing = args.enable is None and args.disable is None
review_url = review_settings(project_root or PROJECT_ROOT) if listing else ""
mode = "ro" if listing else "rw"
database = sqlite3.connect(f"{path.as_uri()}?mode={mode}", uri=True, timeout=10)
try:
database.row_factory = sqlite3.Row
if mode == "ro":
list_routes(database)
if listing:
list_routes(database, review_url)
else:
identifier = args.enable if args.enable is not None else args.disable
moderate(database, identifier, args.enable is not None)