Plugin erinnerung: Redaktionsschluss pro Ausgabe, 4-Wochen-Erinnerung
- Ausgaben (Name + Redaktionsschluss-Datum) verwalten, mehrere parallel, Anlegen/Bearbeiten/Löschen nur für Admin, mit Audit-Log-Einträgen - Erinnerung 28 Tage vor dem Redaktionsschluss an alle aktiven Rezensenten mit offener (oder fehlender) Planung, Versand über das benachrichtigung- Plugin je Kanal-Präferenz, Audit als System/benachrichtigt - Täglicher APScheduler-Job (SPIELE_ERINNERUNG_JOB_UHRZEIT, abschaltbar) plus manuelle Sofort-Prüfung in der Admin-Ansicht - Doppelschutz: Protokolltabelle mit Unique (Ausgabe, Benutzer) - Admin-UI: Ausgabenliste mit Restzeit-Badges, Erinnerungsstatus (wer wurde wann über welche Kanäle erinnert), deutsche Templates - Tests mit eingefrorener Uhr (freezegun): Fenster-Grenzen, kein Versand vor Frist/nach Schluss, Empfänger-/Rollenregeln, keine Duplikate, Scheduler-Konfiguration; Integrationstest mit den echten Plugins (167 Tests grün)
This commit is contained in:
64
plugins/erinnerung/models.py
Normal file
64
plugins/erinnerung/models.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Datenmodelle des Plugins „erinnerung“.
|
||||
|
||||
Zwei eigene Tabellen (SQLite ↔ Postgres portabel), der Kern bleibt unberührt:
|
||||
|
||||
- `erinnerung_ausgabe` — eine Magazin-Ausgabe mit Redaktionsschluss-Datum.
|
||||
Mehrere Ausgaben laufen parallel; der Name ist frei wählbar (z. B. „3/2025“).
|
||||
- `erinnerung_protokoll` — protokolliert jede versendete Erinnerung
|
||||
(Ausgabe + Benutzer). Die Eindeutigkeit von (ausgabe_id, user_id) stellt
|
||||
auf Datenbankebene sicher, dass niemand zweimal pro Ausgabe erinnert wird.
|
||||
|
||||
Keine ORM-Indizes: Bei erneuter Ausführung des Moduls würde ein deklarativ
|
||||
definierter Index doppelt im Table-Objekt landen und die Migration würde ihn
|
||||
zweimal anlegen (gleiche Begründung wie im benachrichtigung-Plugin).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from sqlalchemy import Date, DateTime, ForeignKey, String, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from redaktionskern.db import Base
|
||||
|
||||
|
||||
class Ausgabe(Base):
|
||||
"""Eine Magazin-Ausgabe mit ihrem Redaktionsschluss-Datum."""
|
||||
|
||||
__tablename__ = "erinnerung_ausgabe"
|
||||
__table_args__ = {"extend_existing": True}
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
#: Name/Titel der Ausgabe, z. B. „3/2025“ oder „Sommer-Sonderheft“.
|
||||
name: Mapped[str] = mapped_column(String(200))
|
||||
#: Redaktionsschluss (Datum, ohne Uhrzeit).
|
||||
redaktionsschluss: Mapped[date] = mapped_column(Date)
|
||||
erstellt_am: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
aktualisiert_am: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover - Debug-Hilfe
|
||||
return f"<Ausgabe {self.name!r} (Redaktionsschluss {self.redaktionsschluss})>"
|
||||
|
||||
|
||||
class ErinnerungsProtokoll(Base):
|
||||
"""Eine versendete Erinnerung: wer wurde zu welcher Ausgabe wann erinnert."""
|
||||
|
||||
__tablename__ = "erinnerung_protokoll"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("ausgabe_id", "user_id", name="uq_erinnerung_ausgabe_user"),
|
||||
{"extend_existing": True},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
ausgabe_id: Mapped[int] = mapped_column(ForeignKey("erinnerung_ausgabe.id"))
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
||||
#: Zugestellte Kanäle (Komma-liste, z. B. „email,inapp“) — wie vom
|
||||
#: benachrichtigung-Plugin gemeldet; leer, falls kein Kanal zustellte.
|
||||
kanaele: Mapped[str] = mapped_column(String(200), default="")
|
||||
#: Wann wurde die Erinnerung ausgelöst.
|
||||
erstellt_am: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover - Debug-Hilfe
|
||||
return f"<ErinnerungsProtokoll Ausgabe={self.ausgabe_id} User={self.user_id}>"
|
||||
Reference in New Issue
Block a user