update
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
"""Bereits geschaute Filme und ihre Bewertungen.
|
||||
"""Bereits geschaute Filme — das Buch der Abende.
|
||||
|
||||
Der Host trägt ein, was am Abend lief. Bewerten darf jeder, eine Stimme
|
||||
je Sitzung — dieselbe Cookie-Sitzung wie bei den Vorschlägen. Der Name ist
|
||||
freiwillig, ohne ihn zählt die Stimme trotzdem.
|
||||
Der Host trägt ein, was am Abend lief. Jeder Film bekommt eine Seite mit
|
||||
zwei Teilen. Links die Momentaufnahme: eine Wertung je Person, nur Sterne,
|
||||
so wie es direkt nach dem Abspann war. Rechts die Diskussion: Kommentare,
|
||||
auf die man antworten kann, so tief man will.
|
||||
|
||||
Titel und Poster werden mitgespeichert, damit ein Eintrag lesbar bleibt,
|
||||
auch wenn der Film später aus dem Katalog fällt.
|
||||
auch wenn der Film später aus dem Katalog fällt. Szenenbild und Laufzeit
|
||||
kommen aus dem Katalog.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from . import users
|
||||
@@ -39,16 +42,54 @@ CREATE TABLE IF NOT EXISTS ratings (
|
||||
);
|
||||
"""
|
||||
|
||||
# Die Diskussion: ein Kommentar zum Abend, oder mit parent_id eine
|
||||
# Antwort auf einen anderen Kommentar — beliebig tief
|
||||
NOTES = """
|
||||
CREATE TABLE IF NOT EXISTS watched_notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
watched_id INTEGER NOT NULL,
|
||||
parent_id INTEGER,
|
||||
session_id TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
text TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
MAX_STERNE = 5
|
||||
# Manche schreiben eine ganze Rezension — rund 600 Wörter passen hinein
|
||||
MAX_TEXT = 4000
|
||||
MAX_KOMMENTAR = 4000
|
||||
|
||||
# Was das Gästebuch aus dem Katalog dazuholt
|
||||
AUS_KATALOG = ("backdrop_path", "images", "runtime", "genres")
|
||||
|
||||
|
||||
def init():
|
||||
with connect() as conn:
|
||||
conn.executescript(SCHEMA + RATINGS)
|
||||
conn.executescript(SCHEMA + RATINGS + NOTES)
|
||||
# Eine Stimme je Person und Film
|
||||
users.je_person(conn, "ratings", RATINGS, "watched_id")
|
||||
# Herzen an Kommentaren gab es kurz — keiner hat sie vermisst
|
||||
conn.execute("DROP TABLE IF EXISTS watched_hearts")
|
||||
# Kurz hingen Antworten an der Wertung statt aneinander. Daten gab
|
||||
# es dazu nie, die Spalte bekommt nur ihren neuen Namen.
|
||||
spalten = {r["name"] for r in conn.execute("PRAGMA table_info(watched_notes)")}
|
||||
if "rating_id" in spalten:
|
||||
conn.execute("ALTER TABLE watched_notes RENAME COLUMN rating_id TO parent_id")
|
||||
# Eine Weile stand ein Text an der Wertung. Seit die Sterne allein
|
||||
# die Wertung sind, gehört er in die Diskussion — einmalig umziehen.
|
||||
alte = conn.execute(
|
||||
"SELECT * FROM ratings WHERE text IS NOT NULL AND text != ''"
|
||||
).fetchall()
|
||||
for r in alte:
|
||||
conn.execute(
|
||||
"INSERT INTO watched_notes "
|
||||
"(watched_id, parent_id, session_id, user_id, text, created_at) "
|
||||
"VALUES (?, NULL, ?, ?, ?, ?)",
|
||||
(r["watched_id"], r["session_id"], r["user_id"], r["text"], r["created_at"]),
|
||||
)
|
||||
if alte:
|
||||
conn.execute("UPDATE ratings SET text = NULL")
|
||||
# Die Tabelle stand vor der Spalte schon in bestehenden Datenbanken
|
||||
spalten = {r["name"] for r in conn.execute("PRAGMA table_info(watched)")}
|
||||
if "hidden" not in spalten:
|
||||
@@ -66,43 +107,80 @@ def _jetzt():
|
||||
|
||||
|
||||
def alle(session_id):
|
||||
"""Zuletzt geschaut zuerst, mit Schnitt und der eigenen Stimme."""
|
||||
"""Zuletzt geschaut zuerst, mit Schnitt, eigener Stimme und
|
||||
Kommentaren. Die laufende Nummer zählt vom ersten Abend."""
|
||||
felder = ", ".join(f"m.{f}" for f in AUS_KATALOG)
|
||||
with connect() as conn:
|
||||
filme = conn.execute(
|
||||
"SELECT * FROM watched ORDER BY watched_at DESC, id DESC"
|
||||
f"SELECT w.*, {felder} FROM watched w "
|
||||
"LEFT JOIN movies m ON m.id = w.movie_id "
|
||||
"ORDER BY w.watched_at DESC, w.id DESC"
|
||||
).fetchall()
|
||||
stimmen = conn.execute(
|
||||
"SELECT id, watched_id, session_id, user_id, stars, name, text "
|
||||
"SELECT id, watched_id, session_id, user_id, stars, name, created_at "
|
||||
"FROM ratings ORDER BY created_at ASC"
|
||||
).fetchall()
|
||||
notizen = conn.execute(
|
||||
"SELECT n.*, u.name FROM watched_notes n "
|
||||
"LEFT JOIN users u ON u.id = n.user_id "
|
||||
"ORDER BY n.created_at ASC"
|
||||
).fetchall()
|
||||
|
||||
ich = besitzer(session_id)
|
||||
|
||||
def notiz(n):
|
||||
return {
|
||||
"id": n["id"],
|
||||
"text": n["text"],
|
||||
"name": n["name"],
|
||||
"created_at": n["created_at"],
|
||||
"mine": gehoert_mir(n, session_id, ich),
|
||||
"replies": [],
|
||||
}
|
||||
|
||||
# Die Kommentare eines Films als Baum: jede Antwort hängt sich an
|
||||
# ihren Vater, was keinen hat, steht oben. Die Liste kommt nach
|
||||
# Zeit sortiert, also ist der Vater immer schon da.
|
||||
knoten = {n["id"]: notiz(n) for n in notizen}
|
||||
kommentare = {}
|
||||
for n in notizen:
|
||||
k = knoten[n["id"]]
|
||||
vater = knoten.get(n["parent_id"]) if n["parent_id"] is not None else None
|
||||
if vater:
|
||||
vater["replies"].append(k)
|
||||
else:
|
||||
kommentare.setdefault(n["watched_id"], []).append(k)
|
||||
|
||||
je_film = {}
|
||||
for s in stimmen:
|
||||
je_film.setdefault(s["watched_id"], []).append(s)
|
||||
|
||||
out = []
|
||||
for film in filme:
|
||||
for nummer, film in zip(range(len(filme), 0, -1), filme):
|
||||
eigene = je_film.get(film["id"], [])
|
||||
werte = [s["stars"] for s in eigene]
|
||||
meine = next((s for s in eigene if gehoert_mir(s, session_id, ich)), None)
|
||||
daten = dict(film)
|
||||
daten["images"] = json.loads(daten["images"]) if daten["images"] else []
|
||||
daten["genres"] = json.loads(daten["genres"]) if daten["genres"] else []
|
||||
out.append(
|
||||
{
|
||||
**dict(film),
|
||||
**daten,
|
||||
"nummer": nummer,
|
||||
"ratings": [
|
||||
{
|
||||
"id": s["id"],
|
||||
"stars": s["stars"],
|
||||
"name": s["name"],
|
||||
"text": s["text"],
|
||||
"created_at": s["created_at"],
|
||||
"mine": gehoert_mir(s, session_id, ich),
|
||||
}
|
||||
for s in eigene
|
||||
],
|
||||
"notes": kommentare.get(film["id"], []),
|
||||
"average": round(sum(werte) / len(werte), 1) if werte else None,
|
||||
"count": len(werte),
|
||||
"mine": meine["stars"] if meine else None,
|
||||
"mine_text": meine["text"] if meine else None,
|
||||
}
|
||||
)
|
||||
return out
|
||||
@@ -130,7 +208,9 @@ def markieren(movie):
|
||||
|
||||
|
||||
def entfernen(watched_id):
|
||||
"""Samt Wertungen und Kommentaren."""
|
||||
with connect() as conn:
|
||||
conn.execute("DELETE FROM watched_notes WHERE watched_id = ?", (watched_id,))
|
||||
conn.execute("DELETE FROM ratings WHERE watched_id = ?", (watched_id,))
|
||||
return conn.execute(
|
||||
"DELETE FROM watched WHERE id = ?", (watched_id,)
|
||||
@@ -156,15 +236,9 @@ def sichtbarkeit(watched_id, versteckt):
|
||||
).rowcount
|
||||
|
||||
|
||||
def bewerten(watched_id, session_id, stars, name=None, text=None):
|
||||
"""Sterne und ein optionaler Satz dazu.
|
||||
|
||||
Kommt kein Text mit (None), bleibt der bisherige stehen — wer nur die
|
||||
Sterne nachjustiert, verliert seinen Satz nicht. Ein leerer String
|
||||
löscht ihn ausdrücklich.
|
||||
|
||||
Gibt None zurück, wenn der Film gar nicht in der Liste steht.
|
||||
"""
|
||||
def bewerten(watched_id, session_id, stars, name=None):
|
||||
"""Sterne setzen. Gibt None zurück, wenn der Film nicht in der Liste
|
||||
steht."""
|
||||
with connect() as conn:
|
||||
da = conn.execute(
|
||||
"SELECT 1 FROM watched WHERE id = ?", (watched_id,)
|
||||
@@ -172,14 +246,6 @@ def bewerten(watched_id, session_id, stars, name=None, text=None):
|
||||
if not da:
|
||||
return None
|
||||
ich = besitzer(session_id)
|
||||
if text is None:
|
||||
alt = conn.execute(
|
||||
f"SELECT text FROM ratings WHERE watched_id = ? AND {WEM}",
|
||||
(watched_id, ich, ich, session_id),
|
||||
).fetchone()
|
||||
text = alt["text"] if alt else None
|
||||
else:
|
||||
text = text.strip()[:MAX_TEXT] or None
|
||||
# Eine Stimme je Person — die eigene alte fliegt vorher raus
|
||||
conn.execute(
|
||||
f"DELETE FROM ratings WHERE watched_id = ? AND {WEM}",
|
||||
@@ -187,19 +253,11 @@ def bewerten(watched_id, session_id, stars, name=None, text=None):
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO ratings "
|
||||
"(watched_id, session_id, user_id, stars, name, text, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(
|
||||
watched_id,
|
||||
session_id,
|
||||
ich,
|
||||
stars,
|
||||
(name or "").strip()[:40] or None,
|
||||
text,
|
||||
_jetzt(),
|
||||
),
|
||||
"(watched_id, session_id, user_id, stars, name, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(watched_id, session_id, ich, stars, (name or "").strip()[:40] or None, _jetzt()),
|
||||
)
|
||||
return {"watched_id": watched_id, "stars": stars, "text": text}
|
||||
return {"watched_id": watched_id, "stars": stars}
|
||||
|
||||
|
||||
def stimme_loeschen(rating_id):
|
||||
@@ -210,6 +268,70 @@ def stimme_loeschen(rating_id):
|
||||
).rowcount
|
||||
|
||||
|
||||
def anmerken(watched_id, session_id, text, parent_id=None):
|
||||
"""Kommentar zum Abend, oder mit parent_id eine Antwort auf einen
|
||||
anderen. Gibt None zurück, wenn Film oder Vater fehlen."""
|
||||
text = (text or "").strip()[:MAX_KOMMENTAR]
|
||||
if not text:
|
||||
return None
|
||||
with connect() as conn:
|
||||
da = conn.execute(
|
||||
"SELECT 1 FROM watched WHERE id = ?", (watched_id,)
|
||||
).fetchone()
|
||||
if not da:
|
||||
return None
|
||||
if parent_id is not None:
|
||||
passt = conn.execute(
|
||||
"SELECT 1 FROM watched_notes WHERE id = ? AND watched_id = ?",
|
||||
(parent_id, watched_id),
|
||||
).fetchone()
|
||||
if not passt:
|
||||
return None
|
||||
cur = conn.execute(
|
||||
"INSERT INTO watched_notes "
|
||||
"(watched_id, parent_id, session_id, user_id, text, created_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(watched_id, parent_id, session_id, besitzer(session_id), text, _jetzt()),
|
||||
)
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
def _nachkommen(conn, note_id):
|
||||
"""Alle Antworten unter einem Kommentar, beliebig tief."""
|
||||
gefunden, offen = [], [note_id]
|
||||
while offen:
|
||||
kinder = [
|
||||
r["id"]
|
||||
for r in conn.execute(
|
||||
f"SELECT id FROM watched_notes WHERE parent_id IN "
|
||||
f"({','.join('?' * len(offen))})",
|
||||
offen,
|
||||
)
|
||||
]
|
||||
gefunden.extend(kinder)
|
||||
offen = kinder
|
||||
return gefunden
|
||||
|
||||
|
||||
def anmerkung_loeschen(note_id, session_id, host):
|
||||
"""Der Verfasser und der Host dürfen weg damit — samt allem, was
|
||||
darunter hängt. Eine Antwort ohne ihren Anlass ergäbe keinen Sinn."""
|
||||
ich = besitzer(session_id)
|
||||
with connect() as conn:
|
||||
zeile = conn.execute(
|
||||
"SELECT * FROM watched_notes WHERE id = ?", (note_id,)
|
||||
).fetchone()
|
||||
if not zeile:
|
||||
return 0
|
||||
if not host and not gehoert_mir(zeile, session_id, ich):
|
||||
return 0
|
||||
alle_ids = [note_id, *_nachkommen(conn, note_id)]
|
||||
conn.executemany(
|
||||
"DELETE FROM watched_notes WHERE id = ?", [(i,) for i in alle_ids]
|
||||
)
|
||||
return len(alle_ids)
|
||||
|
||||
|
||||
def ids():
|
||||
"""movie_ids der geschauten Filme, für die Detailansicht."""
|
||||
with connect() as conn:
|
||||
|
||||
Reference in New Issue
Block a user