File size: 3,089 Bytes
4f01198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""
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)