mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Option to disable logging on raw save and via context manager (#446)
* Disable on raw save prototype * Contextmanager to disable instead of just raw - so we can catch m2m relations too Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
This commit is contained in:
parent
aa6d977f8b
commit
8fe776ae45
8 changed files with 156 additions and 5 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
- feat: Add `serialized_data` field on `LogEntry` model. ([#412](https://github.com/jazzband/django-auditlog/pull/412))
|
||||
- feat: Display the field name as it would be displayed in Django Admin or use `mapping_field` if available [#428](https://github.com/jazzband/django-auditlog/pull/428)
|
||||
- feat: New context manager `disable_auditlog` to turn off logging and a new setting `AUDITLOG_DISABLE_ON_RAW_SAVE`
|
||||
to disable it during raw-save operations like loaddata. [#446](https://github.com/jazzband/django-auditlog/pull/446)
|
||||
|
||||
#### Fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -15,3 +15,8 @@ settings.AUDITLOG_EXCLUDE_TRACKING_MODELS = getattr(
|
|||
settings.AUDITLOG_INCLUDE_TRACKING_MODELS = getattr(
|
||||
settings, "AUDITLOG_INCLUDE_TRACKING_MODELS", ()
|
||||
)
|
||||
|
||||
# Disable on raw save to avoid logging imports and similar
|
||||
settings.AUDITLOG_DISABLE_ON_RAW_SAVE = getattr(
|
||||
settings, "AUDITLOG_DISABLE_ON_RAW_SAVE", False
|
||||
)
|
||||
|
|
|
|||
|
|
@ -64,3 +64,15 @@ def _set_actor(user, sender, instance, signal_duid, **kwargs):
|
|||
instance.actor = user
|
||||
|
||||
instance.remote_addr = auditlog["remote_addr"]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def disable_auditlog():
|
||||
threadlocal.auditlog_disabled = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
try:
|
||||
del threadlocal.auditlog_disabled
|
||||
except AttributeError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,9 +1,31 @@
|
|||
import json
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from auditlog.context import threadlocal
|
||||
from auditlog.diff import model_instance_diff
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
|
||||
def check_disable(signal_handler):
|
||||
"""
|
||||
Decorator that passes along disabled in kwargs if any of the following is true:
|
||||
- 'auditlog_disabled' from threadlocal is true
|
||||
- raw = True and AUDITLOG_DISABLE_ON_RAW_SAVE is True
|
||||
"""
|
||||
|
||||
@wraps(signal_handler)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not getattr(threadlocal, "auditlog_disabled", False) and not (
|
||||
kwargs.get("raw") and settings.AUDITLOG_DISABLE_ON_RAW_SAVE
|
||||
):
|
||||
signal_handler(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@check_disable
|
||||
def log_create(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Signal receiver that creates a log entry when a model instance is first saved to the database.
|
||||
|
|
@ -20,6 +42,7 @@ def log_create(sender, instance, created, **kwargs):
|
|||
)
|
||||
|
||||
|
||||
@check_disable
|
||||
def log_update(sender, instance, **kwargs):
|
||||
"""
|
||||
Signal receiver that creates a log entry when a model instance is changed and saved to the database.
|
||||
|
|
@ -45,6 +68,7 @@ def log_update(sender, instance, **kwargs):
|
|||
)
|
||||
|
||||
|
||||
@check_disable
|
||||
def log_delete(sender, instance, **kwargs):
|
||||
"""
|
||||
Signal receiver that creates a log entry when a model instance is deleted from the database.
|
||||
|
|
@ -64,6 +88,7 @@ def log_delete(sender, instance, **kwargs):
|
|||
def make_log_m2m_changes(field_name):
|
||||
"""Return a handler for m2m_changed with field_name enclosed."""
|
||||
|
||||
@check_disable
|
||||
def log_m2m_changes(signal, action, **kwargs):
|
||||
"""Handle m2m_changed and call LogEntry.objects.log_m2m_changes as needed."""
|
||||
if action not in ["post_add", "post_clear", "post_remove"]:
|
||||
|
|
|
|||
|
|
@ -266,7 +266,8 @@ class AuditlogModelRegistry:
|
|||
"""
|
||||
if not isinstance(settings.AUDITLOG_INCLUDE_ALL_MODELS, bool):
|
||||
raise TypeError("Setting 'AUDITLOG_INCLUDE_ALL_MODELS' must be a boolean")
|
||||
|
||||
if not isinstance(settings.AUDITLOG_DISABLE_ON_RAW_SAVE, bool):
|
||||
raise TypeError("Setting 'AUDITLOG_DISABLE_ON_RAW_SAVE' must be a boolean")
|
||||
if not isinstance(settings.AUDITLOG_EXCLUDE_TRACKING_MODELS, (list, tuple)):
|
||||
raise TypeError(
|
||||
"Setting 'AUDITLOG_EXCLUDE_TRACKING_MODELS' must be a list or tuple"
|
||||
|
|
|
|||
15
auditlog_tests/fixtures/m2m_test_fixture.json
Normal file
15
auditlog_tests/fixtures/m2m_test_fixture.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
"model": "auditlog_tests.manyrelatedmodel",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"recursive": [1],
|
||||
"related": [1]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "auditlog_tests.manyrelatedothermodel",
|
||||
"pk": 1,
|
||||
"fields": {}
|
||||
}
|
||||
]
|
||||
|
|
@ -12,12 +12,13 @@ from django.contrib.admin.sites import AdminSite
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import management
|
||||
from django.db.models.signals import pre_save
|
||||
from django.test import RequestFactory, TestCase, override_settings
|
||||
from django.utils import dateformat, formats, timezone
|
||||
|
||||
from auditlog.admin import LogEntryAdmin
|
||||
from auditlog.context import set_actor
|
||||
from auditlog.context import disable_auditlog, set_actor
|
||||
from auditlog.diff import model_instance_diff
|
||||
from auditlog.middleware import AuditlogMiddleware
|
||||
from auditlog.models import LogEntry
|
||||
|
|
@ -1092,6 +1093,12 @@ class RegisterModelSettingsTest(TestCase):
|
|||
):
|
||||
self.test_auditlog.register_from_settings()
|
||||
|
||||
with override_settings(AUDITLOG_DISABLE_ON_RAW_SAVE="bad value"):
|
||||
with self.assertRaisesMessage(
|
||||
TypeError, "Setting 'AUDITLOG_DISABLE_ON_RAW_SAVE' must be a boolean"
|
||||
):
|
||||
self.test_auditlog.register_from_settings()
|
||||
|
||||
@override_settings(
|
||||
AUDITLOG_INCLUDE_ALL_MODELS=True,
|
||||
AUDITLOG_EXCLUDE_TRACKING_MODELS=("auditlog_tests.SimpleExcludeModel",),
|
||||
|
|
@ -1797,3 +1804,54 @@ class TestModelSerialization(TestCase):
|
|||
"value": 11,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@override_settings(AUDITLOG_DISABLE_ON_RAW_SAVE=True)
|
||||
class DisableTest(TestCase):
|
||||
"""
|
||||
All the other tests check logging, so this only needs to test disabled logging.
|
||||
"""
|
||||
|
||||
def test_create(self):
|
||||
# Mimic the way imports create objects
|
||||
inst = SimpleModel(
|
||||
text="I am a bit more difficult.", boolean=False, datetime=timezone.now()
|
||||
)
|
||||
SimpleModel.save_base(inst, raw=True)
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(inst).count())
|
||||
|
||||
def test_create_with_context_manager(self):
|
||||
with disable_auditlog():
|
||||
inst = SimpleModel.objects.create(text="I am a bit more difficult.")
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(inst).count())
|
||||
|
||||
def test_update(self):
|
||||
inst = SimpleModel(
|
||||
text="I am a bit more difficult.", boolean=False, datetime=timezone.now()
|
||||
)
|
||||
SimpleModel.save_base(inst, raw=True)
|
||||
inst.text = "I feel refreshed"
|
||||
inst.save_base(raw=True)
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(inst).count())
|
||||
|
||||
def test_update_with_context_manager(self):
|
||||
inst = SimpleModel(
|
||||
text="I am a bit more difficult.", boolean=False, datetime=timezone.now()
|
||||
)
|
||||
SimpleModel.save_base(inst, raw=True)
|
||||
with disable_auditlog():
|
||||
inst.text = "I feel refreshed"
|
||||
inst.save()
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(inst).count())
|
||||
|
||||
def test_m2m(self):
|
||||
"""
|
||||
Create m2m from fixture and check that nothing was logged.
|
||||
This only works with context manager
|
||||
"""
|
||||
with disable_auditlog():
|
||||
management.call_command("loaddata", "m2m_test_fixture.json", verbosity=0)
|
||||
recursive = ManyRelatedModel.objects.get(pk=1)
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(recursive).count())
|
||||
related = ManyRelatedOtherModel.objects.get(pk=1)
|
||||
self.assertEqual(0, LogEntry.objects.get_for_object(related).count())
|
||||
|
|
|
|||
|
|
@ -201,6 +201,18 @@ It must be a list or tuple. Each item in this setting can be a:
|
|||
|
||||
.. versionadded:: 2.1.0
|
||||
|
||||
**AUDITLOG_DISABLE_ON_RAW_SAVE**
|
||||
|
||||
Disables logging during raw save. (I.e. for instance using loaddata)
|
||||
|
||||
.. note::
|
||||
|
||||
M2M operations will still be logged, since they're never considered `raw`. To disable them
|
||||
you must remove their setting or use the `disable_auditlog` context manager.
|
||||
|
||||
.. versionadded:: 2.2.0
|
||||
|
||||
|
||||
Actors
|
||||
------
|
||||
|
||||
|
|
@ -228,10 +240,11 @@ It is recommended to keep all middleware that alters the request loaded before A
|
|||
user as actor. To only have some object changes to be logged with the current request's user as actor manual logging is
|
||||
required.
|
||||
|
||||
Context manager
|
||||
***************
|
||||
Context managers
|
||||
----------------
|
||||
|
||||
.. versionadded:: 2.1.0
|
||||
Set actor
|
||||
*********
|
||||
|
||||
To enable the automatic logging of the actors outside of request context (e.g. in a Celery task), you can use a context
|
||||
manager::
|
||||
|
|
@ -244,6 +257,26 @@ manager::
|
|||
# if your code here leads to creation of LogEntry instances, these will have the actor set
|
||||
...
|
||||
|
||||
|
||||
.. versionadded:: 2.1.0
|
||||
|
||||
|
||||
Disable auditlog
|
||||
****************
|
||||
|
||||
Disable auditlog temporary, for instance if you need to install a large fixture on a live system or cleanup
|
||||
corrupt data::
|
||||
|
||||
from auditlog.context import disable_auditlog
|
||||
|
||||
with disable_auditlog():
|
||||
# Do things silently here
|
||||
...
|
||||
|
||||
|
||||
.. versionadded:: 2.2.0
|
||||
|
||||
|
||||
Object history
|
||||
--------------
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue