Spaces:
Sleeping
Sleeping
| """ | |
| 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) | |