Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow AuditlogHistoryField to block cascading deletes #172

Merged
merged 1 commit into from
Mar 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/auditlog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist
from django.db import models
from django.db import models, DEFAULT_DB_ALIAS
from django.db.models import QuerySet, Q
from django.utils import formats, timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
Expand Down Expand Up @@ -321,9 +321,13 @@ class AuditlogHistoryField(GenericRelation):

:param pk_indexable: Whether the primary key for this model is not an :py:class:`int` or :py:class:`long`.
:type pk_indexable: bool
:param delete_related: By default, including a generic relation into a model will cause all related objects to be
cascade-deleted when the parent object is deleted. Passing False to this overrides this behavior, retaining
the full auditlog history for the object. Defaults to True, because that's Django's default behavior.
:type delete_related: bool
"""

def __init__(self, pk_indexable=True, **kwargs):
def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
kwargs['to'] = LogEntry

if pk_indexable:
Expand All @@ -332,8 +336,22 @@ def __init__(self, pk_indexable=True, **kwargs):
kwargs['object_id_field'] = 'object_pk'

kwargs['content_type_field'] = 'content_type'
self.delete_related = delete_related
super(AuditlogHistoryField, self).__init__(**kwargs)

def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
"""
Return all objects related to ``objs`` via this ``GenericRelation``.
"""
if self.delete_related:
return super(AuditlogHistoryField, self).bulk_related_objects(objs, using)

# When deleting, Collector.collect() finds related objects using this
# method. However, because we don't want to delete these related
# objects, we simply return an empty list.
return []


# South compatibility for AuditlogHistoryField
try:
from south.modelsinspector import add_introspection_rules
Expand Down
7 changes: 7 additions & 0 deletions src/auditlog_tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ class PostgresArrayFieldModel(models.Model):
history = AuditlogHistoryField()


class NoDeleteHistoryModel(models.Model):
integer = models.IntegerField(blank=True, null=True)

history = AuditlogHistoryField(delete_related=False)


auditlog.register(AltPrimaryKeyModel)
auditlog.register(UUIDPrimaryKeyModel)
auditlog.register(ProxyModel)
Expand All @@ -222,3 +228,4 @@ class PostgresArrayFieldModel(models.Model):
auditlog.register(ChoicesFieldModel)
auditlog.register(CharfieldTextfieldModel)
auditlog.register(PostgresArrayFieldModel)
auditlog.register(NoDeleteHistoryModel)
32 changes: 31 additions & 1 deletion src/auditlog_tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from auditlog_tests.models import SimpleModel, AltPrimaryKeyModel, UUIDPrimaryKeyModel, \
ProxyModel, SimpleIncludeModel, SimpleExcludeModel, SimpleMappingModel, RelatedModel, \
ManyRelatedModel, AdditionalDataIncludedModel, DateTimeFieldModel, ChoicesFieldModel, \
CharfieldTextfieldModel, PostgresArrayFieldModel
CharfieldTextfieldModel, PostgresArrayFieldModel, NoDeleteHistoryModel
from auditlog import compat


Expand Down Expand Up @@ -663,3 +663,33 @@ def test_auditlog_admin(self):
res = self.client.get("/admin/auditlog/logentry/{}/history/".format(log_pk))
assert res.status_code == 200


class NoDeleteHistoryTest(TestCase):
def test_delete_related(self):
instance = SimpleModel.objects.create(integer=1)
assert LogEntry.objects.all().count() == 1
instance.integer = 2
instance.save()
assert LogEntry.objects.all().count() == 2

instance.delete()
entries = LogEntry.objects.order_by('id')

# The "DELETE" record is always retained
assert LogEntry.objects.all().count() == 1
assert entries.first().action == LogEntry.Action.DELETE

def test_no_delete_related(self):
instance = NoDeleteHistoryModel.objects.create(integer=1)
self.assertEqual(LogEntry.objects.all().count(), 1)
instance.integer = 2
instance.save()
self.assertEqual(LogEntry.objects.all().count(), 2)

instance.delete()
entries = LogEntry.objects.order_by('id')
self.assertEqual(entries.count(), 3)
self.assertEqual(
list(entries.values_list('action', flat=True)),
[LogEntry.Action.CREATE, LogEntry.Action.UPDATE, LogEntry.Action.DELETE]
)