Skip to content

Commit

Permalink
Bugfix: DJANGO_EASY_AUDIT_DATABASE_ALIAS support. (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
jheld authored Jan 22, 2020
1 parent 69b4cb6 commit be2ac58
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 81 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ Below are some of the settings you may want to use. These should be defined in y
- ['event_type', 'content_type', 'user', 'datetime', ] for CRUDEventAdmin
- ['login_type', 'user', 'datetime', ] for LoginEventAdmin
- ['method', 'user', 'datetime', ] for RequestEventAdmin

* `DJANGO_EASY_AUDIT_DATABASE_ALIAS`

By default it is the django `default` database alias. But for projects that have split databases,
this is necessary in order to keep database atomicity concerns in check during signal handlers.

To clarify, this is only _truly_ necessary for the model signals.

## What does it do

Expand Down
3 changes: 3 additions & 0 deletions easyaudit/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

from importlib import import_module

import django.db.utils
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import Permission
Expand Down Expand Up @@ -83,6 +85,7 @@ def get_model_list(class_list):
# project defined callbacks
CRUD_DIFFERENCE_CALLBACKS = []
CRUD_DIFFERENCE_CALLBACKS = getattr(settings, 'DJANGO_EASY_AUDIT_CRUD_DIFFERENCE_CALLBACKS', CRUD_DIFFERENCE_CALLBACKS)
DATABASE_ALIAS = getattr(settings, 'DJANGO_EASY_AUDIT_DATABASE_ALIAS', django.db.utils.DEFAULT_DB_ALIAS)
# the callbacks could come in as an iterable of strings, where each string is the package.module.function
for idx, callback in enumerate(CRUD_DIFFERENCE_CALLBACKS):
if not callable(callback): # keep as is if it is callable
Expand Down
179 changes: 99 additions & 80 deletions easyaudit/signals/model_signals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
Expand All @@ -14,7 +15,7 @@
get_current_user
from easyaudit.models import CRUDEvent
from easyaudit.settings import REGISTERED_CLASSES, UNREGISTERED_CLASSES, \
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS, DATABASE_ALIAS
from easyaudit.utils import model_delta

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -49,7 +50,7 @@ def pre_save(sender, instance, raw, using, update_fields, **kwargs):
return

try:
with transaction.atomic():
with transaction.atomic(using=using):
if not should_audit(instance):
return False
try:
Expand Down Expand Up @@ -89,25 +90,30 @@ def pre_save(sender, instance, raw, using, update_fields, **kwargs):
# create crud event only if all callbacks returned True
if create_crud_event and not created:
c_t = ContentType.objects.get_for_model(instance)
sid = transaction.savepoint()
try:
with transaction.atomic():
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
changed_fields=changed_fields,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
transaction.savepoint_rollback(sid)

def crud_flow():
try:
# atomicity based on the easyaudit database alias
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
changed_fields=changed_fields,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
if getattr(settings, "TEST", False):
crud_flow()
else:
transaction.on_commit(crud_flow, using=using)
except Exception:
logger.exception('easy audit had a pre-save exception.')

Expand All @@ -119,7 +125,7 @@ def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
return

try:
with transaction.atomic():
with transaction.atomic(using=using):
if not should_audit(instance):
return False
object_json_repr = serializers.serialize("json", [instance])
Expand Down Expand Up @@ -149,24 +155,28 @@ def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
# create crud event only if all callbacks returned True
if create_crud_event and created:
c_t = ContentType.objects.get_for_model(instance)
sid = transaction.savepoint()
try:
with transaction.atomic():
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
transaction.savepoint_rollback(sid)

def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
if getattr(settings, "TEST", False):
crud_flow()
else:
transaction.on_commit(crud_flow, using=using)
except Exception:
logger.exception('easy audit had a post-save exception.')

Expand All @@ -191,7 +201,7 @@ def _m2m_rev_field_name(model1, model2):
def m2m_changed(sender, instance, action, reverse, model, pk_set, using, **kwargs):
"""https://docs.djangoproject.com/es/1.10/ref/signals/#m2m-changed"""
try:
with transaction.atomic():
with transaction.atomic(using=using):
if not should_audit(instance):
return False

Expand Down Expand Up @@ -228,33 +238,37 @@ def m2m_changed(sender, instance, action, reverse, model, pk_set, using, **kwarg
if isinstance(user, AnonymousUser):
user = None
c_t = ContentType.objects.get_for_model(instance)
sid = transaction.savepoint()

try:
with transaction.atomic():
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
transaction.savepoint_rollback(sid)
def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
crud_event = CRUDEvent.objects.create(
event_type=event_type,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)
except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))

if getattr(settings, "TEST", False):
crud_flow()
else:
transaction.on_commit(crud_flow, using=using)
except Exception:
logger.exception('easy audit had an m2m-changed exception.')


def post_delete(sender, instance, using, **kwargs):
"""https://docs.djangoproject.com/es/1.10/ref/signals/#post-delete"""
try:
with transaction.atomic():
with transaction.atomic(using=using):
if not should_audit(instance):
return False

Expand All @@ -271,26 +285,31 @@ def post_delete(sender, instance, using, **kwargs):
if isinstance(user, AnonymousUser):
user = None
c_t = ContentType.objects.get_for_model(instance)
sid = transaction.savepoint()
try:
with transaction.atomic():
# crud event
crud_event = CRUDEvent.objects.create(
event_type=CRUDEvent.DELETE,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)

except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))
transaction.savepoint_rollback(sid)

def crud_flow():
try:
with transaction.atomic(using=DATABASE_ALIAS):
# crud event
crud_event = CRUDEvent.objects.create(
event_type=CRUDEvent.DELETE,
object_repr=str(instance),
object_json_repr=object_json_repr,
content_type_id=c_t.id,
object_id=instance.pk,
user_id=getattr(user, 'id', None),
datetime=timezone.now(),
user_pk_as_string=str(user.pk) if user else user
)

except Exception as e:
logger.exception(
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
instance, instance.pk))

if getattr(settings, "TEST", False):
crud_flow()
else:
transaction.on_commit(crud_flow, using=using)
except Exception:
logger.exception('easy audit had a post-delete exception.')

Expand Down
6 changes: 5 additions & 1 deletion easyaudit/tests/test_app/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
import json
import re
from django.test import TestCase
from django.test import TestCase, override_settings

try: # Django 2.0
from django.urls import reverse
except: # Django < 2.0
Expand All @@ -20,6 +21,7 @@
TEST_ADMIN_PASSWORD = 'password'


@override_settings(TEST=True)
class TestAuditModels(TestCase):

def test_create_model(self):
Expand Down Expand Up @@ -49,6 +51,7 @@ def test_m2m_model(self):
self.assertEqual(data['fields']['test_m2m'], [obj.id])


@override_settings(TEST=True)
class TestMiddleware(TestCase):
def _setup_user(self, email, password):
user = User(username=email)
Expand Down Expand Up @@ -100,6 +103,7 @@ def test_manual_set_user(self):
self.assertEqual(crud_event.user, None)


@override_settings(TEST=True)
class TestAuditAdmin(TestCase):

def _setup_superuser(self, email, password):
Expand Down

0 comments on commit be2ac58

Please sign in to comment.