""" Audit Log Model — persists every sensitive action in the system. Written to by the AuditLogMiddleware and signal handlers. """ import uuid from django.db import models from django.conf import settings class AuditAction(models.TextChoices): LOGIN = 'login', 'Login' LOGOUT = 'logout', 'Logout' LOGIN_FAILED = 'login_failed', 'Login Failed' REGISTER = 'register', 'Register' PASSWORD_CHANGE = 'password_change', 'Password Changed' CREATE = 'create', 'Create' UPDATE = 'update', 'Update' DELETE = 'delete', 'Delete' VIEW = 'view', 'View' BOOKING_CREATE = 'booking_create', 'Booking Created' BOOKING_UPDATE = 'booking_update', 'Booking Updated' BOOKING_CANCEL = 'booking_cancel', 'Booking Cancelled' CAREGIVER_ADD = 'caregiver_add', 'Caregiver Added' CAREGIVER_UPDATE = 'caregiver_update', 'Caregiver Updated' CAREGIVER_DELETE = 'caregiver_delete', 'Caregiver Deleted' IMAGE_UPLOAD = 'image_upload', 'Image Uploaded' DATA_EXPORT = 'data_export', 'Data Exported' class AuditLog(models.Model): """Immutable audit trail — never update or delete records.""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Who did it actor = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='audit_logs', ) actor_email = models.EmailField(blank=True) # snapshot (actor may be deleted) actor_role = models.CharField(max_length=20, blank=True) # What happened action = models.CharField(max_length=30, choices=AuditAction.choices) description = models.TextField(blank=True) # Target resource resource_type = models.CharField(max_length=50, blank=True) # e.g. 'Caregiver' resource_id = models.CharField(max_length=100, blank=True) # str(pk) # Snapshot of changed data (sanitised — no raw passwords/keys) before_state = models.JSONField(null=True, blank=True) after_state = models.JSONField(null=True, blank=True) # Request metadata ip_address = models.GenericIPAddressField(null=True, blank=True) user_agent = models.TextField(blank=True) request_path = models.CharField(max_length=500, blank=True) request_method = models.CharField(max_length=10, blank=True) response_status = models.PositiveSmallIntegerField(null=True, blank=True) timestamp = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: db_table = 'audit_logs' ordering = ['-timestamp'] # No update/delete permissions at model level — enforced in admin default_permissions = ('add', 'view') # no change/delete def __str__(self): return f'[{self.timestamp:%Y-%m-%d %H:%M}] {self.actor_email} — {self.action}' def save(self, *args, **kwargs): """Snapshot actor info at write time.""" if self.actor and not self.actor_email: self.actor_email = self.actor.email self.actor_role = self.actor.role super().save(*args, **kwargs)