Plugin audit-log: Protokolltabelle, öffentliche API und Admin-Ansicht
- 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)
This commit is contained in:
40
plugins/audit-log/models.py
Normal file
40
plugins/audit-log/models.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""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
|
||||
)
|
||||
Reference in New Issue
Block a user