163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
from django.db.models.signals import post_save, post_delete, pre_save
|
|
from django.dispatch import receiver
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from .models import ActivityLog, AuditLog
|
|
import json
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
import inspect
|
|
|
|
# Try to import models dynamically to avoid circular imports during startup
|
|
# We'll use get_model where possible, but for signals we need the actual models
|
|
from projects.models import Project, Task
|
|
|
|
def get_current_request():
|
|
"""
|
|
A hacky way to get the current request in signals.
|
|
In a real production app, you'd use a middleware like django-crum
|
|
or pass the user explicitly.
|
|
For this prototype, we'll try to find the request in the stack.
|
|
"""
|
|
for f in inspect.stack():
|
|
if 'request' in f[0].f_locals:
|
|
return f[0].f_locals['request']
|
|
return None
|
|
|
|
def get_user_and_tenant_from_instance_or_request(instance):
|
|
request = get_current_request()
|
|
user = getattr(request, 'user', None) if request else None
|
|
|
|
# If user is anonymous, try to get from instance
|
|
if user and user.is_authenticated:
|
|
pass
|
|
elif hasattr(instance, 'created_by') and instance.created_by:
|
|
user = instance.created_by
|
|
elif hasattr(instance, 'user') and instance.user:
|
|
user = instance.user
|
|
|
|
tenant = getattr(request, 'tenant', None) if request else getattr(instance, 'tenant', None)
|
|
|
|
return user, tenant
|
|
|
|
@receiver(post_save, sender=Project)
|
|
@receiver(post_save, sender=Task)
|
|
def log_activity_on_save(sender, instance, created, **kwargs):
|
|
user, tenant = get_user_and_tenant_from_instance_or_request(instance)
|
|
|
|
if not tenant:
|
|
return # Cannot log without tenant
|
|
|
|
action = "created" if created else "updated"
|
|
target_type = sender.__name__
|
|
|
|
metadata = {}
|
|
if hasattr(instance, 'name'):
|
|
metadata['name'] = instance.name
|
|
elif hasattr(instance, 'title'):
|
|
metadata['title'] = instance.title
|
|
|
|
ActivityLog.objects.create(
|
|
tenant=tenant,
|
|
user=user if (user and user.is_authenticated) else None,
|
|
action=action,
|
|
target_type=target_type,
|
|
target_id=str(instance.id),
|
|
metadata=metadata
|
|
)
|
|
|
|
@receiver(post_delete, sender=Project)
|
|
@receiver(post_delete, sender=Task)
|
|
def log_activity_on_delete(sender, instance, **kwargs):
|
|
user, tenant = get_user_and_tenant_from_instance_or_request(instance)
|
|
|
|
if not tenant:
|
|
return
|
|
|
|
action = "deleted"
|
|
target_type = sender.__name__
|
|
|
|
metadata = {}
|
|
if hasattr(instance, 'name'):
|
|
metadata['name'] = instance.name
|
|
elif hasattr(instance, 'title'):
|
|
metadata['title'] = instance.title
|
|
|
|
ActivityLog.objects.create(
|
|
tenant=tenant,
|
|
user=user if (user and user.is_authenticated) else None,
|
|
action=action,
|
|
target_type=target_type,
|
|
target_id=str(instance.id),
|
|
metadata=metadata
|
|
)
|
|
|
|
# --- Audit Logs ---
|
|
|
|
# Small hack: Store the original state before saving
|
|
@receiver(pre_save, sender=Project)
|
|
@receiver(pre_save, sender=Task)
|
|
def store_original_state(sender, instance, **kwargs):
|
|
if instance.pk:
|
|
try:
|
|
old_instance = sender.objects.get(pk=instance.pk)
|
|
# Store it on the instance for the post_save signal
|
|
instance._old_state = {f.name: getattr(old_instance, f.name) for f in sender._meta.fields}
|
|
except sender.DoesNotExist:
|
|
pass
|
|
|
|
@receiver(post_save, sender=Project)
|
|
@receiver(post_save, sender=Task)
|
|
def log_audit_on_save(sender, instance, created, **kwargs):
|
|
user, tenant = get_user_and_tenant_from_instance_or_request(instance)
|
|
request = get_current_request()
|
|
|
|
if not tenant:
|
|
return
|
|
|
|
action = "created" if created else "updated"
|
|
|
|
changes = {}
|
|
if not created and hasattr(instance, '_old_state'):
|
|
for f in sender._meta.fields:
|
|
old_val = instance._old_state.get(f.name)
|
|
new_val = getattr(instance, f.name)
|
|
if old_val != new_val:
|
|
# Basic stringification for JSON serialization
|
|
changes[f.name] = {
|
|
'old': str(old_val),
|
|
'new': str(new_val)
|
|
}
|
|
elif created:
|
|
for f in sender._meta.fields:
|
|
changes[f.name] = {'new': str(getattr(instance, f.name))}
|
|
|
|
AuditLog.objects.create(
|
|
tenant=tenant,
|
|
user=user if (user and user.is_authenticated) else None,
|
|
action=action,
|
|
model_name=sender.__name__,
|
|
object_id=str(instance.id),
|
|
changes=changes,
|
|
ip_address=request.META.get('REMOTE_ADDR') if request else None
|
|
)
|
|
|
|
@receiver(post_delete, sender=Project)
|
|
@receiver(post_delete, sender=Task)
|
|
def log_audit_on_delete(sender, instance, **kwargs):
|
|
user, tenant = get_user_and_tenant_from_instance_or_request(instance)
|
|
request = get_current_request()
|
|
|
|
if not tenant:
|
|
return
|
|
|
|
changes = {f.name: {'old': str(getattr(instance, f.name))} for f in sender._meta.fields}
|
|
|
|
AuditLog.objects.create(
|
|
tenant=tenant,
|
|
user=user if (user and user.is_authenticated) else None,
|
|
action="deleted",
|
|
model_name=sender.__name__,
|
|
object_id=str(instance.id),
|
|
changes=changes,
|
|
ip_address=request.META.get('REMOTE_ADDR') if request else None
|
|
)
|