django-auditlog/auditlog/context.py

108 lines
2.9 KiB
Python
Raw Permalink Normal View History

2022-05-24 07:33:54 +00:00
import contextlib
import time
from contextvars import ContextVar
2022-05-24 07:33:54 +00:00
from functools import partial
from django.contrib.auth import get_user_model
2022-05-24 07:33:54 +00:00
from django.db.models.signals import pre_save
from auditlog import get_logentry_model
2022-05-24 07:33:54 +00:00
auditlog_value = ContextVar("auditlog_value")
auditlog_disabled = ContextVar("auditlog_disabled", default=False)
2022-05-24 07:33:54 +00:00
@contextlib.contextmanager
2024-10-07 13:52:34 +00:00
def set_actor(actor, remote_addr=None, remote_port=None):
context_data = {
"actor": actor,
2022-05-24 07:33:54 +00:00
"remote_addr": remote_addr,
2024-10-07 13:52:34 +00:00
"remote_port": remote_port,
2022-05-24 07:33:54 +00:00
}
return call_context_manager(context_data)
@contextlib.contextmanager
def set_extra_data(context_data):
return call_context_manager(context_data)
def call_context_manager(context_data):
"""Connect a signal receiver with current user attached."""
LogEntry = get_logentry_model()
# Initialize thread local storage
context_data["signal_duid"] = ("set_actor", time.time())
auditlog_value.set(context_data)
2022-05-24 07:33:54 +00:00
# Connect signal for automatic logging
set_extra_data = partial(
_set_extra_data,
2025-01-30 15:03:51 +00:00
signal_duid=context_data["signal_duid"],
)
2022-05-24 07:33:54 +00:00
pre_save.connect(
set_extra_data,
2022-05-24 07:33:54 +00:00
sender=LogEntry,
dispatch_uid=context_data["signal_duid"],
2022-05-24 07:33:54 +00:00
weak=False,
)
try:
yield
finally:
try:
auditlog = auditlog_value.get()
except LookupError:
2022-05-24 07:33:54 +00:00
pass
else:
pre_save.disconnect(sender=LogEntry, dispatch_uid=auditlog["signal_duid"])
def _set_actor(auditlog, instance, sender):
LogEntry = get_logentry_model()
auth_user_model = get_user_model()
if "actor" in auditlog:
actor = auditlog.get("actor")
if (
sender == LogEntry
and isinstance(actor, auth_user_model)
and instance.actor is None
):
instance.actor = actor
instance.actor_email = getattr(actor, "email", None)
def _set_extra_data(sender, instance, signal_duid, **kwargs):
2022-05-24 07:33:54 +00:00
"""Signal receiver with extra 'user' and 'signal_duid' kwargs.
This function becomes a valid signal receiver when it is curried with the actor and a dispatch id.
"""
LogEntry = get_logentry_model()
2022-05-24 07:33:54 +00:00
try:
auditlog = auditlog_value.get()
except LookupError:
2022-05-24 07:33:54 +00:00
pass
else:
if signal_duid != auditlog["signal_duid"]:
return
_set_actor(auditlog, instance, sender)
for key in auditlog:
if key != "actor" and hasattr(LogEntry, key):
if callable(auditlog[key]):
setattr(instance, key, auditlog[key]())
else:
setattr(instance, key, auditlog[key])
@contextlib.contextmanager
def disable_auditlog():
token = auditlog_disabled.set(True)
try:
yield
finally:
try:
auditlog_disabled.reset(token)
except LookupError:
pass