moderation preview of pictures and audio files
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user