Neuheiten+Planung: Pagination (50/Seite, Infinite Scroll) + quellen_url in Planung mit Quellen-Link

This commit is contained in:
Flo Hartmann
2026-08-24 22:44:18 +00:00
parent fa453518d4
commit aea52d1563
6 changed files with 69 additions and 6 deletions

View File

@@ -28,7 +28,7 @@ from urllib.parse import quote
from fastapi import Depends, Form, Request
from fastapi.responses import RedirectResponse
from sqlalchemy import select, update
from sqlalchemy import func, select, update
from sqlalchemy.orm import Session
from redaktionskern.auth.deps import AccessDenied, get_db, require_user
@@ -106,9 +106,29 @@ class PlanungPlugin(BasePlugin):
"ALTER TABLE planungsliste ADD COLUMN bild_url VARCHAR(500)"
)
def quellen_url_spalte(conn) -> None:
"""Migration 0003: Spalte quellen_url (Link zur Herkunftsquelle)."""
if conn.dialect.name == "sqlite":
info = conn.exec_driver_sql(
"PRAGMA table_info(planungsliste)"
).fetchall()
if any(zeile[1] == "quellen_url" for zeile in info):
return
else:
existiert = conn.exec_driver_sql(
"SELECT column_name FROM information_schema.columns "
"WHERE table_name = 'planungsliste' AND column_name = 'quellen_url'"
).scalar()
if existiert is not None:
return
conn.exec_driver_sql(
"ALTER TABLE planungsliste ADD COLUMN quellen_url VARCHAR(500)"
)
return [
Migration(version="0001_planungsliste", up=_planungs_tabelle),
Migration(version="0002_bild_url", up=bild_url_spalte),
Migration(version="0003_quellen_url", up=quellen_url_spalte),
]
def navigation(self) -> list[NavEntry]:
@@ -220,15 +240,22 @@ class PlanungPlugin(BasePlugin):
status: str = "",
meldung: str = "",
fehler: str = "",
seite_nr: int = 1,
):
"""Planungsliste: alle Einträge mit Zuordnung und Statuswechsel."""
"""Planungsliste: Einträge paginiert (50/Seite) mit Zuordnung."""
pro_seite = 50
abfrage = select(Planungseintrag)
if status in GUELTIGE_STATUS:
abfrage = abfrage.where(Planungseintrag.status == status)
gesamt = db.scalar(
select(func.count()).select_from(abfrage.subquery())
) or 0
eintraege = db.scalars(
abfrage.order_by(
Planungseintrag.aktualisiert_am.desc(), Planungseintrag.id.desc()
)
.offset((max(seite_nr, 1) - 1) * pro_seite)
.limit(pro_seite)
).all()
namen = {
benutzer.id: (benutzer.display_name or benutzer.username)
@@ -249,7 +276,9 @@ class PlanungPlugin(BasePlugin):
"ist_redaktion": user.role in REDAKTION,
"meldung": meldung[:300],
"fehler": fehler[:300],
"anzahl": len(eintraege),
"anzahl": gesamt,
"hat_weitere": max(seite_nr, 1) * pro_seite < gesamt,
"naechste_seite": max(seite_nr, 1) + 1,
},
)
@@ -392,6 +421,7 @@ class PlanungPlugin(BasePlugin):
autor=zeile.autor,
bgg_id=zeile.bgg_id,
bild_url=zeile.bild_url,
quellen_url=zeile.quellen_url,
ausgabe="",
rezensent_id=user.id,
status="offen",

View File

@@ -50,6 +50,7 @@ class Planungseintrag(Base):
bgg_id: Mapped[int | None] = mapped_column(Integer)
#: URL des Coverbildes (aus der Neuheitenliste übernommen oder leer).
bild_url: Mapped[str | None] = mapped_column(String(500))
quellen_url: Mapped[str | None] = mapped_column(String(500))
#: Magazin-Ausgabe, z. B. „3/2025“ (mehrere Ausgaben parallel möglich).
ausgabe: Mapped[str] = mapped_column(String(100), default="")
rezensent_id: Mapped[int] = mapped_column(ForeignKey("users.id"))

View File

@@ -76,6 +76,10 @@
{% if eintrag.bgg_id %}
<a href="https://boardgamegeek.com/boardgame/{{ eintrag.bgg_id }}" target="_blank" rel="noopener"
class="ml-0.5 text-[10px] text-slate-400 hover:text-emerald-700 align-super">BGG&nbsp;{{ eintrag.bgg_id }}</a>
{% elif eintrag.quellen_url %}
<a href="{{ eintrag.quellen_url }}" target="_blank" rel="noopener"
title="Zur Quelle ({{ eintrag.quelle }})"
class="ml-0.5 text-[10px] uppercase tracking-wide text-sky-600/70 hover:text-sky-800 align-super">{{ eintrag.quelle }}&nbsp;</a>
{% endif %}
{% if eintrag.pruefung %}
<span class="ml-0.5 inline-block px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 text-xs align-middle"
@@ -159,6 +163,10 @@
{% if eintrag.bgg_id %}
<a href="https://boardgamegeek.com/boardgame/{{ eintrag.bgg_id }}" target="_blank" rel="noopener"
class="ml-1 text-[10px] text-slate-400 hover:text-emerald-700 align-super">BGG&nbsp;{{ eintrag.bgg_id }}</a>
{% elif eintrag.quellen_url %}
<a href="{{ eintrag.quellen_url }}" target="_blank" rel="noopener"
title="Zur Quelle ({{ eintrag.quelle }})"
class="ml-1 text-[10px] uppercase tracking-wide text-sky-600/70 hover:text-sky-800 align-super">{{ eintrag.quelle }}&nbsp;</a>
{% endif %}
{% if eintrag.pruefung %}
<span class="ml-1 inline-block px-2 py-0.5 rounded-full bg-amber-100 text-amber-800 text-xs"
@@ -216,4 +224,11 @@
</tbody>
</table>
</div>
{% if hat_weitere %}
<div id="planung-lade-mehr"
hx-get="/planung?seite_nr={{ naechste_seite }}&status={{ status_filter }}"
hx-trigger="revealed delay:150ms" hx-swap="outerHTML">
<div class="py-6 text-center text-sm text-slate-400">Lade weitere Eintr&auml;ge&nbsp;&hellip;</div>
</div>
{% endif %}
{% endblock %}