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(), )