Delivers the append-only audit trail system for all compliance-critical actions per the TRA-246 acceptance criteria. - tracking/models.py: AuditEvent model with ORM-level immutability guard (save raises on update, delete raises on direct call) - tracking/audit.py: single record() call point; never raises in production - tracking/admin.py: read-only Django admin for AuditEvent inspection - tracking/migrations/0001_initial.py: DB schema with composite indexes - tracking/serializers.py: PII metadata gating (oidc_sub stripped for non-admin callers) - tracking/views.py: read-only AuditEventViewSet (IsPrivileged + 60/min throttle) - tracking/urls.py: registers audit/events/ router - tracking/management/commands/prune_audit_log.py: retention enforcement command with --dry-run and --class filter; writes access.admin_action event on real prune runs - config/settings/base.py: AUDIT_RETENTION_DAYS per event class + audit throttle rate - api/exceptions.py: wires access.permission_denied audit event on every PermissionDenied exception (M1 integration point) - tests/test_audit.py: 26-event taxonomy coverage, immutability, retention, API permission, PII gating, and service helper unit tests Co-Authored-By: Paperclip <noreply@paperclip.ing>
30 lines
787 B
Python
30 lines
787 B
Python
from django.contrib import admin
|
|
from .models import AuditEvent
|
|
|
|
|
|
@admin.register(AuditEvent)
|
|
class AuditEventAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
"event_type",
|
|
"event_class",
|
|
"actor_id",
|
|
"actor_ip",
|
|
"object_type",
|
|
"object_id",
|
|
"occurred_at",
|
|
"recorded_at",
|
|
]
|
|
list_filter = ["event_class", "actor_type", "retention_class"]
|
|
search_fields = ["event_type", "actor_id", "object_id"]
|
|
readonly_fields = [f.name for f in AuditEvent._meta.get_fields()]
|
|
ordering = ["-occurred_at"]
|
|
|
|
def has_add_permission(self, request):
|
|
return False
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|