update
This commit is contained in:
@@ -13,6 +13,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from . import (
|
||||
backup,
|
||||
punkte,
|
||||
db,
|
||||
events,
|
||||
features,
|
||||
@@ -364,8 +365,9 @@ def mitglied(sid):
|
||||
return ich
|
||||
|
||||
|
||||
# Der letzte Dreh, damit eine Seite mitten in der Animation nachziehen kann
|
||||
letzter_dreh = {"ergebnis": None, "start": 0.0}
|
||||
# Der letzte Dreh, damit eine Seite mitten in der Animation nachziehen
|
||||
# kann — samt Anwesenheit der Runde, die beim Eintragen am Abend landet
|
||||
letzter_dreh = {"ergebnis": None, "start": 0.0, "teilnehmer": []}
|
||||
|
||||
|
||||
@app.get("/api/events")
|
||||
@@ -394,6 +396,7 @@ def drehen():
|
||||
|
||||
letzter_dreh["ergebnis"] = ergebnis
|
||||
letzter_dreh["start"] = time.monotonic()
|
||||
letzter_dreh["teilnehmer"] = suggestions.teilnehmer()
|
||||
events.senden("spin", ergebnis)
|
||||
return {"started": True, **ergebnis}
|
||||
|
||||
@@ -418,6 +421,7 @@ def liste_vorschlaege(response: Response, filmabend_sid: str | None = Cookie(Non
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
eintraege = suggestions.alle()
|
||||
ich = users.besitzer(sid)
|
||||
dabei = suggestions.dabei_alle()
|
||||
return {
|
||||
"items": [
|
||||
{k: v for k, v in e.items() if k not in ("session_id", "user_id")}
|
||||
@@ -428,9 +432,22 @@ def liste_vorschlaege(response: Response, filmabend_sid: str | None = Cookie(Non
|
||||
(e["id"] for e in eintraege if users.gehoert_mir(e, sid, ich)),
|
||||
None,
|
||||
),
|
||||
# Wer ohne Vorschlag dabei ist
|
||||
"dabei": [d["name"] or "Anonym" for d in dabei],
|
||||
"dabei_mine": any(users.gehoert_mir(d, sid, ich) for d in dabei),
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/dabei")
|
||||
def dabei(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
"""Dabei ohne Vorschlag — an oder wieder aus."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
mitglied(sid)
|
||||
steht = suggestions.dabei_umschalten(sid)
|
||||
events.senden("suggestions")
|
||||
return {"dabei": steht}
|
||||
|
||||
|
||||
@app.post("/api/suggestions")
|
||||
async def vorschlagen(
|
||||
response: Response,
|
||||
@@ -595,8 +612,10 @@ def vergessen(
|
||||
@app.get("/api/users")
|
||||
def runde(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
staende = punkte.alle()
|
||||
leer = punkte.stand(0)
|
||||
return {
|
||||
"items": users.alle(),
|
||||
"items": [{**u, **staende.get(u["id"], leer)} for u in users.alle()],
|
||||
"mine": users.aktuell(sid),
|
||||
"host": users.ist_host(sid),
|
||||
}
|
||||
@@ -739,17 +758,45 @@ def name_waehlen(
|
||||
@app.get("/api/watched")
|
||||
def liste_gesehen(response: Response, filmabend_sid: str | None = Cookie(None)):
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
return watched.alle(sid)
|
||||
return watched.alle(sid, users.ist_host(sid))
|
||||
|
||||
|
||||
@app.post("/api/watched")
|
||||
async def als_gesehen(movie_id: int = Body(..., embed=True)):
|
||||
"""Der Host trägt ein, was am Abend lief."""
|
||||
"""Der Host trägt ein, was am Abend lief. Ist es der gezogene Film,
|
||||
hängt die Runde vom Rad als Anwesenheit am Abend."""
|
||||
eintrag = watched.markieren(await film_holen(movie_id))
|
||||
gezogen = letzter_dreh["ergebnis"]
|
||||
if gezogen and gezogen["gewinner"]["movie_id"] == movie_id and letzter_dreh["teilnehmer"]:
|
||||
watched.dabei_setzen(eintrag["id"], letzter_dreh["teilnehmer"])
|
||||
events.senden("watched")
|
||||
return eintrag
|
||||
|
||||
|
||||
@app.post("/api/watched/{watched_id}/dabei")
|
||||
def dabei_aendern(watched_id: int, user_ids: list[int] = Body(..., embed=True)):
|
||||
"""Der Host berichtigt, wer dabei war — etwa wer spät kam."""
|
||||
watched.dabei_setzen(watched_id, user_ids)
|
||||
events.senden("watched")
|
||||
return {"dabei": user_ids}
|
||||
|
||||
|
||||
@app.post("/api/watched/hearts")
|
||||
def herz_setzen(
|
||||
response: Response,
|
||||
note_id: int = Body(..., embed=True),
|
||||
filmabend_sid: str | None = Cookie(None),
|
||||
):
|
||||
"""Ein Klick setzt das Herz, der nächste nimmt es zurück."""
|
||||
sid = sitzung(response, filmabend_sid)
|
||||
mitglied(sid)
|
||||
steht = watched.herz(sid, note_id)
|
||||
if steht is None:
|
||||
raise HTTPException(status_code=404, detail="Kommentar nicht gefunden")
|
||||
events.senden("watched")
|
||||
return {"hearted": steht}
|
||||
|
||||
|
||||
@app.delete("/api/watched/{watched_id}")
|
||||
def gesehen_entfernen(watched_id: int):
|
||||
entfernt = watched.entfernen(watched_id)
|
||||
|
||||
75
backend/app/punkte.py
Normal file
75
backend/app/punkte.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Erfahrung und Level — berechnet, nicht gebucht.
|
||||
|
||||
Punkte sind eine Formel über dem, was gerade in der Datenbank steht:
|
||||
wer dabei war, bewertet, kommentiert, geantwortet und ein Herz gegeben
|
||||
hat. Nimmt jemand seine Wertung zurück, sind die Punkte weg. Es gibt
|
||||
kein Konto, das man pflegen oder austricksen könnte.
|
||||
|
||||
Je Film zählt jede Art nur einmal. Level nach Dreieckszahlen: Level L
|
||||
ist erreicht, sobald L·(L−1)/2 Punkte da sind — anfangs schnell, dann
|
||||
stetig langsamer, aber immer in Reichweite.
|
||||
"""
|
||||
|
||||
from math import isqrt
|
||||
|
||||
from .db import connect
|
||||
|
||||
SCHAUEN = 10
|
||||
BEWERTEN = 3
|
||||
KOMMENTAR = 5
|
||||
ANTWORT = 2
|
||||
HERZ = 1
|
||||
|
||||
ZAEHLUNGEN = (
|
||||
(SCHAUEN, "SELECT user_id, COUNT(*) AS n FROM watched_dabei GROUP BY user_id"),
|
||||
(
|
||||
BEWERTEN,
|
||||
"SELECT user_id, COUNT(DISTINCT watched_id) AS n FROM ratings "
|
||||
"WHERE user_id IS NOT NULL GROUP BY user_id",
|
||||
),
|
||||
(
|
||||
KOMMENTAR,
|
||||
"SELECT user_id, COUNT(DISTINCT watched_id) AS n FROM watched_notes "
|
||||
"WHERE user_id IS NOT NULL AND parent_id IS NULL GROUP BY user_id",
|
||||
),
|
||||
(
|
||||
ANTWORT,
|
||||
"SELECT user_id, COUNT(DISTINCT watched_id) AS n FROM watched_notes "
|
||||
"WHERE user_id IS NOT NULL AND parent_id IS NOT NULL GROUP BY user_id",
|
||||
),
|
||||
(
|
||||
HERZ,
|
||||
"SELECT h.user_id, COUNT(DISTINCT n.watched_id) AS n FROM watched_hearts h "
|
||||
"JOIN watched_notes n ON n.id = h.note_id "
|
||||
"WHERE h.user_id IS NOT NULL GROUP BY h.user_id",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def level(punkte):
|
||||
"""Größtes L mit L·(L−1)/2 ≤ Punkte. Null Punkte sind Level 1."""
|
||||
stufe = (1 + isqrt(1 + 8 * punkte)) // 2
|
||||
while stufe * (stufe - 1) // 2 > punkte:
|
||||
stufe -= 1
|
||||
return max(1, stufe)
|
||||
|
||||
|
||||
def stand(punkte):
|
||||
stufe = level(punkte)
|
||||
return {
|
||||
"punkte": punkte,
|
||||
"level": stufe,
|
||||
# Wo das aktuelle Level begann und wo das nächste anfängt — für den Balken
|
||||
"von": stufe * (stufe - 1) // 2,
|
||||
"bis": stufe * (stufe + 1) // 2,
|
||||
}
|
||||
|
||||
|
||||
def alle():
|
||||
"""Punktestand je user_id."""
|
||||
summen = {}
|
||||
with connect() as conn:
|
||||
for wert, sql in ZAEHLUNGEN:
|
||||
for row in conn.execute(sql):
|
||||
summen[row["user_id"]] = summen.get(row["user_id"], 0) + wert * row["n"]
|
||||
return {user_id: stand(p) for user_id, p in summen.items()}
|
||||
@@ -32,6 +32,17 @@ CREATE TABLE IF NOT EXISTS suggestions (
|
||||
);
|
||||
"""
|
||||
|
||||
# Wer am Abend da ist, aber nichts vorschlagen will. Zusammen mit den
|
||||
# Vorschlagenden ergibt das die Anwesenheit — die zählt beim Dreh.
|
||||
DABEI = """
|
||||
CREATE TABLE IF NOT EXISTS dabei (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
COOKIE = "filmabend_sid"
|
||||
|
||||
# So lange läuft die Animation im Browser. Der Server merkt sich das
|
||||
@@ -54,9 +65,10 @@ BRUCH_ANTEIL = 0.5
|
||||
|
||||
def init():
|
||||
with connect() as conn:
|
||||
conn.executescript(SCHEMA)
|
||||
conn.executescript(SCHEMA + DABEI)
|
||||
# Ein Vorschlag je Person
|
||||
users.je_person(conn, "suggestions", SCHEMA)
|
||||
users.je_person(conn, "dabei", DABEI)
|
||||
|
||||
|
||||
def neue_session():
|
||||
@@ -196,4 +208,40 @@ def loeschen(session_id):
|
||||
def alles_loeschen():
|
||||
"""Host setzt die Runde zurück."""
|
||||
with connect() as conn:
|
||||
conn.execute("DELETE FROM dabei")
|
||||
return conn.execute("DELETE FROM suggestions").rowcount
|
||||
|
||||
|
||||
def dabei_umschalten(session_id):
|
||||
"""Dabei ohne Vorschlag — der zweite Klick nimmt es zurück.
|
||||
Gibt zurück, ob es jetzt gesetzt ist."""
|
||||
ich = besitzer(session_id)
|
||||
with connect() as conn:
|
||||
weg = conn.execute(
|
||||
f"DELETE FROM dabei WHERE {WEM}", (ich, ich, session_id)
|
||||
).rowcount
|
||||
if weg:
|
||||
return False
|
||||
conn.execute(
|
||||
"INSERT INTO dabei (session_id, user_id, created_at) VALUES (?, ?, ?)",
|
||||
(session_id, ich, datetime.now(timezone.utc).isoformat(timespec="seconds")),
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def dabei_alle():
|
||||
"""Wer sich ohne Vorschlag eingetragen hat — Namen und Kennungen."""
|
||||
with connect() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT d.*, u.name FROM dabei d LEFT JOIN users u ON u.id = d.user_id "
|
||||
"ORDER BY d.created_at ASC"
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def teilnehmer():
|
||||
"""user_ids aller Anwesenden: Vorschlagende und Eingetragene."""
|
||||
with connect() as conn:
|
||||
ids = {r["user_id"] for r in conn.execute("SELECT user_id FROM suggestions")}
|
||||
ids |= {r["user_id"] for r in conn.execute("SELECT user_id FROM dabei")}
|
||||
return sorted(i for i in ids if i is not None)
|
||||
|
||||
@@ -56,6 +56,28 @@ CREATE TABLE IF NOT EXISTS watched_notes (
|
||||
);
|
||||
"""
|
||||
|
||||
# Ein Herz je Person und Kommentar: "sehe ich auch so". Alle sehen die
|
||||
# Zahl, wer es gegeben hat, sieht nur der Host.
|
||||
HEARTS = """
|
||||
CREATE TABLE IF NOT EXISTS watched_hearts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
note_id INTEGER NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
user_id INTEGER,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
# Wer am Abend dabei war — kommt aus der Runde am Rad, sobald der Host
|
||||
# den gezogenen Film einträgt. Daraus rechnen sich die Punkte.
|
||||
DABEI = """
|
||||
CREATE TABLE IF NOT EXISTS watched_dabei (
|
||||
watched_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (watched_id, user_id)
|
||||
);
|
||||
"""
|
||||
|
||||
MAX_STERNE = 5
|
||||
# Manche schreiben eine ganze Rezension — rund 600 Wörter passen hinein
|
||||
MAX_KOMMENTAR = 4000
|
||||
@@ -66,11 +88,10 @@ AUS_KATALOG = ("backdrop_path", "images", "runtime", "genres")
|
||||
|
||||
def init():
|
||||
with connect() as conn:
|
||||
conn.executescript(SCHEMA + RATINGS + NOTES)
|
||||
# Eine Stimme je Person und Film
|
||||
conn.executescript(SCHEMA + RATINGS + NOTES + HEARTS + DABEI)
|
||||
# Eine Stimme je Person und Film, ein Herz je Person und Kommentar
|
||||
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")
|
||||
users.je_person(conn, "watched_hearts", HEARTS, "note_id")
|
||||
# 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)")}
|
||||
@@ -106,9 +127,12 @@ def _jetzt():
|
||||
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def alle(session_id):
|
||||
"""Zuletzt geschaut zuerst, mit Schnitt, eigener Stimme und
|
||||
Kommentaren. Die laufende Nummer zählt vom ersten Abend."""
|
||||
def alle(session_id, host=False):
|
||||
"""Zuletzt geschaut zuerst, mit Schnitt, eigener Stimme, Kommentaren
|
||||
und wer dabei war. Die laufende Nummer zählt vom ersten Abend.
|
||||
|
||||
Wer wie gewertet und wer ein Herz gegeben hat, sieht nur der Host —
|
||||
für die Runde bleibt die Wertung anonym, das nimmt Hemmung."""
|
||||
felder = ", ".join(f"m.{f}" for f in AUS_KATALOG)
|
||||
with connect() as conn:
|
||||
filme = conn.execute(
|
||||
@@ -125,10 +149,26 @@ def alle(session_id):
|
||||
"LEFT JOIN users u ON u.id = n.user_id "
|
||||
"ORDER BY n.created_at ASC"
|
||||
).fetchall()
|
||||
herzen = conn.execute(
|
||||
"SELECT h.note_id, h.session_id, h.user_id, u.name FROM watched_hearts h "
|
||||
"LEFT JOIN users u ON u.id = h.user_id ORDER BY h.created_at ASC"
|
||||
).fetchall()
|
||||
anwesend = conn.execute(
|
||||
"SELECT d.watched_id, u.id, u.name FROM watched_dabei d "
|
||||
"JOIN users u ON u.id = d.user_id ORDER BY u.name COLLATE NOCASE"
|
||||
).fetchall()
|
||||
|
||||
ich = besitzer(session_id)
|
||||
|
||||
herz_je_notiz = {}
|
||||
for h in herzen:
|
||||
herz_je_notiz.setdefault(h["note_id"], []).append(h)
|
||||
dabei_je_film = {}
|
||||
for d in anwesend:
|
||||
dabei_je_film.setdefault(d["watched_id"], []).append({"id": d["id"], "name": d["name"]})
|
||||
|
||||
def notiz(n):
|
||||
meine_herzen = herz_je_notiz.get(n["id"], [])
|
||||
return {
|
||||
"id": n["id"],
|
||||
"text": n["text"],
|
||||
@@ -136,6 +176,9 @@ def alle(session_id):
|
||||
"created_at": n["created_at"],
|
||||
"mine": gehoert_mir(n, session_id, ich),
|
||||
"replies": [],
|
||||
"hearts": len(meine_herzen),
|
||||
"hearted": any(gehoert_mir(h, session_id, ich) for h in meine_herzen),
|
||||
"hearted_by": [h["name"] or "Anonym" for h in meine_herzen] if host else [],
|
||||
}
|
||||
|
||||
# Die Kommentare eines Films als Baum: jede Antwort hängt sich an
|
||||
@@ -176,7 +219,10 @@ def alle(session_id):
|
||||
"mine": gehoert_mir(s, session_id, ich),
|
||||
}
|
||||
for s in eigene
|
||||
],
|
||||
]
|
||||
if host
|
||||
else [],
|
||||
"dabei": dabei_je_film.get(film["id"], []),
|
||||
"notes": kommentare.get(film["id"], []),
|
||||
"average": round(sum(werte) / len(werte), 1) if werte else None,
|
||||
"count": len(werte),
|
||||
@@ -207,9 +253,31 @@ def markieren(movie):
|
||||
return dict(row)
|
||||
|
||||
|
||||
def entfernen(watched_id):
|
||||
"""Samt Wertungen und Kommentaren."""
|
||||
def dabei_setzen(watched_id, user_ids):
|
||||
"""Die Anwesenheit eines Abends — ersetzt die bisherige Liste."""
|
||||
with connect() as conn:
|
||||
conn.execute("DELETE FROM watched_dabei WHERE watched_id = ?", (watched_id,))
|
||||
conn.executemany(
|
||||
"INSERT OR IGNORE INTO watched_dabei (watched_id, user_id) VALUES (?, ?)",
|
||||
[(watched_id, i) for i in user_ids],
|
||||
)
|
||||
|
||||
|
||||
def _herzen_weg(conn, note_ids):
|
||||
conn.executemany(
|
||||
"DELETE FROM watched_hearts WHERE note_id = ?", [(i,) for i in note_ids]
|
||||
)
|
||||
|
||||
|
||||
def entfernen(watched_id):
|
||||
"""Samt Wertungen, Kommentaren, Herzen und Anwesenheit."""
|
||||
with connect() as conn:
|
||||
notizen = [
|
||||
r["id"]
|
||||
for r in conn.execute("SELECT id FROM watched_notes WHERE watched_id = ?", (watched_id,))
|
||||
]
|
||||
_herzen_weg(conn, notizen)
|
||||
conn.execute("DELETE FROM watched_dabei WHERE watched_id = ?", (watched_id,))
|
||||
conn.execute("DELETE FROM watched_notes WHERE watched_id = ?", (watched_id,))
|
||||
conn.execute("DELETE FROM ratings WHERE watched_id = ?", (watched_id,))
|
||||
return conn.execute(
|
||||
@@ -326,12 +394,35 @@ def anmerkung_loeschen(note_id, session_id, host):
|
||||
if not host and not gehoert_mir(zeile, session_id, ich):
|
||||
return 0
|
||||
alle_ids = [note_id, *_nachkommen(conn, note_id)]
|
||||
_herzen_weg(conn, alle_ids)
|
||||
conn.executemany(
|
||||
"DELETE FROM watched_notes WHERE id = ?", [(i,) for i in alle_ids]
|
||||
)
|
||||
return len(alle_ids)
|
||||
|
||||
|
||||
def herz(session_id, note_id):
|
||||
"""Herz setzen oder zurücknehmen. None, wenn es den Kommentar nicht
|
||||
gibt, sonst ob das Herz jetzt steht."""
|
||||
ich = besitzer(session_id)
|
||||
with connect() as conn:
|
||||
weg = conn.execute(
|
||||
f"DELETE FROM watched_hearts WHERE note_id = ? AND {WEM}",
|
||||
(note_id, ich, ich, session_id),
|
||||
).rowcount
|
||||
if weg:
|
||||
return False
|
||||
da = conn.execute("SELECT 1 FROM watched_notes WHERE id = ?", (note_id,)).fetchone()
|
||||
if not da:
|
||||
return None
|
||||
conn.execute(
|
||||
"INSERT INTO watched_hearts (note_id, session_id, user_id, created_at) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(note_id, session_id, ich, _jetzt()),
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def ids():
|
||||
"""movie_ids der geschauten Filme, für die Detailansicht."""
|
||||
with connect() as conn:
|
||||
|
||||
Binary file not shown.
@@ -9,8 +9,7 @@ def abend(client, film, movie_id=1):
|
||||
|
||||
def wertung(client, watched_id, stars=4):
|
||||
client.post(f"/api/watched/{watched_id}/rating", json={"stars": stars})
|
||||
film = next(f for f in client.get("/api/watched").json() if f["id"] == watched_id)
|
||||
return next(r for r in film["ratings"] if r["mine"])
|
||||
return next(f for f in client.get("/api/watched").json() if f["id"] == watched_id)
|
||||
|
||||
|
||||
def film_von(client, watched_id):
|
||||
@@ -103,8 +102,9 @@ def test_wertungen_und_kommentare_sind_unabhaengig(client, film):
|
||||
wertung(client, watched_id, 5)
|
||||
kommentar(client, watched_id, "Diskussion.")
|
||||
f = film_von(client, watched_id)
|
||||
assert f["ratings"][0]["stars"] == 5
|
||||
assert "text" not in f["ratings"][0]
|
||||
assert f["average"] == 5.0 and f["mine"] == 5
|
||||
# Die Namen an den Wertungen sieht nur der Host
|
||||
assert f["ratings"] == []
|
||||
assert [n["text"] for n in f["notes"]] == ["Diskussion."]
|
||||
|
||||
|
||||
|
||||
106
backend/tests/test_punkte.py
Normal file
106
backend/tests/test_punkte.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Erfahrung und Level: berechnet aus dem Bestand, nicht gebucht."""
|
||||
|
||||
from app import punkte
|
||||
|
||||
|
||||
def abend(client, film, movie_id=1):
|
||||
return client.post("/api/watched", json={"movie_id": film(movie_id)}).json()["id"]
|
||||
|
||||
|
||||
def stand(client, name):
|
||||
return next(u for u in client.get("/api/users").json()["items"] if u["name"] == name)
|
||||
|
||||
|
||||
def test_level_nach_dreieckszahlen():
|
||||
assert punkte.level(0) == 1
|
||||
assert punkte.level(1) == 2
|
||||
assert punkte.level(2) == 2
|
||||
assert punkte.level(3) == 3
|
||||
assert punkte.level(20) == 6
|
||||
assert punkte.level(21) == 7
|
||||
assert punkte.level(1081) == 47
|
||||
assert punkte.stand(21) == {"punkte": 21, "level": 7, "von": 21, "bis": 28}
|
||||
|
||||
|
||||
def test_punkte_kommen_und_gehen_mit_dem_bestand(client, client2, film):
|
||||
watched_id = abend(client, film)
|
||||
assert stand(client, "Anna")["punkte"] == 0
|
||||
|
||||
client.post(f"/api/watched/{watched_id}/rating", json={"stars": 4})
|
||||
assert stand(client, "Anna")["punkte"] == punkte.BEWERTEN
|
||||
|
||||
oben = client.post(f"/api/watched/{watched_id}/notes", json={"text": "gut"}).json()["id"]
|
||||
client.post(f"/api/watched/{watched_id}/notes", json={"text": "nochmal"})
|
||||
# Zwei Kommentare, einer zählt
|
||||
assert stand(client, "Anna")["punkte"] == punkte.BEWERTEN + punkte.KOMMENTAR
|
||||
|
||||
client2.post(f"/api/watched/{watched_id}/notes", json={"text": "finde ich nicht", "parent_id": oben})
|
||||
client2.post("/api/watched/hearts", json={"note_id": oben})
|
||||
assert stand(client2, "Ben")["punkte"] == punkte.ANTWORT + punkte.HERZ
|
||||
|
||||
# Wertung weg, Punkte weg — nichts wird gebucht
|
||||
client.post(f"/api/watched/{watched_id}/rating", json={"stars": 4})
|
||||
assert stand(client, "Anna")["level"] == punkte.level(punkte.BEWERTEN + punkte.KOMMENTAR)
|
||||
client.delete(f"/api/watched/{watched_id}")
|
||||
assert stand(client, "Anna")["punkte"] == 0
|
||||
assert stand(client2, "Ben")["punkte"] == 0
|
||||
|
||||
|
||||
def test_anwesenheit_kommt_vom_rad(client, client2, zuschauer, hostmodus, film):
|
||||
"""Wer vorschlägt oder sich einträgt, war dabei — sobald der Host den
|
||||
gezogenen Film einträgt."""
|
||||
from app import main
|
||||
|
||||
client.post("/api/suggestions", json={"movie_id": film(1)})
|
||||
assert client2.post("/api/dabei").json()["dabei"] is True
|
||||
liste = client2.get("/api/suggestions").json()
|
||||
assert liste["dabei"] == ["Ben"] and liste["dabei_mine"] is True
|
||||
|
||||
hostmodus(zuschauer)
|
||||
assert zuschauer.post("/api/spin").json()["started"] is True
|
||||
main.letzter_dreh["start"] = 0.0 # Animation vorbei
|
||||
watched_id = zuschauer.post("/api/watched", json={"movie_id": film(1)}).json()["id"]
|
||||
|
||||
f = next(x for x in client.get("/api/watched").json() if x["id"] == watched_id)
|
||||
assert sorted(d["name"] for d in f["dabei"]) == ["Anna", "Ben"]
|
||||
assert stand(client, "Anna")["punkte"] == punkte.SCHAUEN
|
||||
assert stand(client, "Ben")["level"] == punkte.level(punkte.SCHAUEN)
|
||||
|
||||
# Der Host berichtigt: Ben kam doch nicht
|
||||
anna = stand(client, "Anna")["id"]
|
||||
zuschauer.post(f"/api/watched/{watched_id}/dabei", json={"user_ids": [anna]})
|
||||
f = next(x for x in client.get("/api/watched").json() if x["id"] == watched_id)
|
||||
assert [d["name"] for d in f["dabei"]] == ["Anna"]
|
||||
|
||||
# Reset leert auch die Eingetragenen
|
||||
zuschauer.delete("/api/suggestions/alle")
|
||||
assert client2.get("/api/suggestions").json()["dabei"] == []
|
||||
assert client2.post("/api/dabei").json()["dabei"] is True
|
||||
assert client2.post("/api/dabei").json()["dabei"] is False
|
||||
|
||||
|
||||
def test_wertungen_und_herzen_sieht_nur_der_host(client, client2, zuschauer, hostmodus, film):
|
||||
watched_id = abend(client, film)
|
||||
client.post(f"/api/watched/{watched_id}/rating", json={"stars": 5})
|
||||
note_id = client.post(f"/api/watched/{watched_id}/notes", json={"text": "x"}).json()["id"]
|
||||
client2.post("/api/watched/hearts", json={"note_id": note_id})
|
||||
|
||||
def film_von(c):
|
||||
return next(x for x in c.get("/api/watched").json() if x["id"] == watched_id)
|
||||
|
||||
# Ben sieht Schnitt und Herzzahl, aber keine Namen
|
||||
f = film_von(client2)
|
||||
assert f["ratings"] == [] and f["average"] == 5.0 and f["count"] == 1
|
||||
assert f["notes"][0]["hearts"] == 1 and f["notes"][0]["hearted"] is True
|
||||
assert f["notes"][0]["hearted_by"] == []
|
||||
# Anna sieht ihre eigenen Sterne trotzdem
|
||||
assert film_von(client)["mine"] == 5
|
||||
# Der Host sieht alles
|
||||
hostmodus(zuschauer)
|
||||
f = film_von(zuschauer)
|
||||
assert f["ratings"][0]["name"] == "Anna"
|
||||
assert f["notes"][0]["hearted_by"] == ["Ben"]
|
||||
# Herz zurück, Zähler runter; Zuschauer ohne Namen darf kein Herz geben
|
||||
assert client2.post("/api/watched/hearts", json={"note_id": note_id}).json()["hearted"] is False
|
||||
assert film_von(client)["notes"][0]["hearts"] == 0
|
||||
assert client.post("/api/watched/hearts", json={"note_id": 999}).status_code == 404
|
||||
@@ -16,7 +16,8 @@ def test_wertung_ist_nur_sterne(client, film):
|
||||
assert eintrag["mine"] == 4
|
||||
assert eintrag["average"] == 4.0
|
||||
assert eintrag["count"] == 1
|
||||
assert "text" not in eintrag["ratings"][0]
|
||||
# Wer wie gewertet hat, sieht nur der Host
|
||||
assert eintrag["ratings"] == []
|
||||
assert "mine_text" not in eintrag
|
||||
|
||||
|
||||
@@ -58,7 +59,8 @@ def test_fremde_wertung_ist_nicht_meine(client, client2, film):
|
||||
[eintrag] = client2.get("/api/watched").json()
|
||||
assert eintrag["mine"] is None
|
||||
assert eintrag["count"] == 1
|
||||
assert eintrag["ratings"][0]["mine"] is False
|
||||
assert eintrag["ratings"] == []
|
||||
assert eintrag["count"] == 1
|
||||
|
||||
client2.post(f"/api/watched/{wid}/rating", json={"stars": 2})
|
||||
[eintrag] = client2.get("/api/watched").json()
|
||||
|
||||
Reference in New Issue
Block a user