Backend Draft
This commit is contained in:
162
backend/analytics/signals.py
Normal file
162
backend/analytics/signals.py
Normal file
@@ -0,0 +1,162 @@
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user