infrasynth-backend-kit/infrasynth/audit/receivers.py
2026-08-28 14:38:47 -05:00

130 lines
4 KiB
Python

import uuid
from django.conf import settings
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from .models import ModelChangeLog, SecurityEvent
from .signals import security_event_occurred
def _get_excluded_models():
config = getattr(settings, "INFRASYNTH_AUDIT", {})
return set(config.get("EXCLUDED_MODELS", []))
def _get_excluded_fields():
config = getattr(settings, "INFRASYNTH_AUDIT", {})
return set(config.get("EXCLUDED_FIELDS", []))
def _get_request_id(request=None):
if request:
return getattr(request, "request_id", "") or str(uuid.uuid4())[:8]
return str(uuid.uuid4())[:8]
@receiver(post_save)
def track_model_change(sender, instance, created, raw, **kwargs):
if raw:
return
label = sender._meta.label
if label in _get_excluded_models():
return
config = getattr(settings, "INFRASYNTH_AUDIT", {})
if not config.get("ENABLE_MODEL_CHANGE_TRACKING", True):
return
if created:
ModelChangeLog.objects.create(
model_label=label,
object_id=str(instance.pk),
action="create",
changes=_get_created_changes(instance),
actor=_get_actor_from_instance(instance),
request_id=_get_request_id(),
)
else:
if hasattr(instance, "_previous_state"):
changes = _compute_changes(instance._previous_state, instance)
if changes:
ModelChangeLog.objects.create(
model_label=label,
object_id=str(instance.pk),
action="update",
changes=changes,
actor=_get_actor_from_instance(instance),
request_id=_get_request_id(),
)
@receiver(post_delete)
def track_model_delete(sender, instance, **kwargs):
label = sender._meta.label
if label in _get_excluded_models():
return
config = getattr(settings, "INFRASYNTH_AUDIT", {})
if not config.get("ENABLE_MODEL_CHANGE_TRACKING", True):
return
ModelChangeLog.objects.create(
model_label=label,
object_id=str(instance.pk),
action="delete",
changes={},
actor=_get_actor_from_instance(instance),
request_id=_get_request_id(),
)
def _get_actor_from_instance(instance):
for field in ["actor", "user", "usuario_creacion", "created_by", "uploaded_by"]:
val = getattr(instance, field, None)
if val is not None:
return val
return None
def _get_created_changes(instance):
excluded = _get_excluded_fields()
changes = {}
for field in instance._meta.get_fields():
if field.name in excluded:
continue
if hasattr(field, "serialize") and field.serialize:
val = getattr(instance, field.name, None)
if val is not None:
changes[field.name] = [None, str(val)]
return changes
def _compute_changes(old, new):
excluded = _get_excluded_fields()
changes = {}
for field in new._meta.get_fields():
if field.name in excluded:
continue
if not hasattr(field, "column") or field.column is None:
continue
old_val = getattr(old, field.name, None)
new_val = getattr(new, field.name, None)
if old_val != new_val:
changes[field.name] = [
str(old_val) if old_val is not None else None,
str(new_val) if new_val is not None else None,
]
return changes
@receiver(security_event_occurred)
def log_security_event(sender, **kwargs):
config = getattr(settings, "INFRASYNTH_AUDIT", {})
if not config.get("ENABLE_SECURITY_EVENTS", True):
return
SecurityEvent.objects.create(
event_type=kwargs.get("event_type", "unknown"),
actor=kwargs.get("actor"),
ip_address=kwargs.get("ip_address"),
metadata=kwargs.get("metadata", {}),
request_id=_get_request_id(),
)