Skip to content

Commit 5249423

Browse files
authored
Merge pull request #80 from jheld/bugfix/content-type-models-transaction
split user db constraint, content type retrieve and create fix
2 parents 858403b + 75216be commit 5249423

File tree

9 files changed

+193
-72
lines changed

9 files changed

+193
-72
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ Below are some of the settings you may want to use. These should be defined in y
5959
will be matched against the URL path.
6060
[Check our wiki](https://github.com/soynatan/django-easy-audit/wiki/Settings#request-auditing)
6161
for more details on how to use it.
62+
63+
* `DJANGO_EASY_AUDIT_CRUD_DIFFERENCE_CALLBACKS`
64+
65+
May point to a list of callables/string-paths-to-functions-classes in which the application code can determine
66+
on a per CRUDEvent whether or not the application chooses to create the CRUDEvent or not. This is different
67+
from the registered/unregistered settings (e.g. `DJANGO_EASY_AUDIT_UNREGISTERED_CLASSES_EXTRA`).
68+
This is meant to be for dynamic configurations where the application
69+
may inspect the current save/create/delete and choose whether or not to save that into the database or ignore it.
70+
71+
* `DJANGO_EASY_AUDIT_USER_DB_CONSTRAINT`
72+
73+
Default is `True`. This is reserved for future use (does not do anything yet). The functionality provided by the
74+
setting (whether enabled or disabled) could be handled more explicitly in certain
75+
code paths (or even internally as custom model managers). For projects that separate the easyaudit database, such
76+
that the tables are not on the same database as the user table, this could help with making certain queries easier.
77+
Again, this doesn't do anything yet, and if it ever does, the version will be increased and the README will be
78+
updated accordingly. If you keep your database together (the standard usage), you have nothing to worry about.
6279

6380
## What does it do
6481

easyaudit/migrations/0009_auto_20180314_2225.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ class Migration(migrations.Migration):
2020
migrations.AlterField(
2121
model_name='requestevent',
2222
name='url',
23-
field=models.TextField(db_index=True),
23+
field=models.TextField(db_index=False),
2424
),
2525
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11 on 2018-11-01 13:39
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('easyaudit', '0010_repr_text'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='requestevent',
17+
name='url',
18+
field=models.TextField(),
19+
),
20+
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.12 on 2018-10-18 00:12
3+
from __future__ import unicode_literals
4+
5+
from django.conf import settings
6+
from django.db import migrations, models
7+
import django.db.models.deletion
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
dependencies = [
13+
('easyaudit', '0011_auto_20181101_1339'),
14+
]
15+
16+
operations = [
17+
migrations.AlterField(
18+
model_name='crudevent',
19+
name='user',
20+
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
21+
),
22+
migrations.AlterField(
23+
model_name='crudevent',
24+
name='content_type',
25+
field=models.ForeignKey(to='contenttypes.ContentType', db_constraint=False, on_delete=models.CASCADE),
26+
),
27+
migrations.AlterField(
28+
model_name='loginevent',
29+
name='user',
30+
field=models.ForeignKey(blank=True, db_constraint=False, null=True,
31+
on_delete=django.db.models.deletion.SET_NULL,
32+
to=settings.AUTH_USER_MODEL),
33+
),
34+
migrations.AlterField(
35+
model_name='requestevent',
36+
name='user',
37+
field=models.ForeignKey(blank=True, db_constraint=False, null=True,
38+
on_delete=django.db.models.deletion.SET_NULL,
39+
to=settings.AUTH_USER_MODEL),
40+
),
41+
42+
]

easyaudit/models.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ class CRUDEvent(models.Model):
2121

2222
event_type = models.SmallIntegerField(choices=TYPES)
2323
object_id = models.IntegerField() # we should try to allow other ID types
24-
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
24+
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, db_constraint=False)
2525
object_repr = models.TextField(null=True, blank=True)
2626
object_json_repr = models.TextField(null=True, blank=True)
2727
changed_fields = models.TextField(null=True, blank=True)
2828
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
29-
blank=True, on_delete=models.SET_NULL)
29+
blank=True, on_delete=models.SET_NULL,
30+
db_constraint=False)
3031
user_pk_as_string = models.CharField(max_length=255, null=True, blank=True,
31-
help_text='String version of the user pk')
32+
help_text='String version of the user pk')
3233
datetime = models.DateTimeField(auto_now_add=True)
3334

3435
def is_create(self):
@@ -59,7 +60,7 @@ class LoginEvent(models.Model):
5960
login_type = models.SmallIntegerField(choices=TYPES)
6061
username = models.CharField(max_length=255, null=True, blank=True)
6162
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
62-
on_delete=models.SET_NULL)
63+
on_delete=models.SET_NULL, db_constraint=False)
6364
remote_ip = models.CharField(max_length=50, null=True, db_index=True)
6465
datetime = models.DateTimeField(auto_now_add=True)
6566

@@ -70,11 +71,11 @@ class Meta:
7071

7172

7273
class RequestEvent(models.Model):
73-
url = models.TextField(null=False, db_index=True)
74+
url = models.TextField(null=False, db_index=False)
7475
method = models.CharField(max_length=20, null=False, db_index=True)
7576
query_string = models.TextField(null=True)
7677
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
77-
on_delete=models.SET_NULL)
78+
on_delete=models.SET_NULL, db_constraint=False)
7879
remote_ip = models.CharField(max_length=50, null=True, db_index=True)
7980
datetime = models.DateTimeField(auto_now_add=True)
8081

easyaudit/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def get_model_list(class_list):
3030
WATCH_REQUEST_EVENTS = getattr(settings, 'DJANGO_EASY_AUDIT_WATCH_REQUEST_EVENTS', True)
3131
REMOTE_ADDR_HEADER = getattr(settings, 'DJANGO_EASY_AUDIT_REMOTE_ADDR_HEADER', 'REMOTE_ADDR')
3232

33+
USER_DB_CONSTRAINT = bool(getattr(settings, 'DJANGO_EASY_AUDIT_USER_DB_CONSTRAINT', True))
34+
3335

3436
# Models which Django Easy Audit will not log.
3537
# By default, all but some models will be audited.

easyaudit/signals/auth_signals.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def user_logged_in(sender, request, user, **kwargs):
1010
with transaction.atomic():
1111
login_event = LoginEvent.objects.create(login_type=LoginEvent.LOGIN,
1212
username=getattr(user, user.USERNAME_FIELD),
13-
user=user,
13+
user_id=getattr(user, 'id', None),
1414
remote_ip=request.META[REMOTE_ADDR_HEADER])
1515
except:
1616
pass
@@ -20,8 +20,8 @@ def user_logged_out(sender, request, user, **kwargs):
2020
try:
2121
with transaction.atomic():
2222
login_event = LoginEvent.objects.create(login_type=LoginEvent.LOGOUT,
23-
username=getattr(user, user.USERNAME_FIELD),
24-
user=user,
23+
username=getattr(user, user.USERNAME_FIELD, None),
24+
user_id=getattr(user, 'id', None),
2525
remote_ip=request.META[REMOTE_ADDR_HEADER])
2626
except:
2727
pass

easyaudit/signals/model_signals.py

Lines changed: 100 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@
1010
from django.utils import timezone
1111
from django.utils.encoding import force_text
1212

13-
from easyaudit.middleware.easyaudit import get_current_request,\
14-
get_current_user
13+
from easyaudit.middleware.easyaudit import get_current_request, \
14+
get_current_user
1515
from easyaudit.models import CRUDEvent
16-
from easyaudit.settings import REGISTERED_CLASSES, UNREGISTERED_CLASSES,\
17-
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS
16+
from easyaudit.settings import REGISTERED_CLASSES, UNREGISTERED_CLASSES, \
17+
WATCH_MODEL_EVENTS, CRUD_DIFFERENCE_CALLBACKS
1818
from easyaudit.utils import model_delta
1919

20-
2120
logger = logging.getLogger(__name__)
2221

2322

@@ -46,14 +45,18 @@ def should_audit(instance):
4645
def pre_save(sender, instance, raw, using, update_fields, **kwargs):
4746
"""https://docs.djangoproject.com/es/1.10/ref/signals/#post-save"""
4847
if raw:
49-
# Return if loading Fixtures
50-
return
51-
48+
# Return if loading Fixtures
49+
return
50+
5251
try:
5352
with transaction.atomic():
5453
if not should_audit(instance):
5554
return False
56-
object_json_repr = serializers.serialize("json", [instance])
55+
try:
56+
object_json_repr = serializers.serialize("json", [instance])
57+
except Exception:
58+
# We need a better way for this to work. ManyToMany will fail on pre_save on create
59+
return None
5760

5861
if instance.pk is None:
5962
created = True
@@ -80,32 +83,41 @@ def pre_save(sender, instance, raw, using, update_fields, **kwargs):
8083

8184
# callbacks
8285
kwargs['request'] = get_current_request() # make request available for callbacks
83-
create_crud_event = all(callback(instance, object_json_repr, created, raw, using, update_fields, **kwargs)
84-
for callback in CRUD_DIFFERENCE_CALLBACKS if callable(callback))
85-
86+
create_crud_event = all(
87+
callback(instance, object_json_repr, created, raw, using, update_fields, **kwargs)
88+
for callback in CRUD_DIFFERENCE_CALLBACKS if callable(callback))
8689
# create crud event only if all callbacks returned True
8790
if create_crud_event and not created:
88-
crud_event = CRUDEvent.objects.create(
89-
event_type=event_type,
90-
object_repr=str(instance),
91-
object_json_repr=object_json_repr,
92-
changed_fields=changed_fields,
93-
content_type=ContentType.objects.get_for_model(instance),
94-
object_id=instance.pk,
95-
user=user,
96-
datetime=timezone.now(),
97-
user_pk_as_string=str(user.pk) if user else user
98-
)
91+
c_t = ContentType.objects.get_for_model(instance)
92+
sid = transaction.savepoint()
93+
try:
94+
with transaction.atomic():
95+
crud_event = CRUDEvent.objects.create(
96+
event_type=event_type,
97+
object_repr=str(instance),
98+
object_json_repr=object_json_repr,
99+
changed_fields=changed_fields,
100+
content_type_id=c_t.id,
101+
object_id=instance.pk,
102+
user_id=getattr(user, 'id', None),
103+
datetime=timezone.now(),
104+
user_pk_as_string=str(user.pk) if user else user
105+
)
106+
except Exception as e:
107+
logger.exception(
108+
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
109+
instance, instance.pk))
110+
transaction.savepoint_rollback(sid)
99111
except Exception:
100112
logger.exception('easy audit had a pre-save exception.')
101113

102114

103115
def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
104116
"""https://docs.djangoproject.com/es/1.10/ref/signals/#post-save"""
105117
if raw:
106-
# Return if loading Fixtures
107-
return
108-
118+
# Return if loading Fixtures
119+
return
120+
109121
try:
110122
with transaction.atomic():
111123
if not should_audit(instance):
@@ -130,22 +142,31 @@ def post_save(sender, instance, created, raw, using, update_fields, **kwargs):
130142
# callbacks
131143
kwargs['request'] = get_current_request() # make request available for callbacks
132144
create_crud_event = all(callback(instance, object_json_repr,
133-
created, raw, using, update_fields, **kwargs)
145+
created, raw, using, update_fields, **kwargs)
134146
for callback in CRUD_DIFFERENCE_CALLBACKS
135147
if callable(callback))
136148

137149
# create crud event only if all callbacks returned True
138150
if create_crud_event and created:
139-
crud_event = CRUDEvent.objects.create(
140-
event_type=event_type,
141-
object_repr=str(instance),
142-
object_json_repr=object_json_repr,
143-
content_type=ContentType.objects.get_for_model(instance),
144-
object_id=instance.pk,
145-
user=user,
146-
datetime=timezone.now(),
147-
user_pk_as_string=str(user.pk) if user else user
148-
)
151+
c_t = ContentType.objects.get_for_model(instance)
152+
sid = transaction.savepoint()
153+
try:
154+
with transaction.atomic():
155+
crud_event = CRUDEvent.objects.create(
156+
event_type=event_type,
157+
object_repr=str(instance),
158+
object_json_repr=object_json_repr,
159+
content_type_id=c_t.id,
160+
object_id=instance.pk,
161+
user_id=getattr(user, 'id', None),
162+
datetime=timezone.now(),
163+
user_pk_as_string=str(user.pk) if user else user
164+
)
165+
except Exception as e:
166+
logger.exception(
167+
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
168+
instance, instance.pk))
169+
transaction.savepoint_rollback(sid)
149170
except Exception:
150171
logger.exception('easy audit had a post-save exception.')
151172

@@ -161,8 +182,8 @@ def _m2m_rev_field_name(model1, model2):
161182
m2m_field_names = [
162183
rel.get_accessor_name() for rel in model1._meta.get_fields()
163184
if rel.many_to_many
164-
and rel.auto_created
165-
and rel.related_model == model2
185+
and rel.auto_created
186+
and rel.related_model == model2
166187
]
167188
return m2m_field_names[0]
168189

@@ -206,17 +227,26 @@ def m2m_changed(sender, instance, action, reverse, model, pk_set, using, **kwarg
206227

207228
if isinstance(user, AnonymousUser):
208229
user = None
230+
c_t = ContentType.objects.get_for_model(instance)
231+
sid = transaction.savepoint()
209232

210-
crud_event = CRUDEvent.objects.create(
211-
event_type=event_type,
212-
object_repr=str(instance),
213-
object_json_repr=object_json_repr,
214-
content_type=ContentType.objects.get_for_model(instance),
215-
object_id=instance.pk,
216-
user=user,
217-
datetime=timezone.now(),
218-
user_pk_as_string=str(user.pk) if user else user
219-
)
233+
try:
234+
with transaction.atomic():
235+
crud_event = CRUDEvent.objects.create(
236+
event_type=event_type,
237+
object_repr=str(instance),
238+
object_json_repr=object_json_repr,
239+
content_type_id=c_t.id,
240+
object_id=instance.pk,
241+
user_id=getattr(user, 'id', None),
242+
datetime=timezone.now(),
243+
user_pk_as_string=str(user.pk) if user else user
244+
)
245+
except Exception as e:
246+
logger.exception(
247+
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
248+
instance, instance.pk))
249+
transaction.savepoint_rollback(sid)
220250
except Exception:
221251
logger.exception('easy audit had an m2m-changed exception.')
222252

@@ -240,18 +270,27 @@ def post_delete(sender, instance, using, **kwargs):
240270

241271
if isinstance(user, AnonymousUser):
242272
user = None
243-
244-
# crud event
245-
crud_event = CRUDEvent.objects.create(
246-
event_type=CRUDEvent.DELETE,
247-
object_repr=str(instance),
248-
object_json_repr=object_json_repr,
249-
content_type=ContentType.objects.get_for_model(instance),
250-
object_id=instance.pk,
251-
user=user,
252-
datetime=timezone.now(),
253-
user_pk_as_string=str(user.pk) if user else user
254-
)
273+
c_t = ContentType.objects.get_for_model(instance)
274+
sid = transaction.savepoint()
275+
try:
276+
with transaction.atomic():
277+
# crud event
278+
crud_event = CRUDEvent.objects.create(
279+
event_type=CRUDEvent.DELETE,
280+
object_repr=str(instance),
281+
object_json_repr=object_json_repr,
282+
content_type_id=c_t.id,
283+
object_id=instance.pk,
284+
user_id=getattr(user, 'id', None),
285+
datetime=timezone.now(),
286+
user_pk_as_string=str(user.pk) if user else user
287+
)
288+
289+
except Exception as e:
290+
logger.exception(
291+
"easy audit had a pre-save exception on CRUDEvent creation. instance: {}, instance pk: {}".format(
292+
instance, instance.pk))
293+
transaction.savepoint_rollback(sid)
255294
except Exception:
256295
logger.exception('easy audit had a post-delete exception.')
257296

0 commit comments

Comments
 (0)