- Eigene Migration 0001_audit_eintraege (Tabelle audit_eintraege: Akteur,
Aktion, Objekttyp/-ID, Alt/Neu + Details als JSON, optionale IP, Zeitstempel)
- Öffentliche Plugin-API: await log(actor, action, objekt_typ, objekt_id,
details, ip_adresse) bzw. log_sync() für synchrone Routen; andere Plugins
holen die API über request.app.state.registry.get("audit-log")
- Admin-Ansicht /audit-log (nur Admin): Filter nach Benutzer, Aktionstyp und
Zeitraum, Paginierung (25/Seite), deutsche UI, Alt/Neu-JSON-Darstellung
- Tests: Logging-Funktion (Actor-Varianten, alt/neu-Extraktion), Migration,
Filter-Querys, Paginierung, Nur-Admin-Zugriff (13 neue Tests)
- Loader-Test angepasst: voll implementierte Plugins tragen keinen
Platzhalter-Text mehr
- AGENTS.md: Status aktualisiert
Verifiziert: HEAD + diese Änderungen = 58 Tests grün (uv run pytest)
41 lines
1.6 KiB
Python
41 lines
1.6 KiB
Python
"""Datenmodell des Plugins „audit-log“.
|
|
|
|
Ein AuditEintrag protokolliert ein Ereignis: wer hat was wann mit welchem
|
|
Objekt getan (Alt-/Neustand als JSON, IP optional). Die Tabelle gehört
|
|
ausschließlich zu diesem Plugin; der Kern kennt sie nicht.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import JSON, DateTime, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from redaktionskern.db import Base
|
|
|
|
|
|
class AuditEintrag(Base):
|
|
"""Ein protokolliertes Ereignis im Audit-Log."""
|
|
|
|
__tablename__ = "audit_eintraege"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
# Wer: Benutzer-ID und -Name (NULL/„System“ bei automatischen Ereignissen)
|
|
actor_id: Mapped[int | None] = mapped_column(Integer, index=True)
|
|
actor_name: Mapped[str] = mapped_column(String(100), default="", index=True)
|
|
# Was: Aktionstyp (z. B. „erstellt“, „geaendert“, „geloescht“)
|
|
action: Mapped[str] = mapped_column(String(100), index=True)
|
|
# Auf welches Objekt: Typ + ID (ID als Text, damit alle ID-Arten passen)
|
|
objekt_typ: Mapped[str] = mapped_column(String(100), default="")
|
|
objekt_id: Mapped[str] = mapped_column(String(100), default="")
|
|
# Alt-/Neustand und freie Zusatzinformationen als JSON
|
|
alt: Mapped[dict | None] = mapped_column(JSON)
|
|
neu: Mapped[dict | None] = mapped_column(JSON)
|
|
details: Mapped[dict | None] = mapped_column(JSON)
|
|
# Optionale Herkunftsangabe
|
|
ip_adresse: Mapped[str | None] = mapped_column(String(64))
|
|
# Wann
|
|
erstellt_am: Mapped[datetime] = mapped_column(
|
|
DateTime, server_default=func.now(), index=True
|
|
)
|