From 6a7908f2ca0671a4ebc9e9e154beb7d6e55bf17d Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Thu, 16 Apr 2015 11:33:59 -0700 Subject: [PATCH 1/8] Add detailed_object_repr JSONField to logentry model --- src/auditlog/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/auditlog/models.py b/src/auditlog/models.py index 1223a6b..f767ae5 100644 --- a/src/auditlog/models.py +++ b/src/auditlog/models.py @@ -6,6 +6,8 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import ugettext_lazy as _ +from jsonfield import JSONField + class LogEntryManager(models.Manager): """ @@ -28,6 +30,10 @@ class LogEntryManager(models.Manager): if isinstance(pk, (int, long)): kwargs.setdefault('object_id', pk) + get_detailed_object_repr = getattr(instance, 'get_detailed_object_repr', None) + if callable(get_detailed_object_repr): + kwargs.setdefault('detailed_object_repr', instance.get_detailed_object_repr()) + # Delete log entries with the same pk as a newly created model. This should only be necessary when an pk is # used twice. if kwargs.get('action', None) is LogEntry.Action.CREATE: @@ -110,6 +116,7 @@ class LogEntry(models.Model): changes = models.TextField(blank=True, verbose_name=_("change message")) actor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("actor")) timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("timestamp")) + detailed_object_repr = JSONField(blank=True, null=True) objects = LogEntryManager() From d4522ad74add7848cfddb25bfcab656a7d47730e Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Thu, 16 Apr 2015 11:34:21 -0700 Subject: [PATCH 2/8] Add migration for detailed_object_repr field --- .../0003_logentry_detailed_object_repr.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/auditlog/migrations/0003_logentry_detailed_object_repr.py diff --git a/src/auditlog/migrations/0003_logentry_detailed_object_repr.py b/src/auditlog/migrations/0003_logentry_detailed_object_repr.py new file mode 100644 index 0000000..bd71ddb --- /dev/null +++ b/src/auditlog/migrations/0003_logentry_detailed_object_repr.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('auditlog', '0002_auto_support_long_primary_keys'), + ] + + operations = [ + migrations.AddField( + model_name='logentry', + name='detailed_object_repr', + field=jsonfield.fields.JSONField(null=True, blank=True), + ), + ] From 4409e1c1a6f850b77e58e90bd6d89aeecff7a53a Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Thu, 16 Apr 2015 11:34:48 -0700 Subject: [PATCH 3/8] Add jsonfield to pip requirements --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index be600a3..0828b7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ Django>=1.5 + +#allows use of verified JSON model fields +django-jsonfield==0.9.13 From 8b47ce1b19367165181856365a0d0ed92bb5a64b Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Mon, 20 Apr 2015 15:52:59 -0700 Subject: [PATCH 4/8] Add django-jsonfield dependency to setup file and update pip --- requirements.txt | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0828b7f..9c0b077 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ Django>=1.5 #allows use of verified JSON model fields -django-jsonfield==0.9.13 +django-jsonfield>=0.9.13 diff --git a/setup.py b/setup.py index 969439f..443d5f3 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ setup( author_email='janjelle@jjkester.nl', description='Audit log app for Django', install_requires=[ - 'Django>=1.5' + 'Django>=1.5', + 'django-jsonfield>=0.9.13' ] ) From 72cdde68648857471e34ecea6e0be5a58a163c70 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Mon, 1 Jun 2015 09:24:13 -0700 Subject: [PATCH 5/8] Change naming to use additional_data --- src/auditlog/.DS_Store | Bin 0 -> 6148 bytes .../0003_logentry_detailed_object_repr.py | 2 +- src/auditlog/models.py | 8 ++++---- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 src/auditlog/.DS_Store diff --git a/src/auditlog/.DS_Store b/src/auditlog/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e45ac8a880831b863a137497b3602d393a46a6bc GIT binary patch literal 6148 zcmeHK!A`LnxFR zF~-a^GjF>)ud{t^x?KR$c};GBCV(1Uu~KI!F&$SwuuACBL2=jUV}%S0OnT`-wsrhP z1@zg~(MA`24CveEmyJhhHsYwh8lUu!jWf1@26G3PVNPGUb{j8P_tFCEj+w0;);SX9 zKk+Eb)*3CkY&ML*6`q-$@JKPiJBIpRl`-X>*}HWv9COP2A!j$^8#%|aI5+v)%q1vvnd)!jka7!_Bbt%54J7a&O4PqQH_sAZa{RlW2 KESLhns=yD@jCxi8 literal 0 HcmV?d00001 diff --git a/src/auditlog/migrations/0003_logentry_detailed_object_repr.py b/src/auditlog/migrations/0003_logentry_detailed_object_repr.py index bd71ddb..23f59bd 100644 --- a/src/auditlog/migrations/0003_logentry_detailed_object_repr.py +++ b/src/auditlog/migrations/0003_logentry_detailed_object_repr.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='logentry', - name='detailed_object_repr', + name='additional_data', field=jsonfield.fields.JSONField(null=True, blank=True), ), ] diff --git a/src/auditlog/models.py b/src/auditlog/models.py index f767ae5..323cf23 100644 --- a/src/auditlog/models.py +++ b/src/auditlog/models.py @@ -30,9 +30,9 @@ class LogEntryManager(models.Manager): if isinstance(pk, (int, long)): kwargs.setdefault('object_id', pk) - get_detailed_object_repr = getattr(instance, 'get_detailed_object_repr', None) - if callable(get_detailed_object_repr): - kwargs.setdefault('detailed_object_repr', instance.get_detailed_object_repr()) + get_additional_data = getattr(instance, 'get_additional_data', None) + if callable(get_additional_data): + kwargs.setdefault('additional_data', instance.get_additional_data()) # Delete log entries with the same pk as a newly created model. This should only be necessary when an pk is # used twice. @@ -116,7 +116,7 @@ class LogEntry(models.Model): changes = models.TextField(blank=True, verbose_name=_("change message")) actor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("actor")) timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("timestamp")) - detailed_object_repr = JSONField(blank=True, null=True) + additional_data = JSONField(blank=True, null=True) objects = LogEntryManager() From 5fb006b226c9604747cfa753af0b6bedf7cf5d17 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Mon, 1 Jun 2015 11:01:40 -0700 Subject: [PATCH 6/8] Tests for models with/without get_additional_data defined --- src/testapp/models.py | 25 +++++++++++++++++++++++++ src/testapp/tests.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/testapp/models.py b/src/testapp/models.py index 4d5d0eb..055ede1 100644 --- a/src/testapp/models.py +++ b/src/testapp/models.py @@ -82,8 +82,33 @@ class SimpleExcludeModel(models.Model): history = AuditlogHistoryField() +class AdditionDataIncludedModel(models.Model): + """ + A model where get_additional_data is defined which allows for logging extra + information about the model in JSON + """ + + label = models.CharField(max_length=100) + text = models.TextField(blank=True) + related = models.ForeignKey(SimpleModel) + + history = AuditlogHistoryField() + + def get_additional_data(self): + """ + Returns JSON that captures a snapshot of additional details of the + model instance. This method, if defined, is accessed by auditlog + manager and added to each logentry instance on creation. + """ + object_details = { + 'related_model_id': self.related.id, + 'related_model_text': self.related.text + } + return object_details + auditlog.register(SimpleModel) auditlog.register(AltPrimaryKeyModel) auditlog.register(ProxyModel) auditlog.register(SimpleIncludeModel, include_fields=['label', ]) auditlog.register(SimpleExcludeModel, exclude_fields=['label', ]) +auditlog.register(AdditionDataIncludedModel) diff --git a/src/testapp/tests.py b/src/testapp/tests.py index 53c73ae..6bfa01f 100644 --- a/src/testapp/tests.py +++ b/src/testapp/tests.py @@ -6,8 +6,8 @@ from django.http import HttpResponse from django.test import TestCase, RequestFactory from auditlog.middleware import AuditlogMiddleware from auditlog.models import LogEntry -from testapp.models import SimpleModel, AltPrimaryKeyModel, ProxyModel, \ - SimpleIncludeModel, SimpleExcludeModel +from testapp.models import (SimpleModel, AltPrimaryKeyModel, ProxyModel, + SimpleIncludeModel, SimpleExcludeModel, AdditionDataIncludedModel,) class SimpleModelTest(TestCase): @@ -178,3 +178,28 @@ class SimpeExcludeModelTest(TestCase): sem.text = 'Short text' sem.save() self.assertTrue(sem.history.count() == 2, msg="There are two log entries") + + +class AdditionalDataModelTest(TestCase): + """Log additional data if get_additional_data is defined in the model""" + + def test_model_without_additional_data(self): + obj_wo_additional_data = SimpleModel.objects.create(text='No additional ' + 'data') + obj_log_entry = obj_wo_additional_data.history.get() + self.assertIsNone(obj_log_entry.additional_data) + + def test_model_with_additional_data(self): + related_model = SimpleModel.objects.create(text='Log my reference') + obj_with_additional_data = AdditionDataIncludedModel( + label='Additional data to log entries', related=related_model) + obj_with_additional_data.save() + self.assertTrue(obj_with_additional_data.history.count() == 1, + msg="There is 1 log entry") + log_entry = obj_with_additional_data.history.get() + self.assertIsNotNone(log_entry.additional_data) + extra_data = log_entry.additional_data + self.assertTrue(extra_data['related_model_text'] == related_model.text, + msg="Related model's text is logged") + self.assertTrue(extra_data['related_model_id'] == related_model.id, + msg="Related model's id is logged") From 23927ea31741942d0481eca045a518f258e6a568 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Wed, 3 Jun 2015 08:47:50 -0700 Subject: [PATCH 7/8] Add verbose name to the additional_data field Directly call the get_additional_data method without instance --- src/auditlog/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auditlog/models.py b/src/auditlog/models.py index 323cf23..dbe82b1 100644 --- a/src/auditlog/models.py +++ b/src/auditlog/models.py @@ -32,7 +32,7 @@ class LogEntryManager(models.Manager): get_additional_data = getattr(instance, 'get_additional_data', None) if callable(get_additional_data): - kwargs.setdefault('additional_data', instance.get_additional_data()) + kwargs.setdefault('additional_data', get_additional_data()) # Delete log entries with the same pk as a newly created model. This should only be necessary when an pk is # used twice. @@ -116,7 +116,7 @@ class LogEntry(models.Model): changes = models.TextField(blank=True, verbose_name=_("change message")) actor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("actor")) timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("timestamp")) - additional_data = JSONField(blank=True, null=True) + additional_data = JSONField(blank=True, null=True, verbose_name=_("additional data")) objects = LogEntryManager() From bade679b8136e8b8332a8e4d27eaa2fa8cff9d60 Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Wed, 3 Jun 2015 08:49:03 -0700 Subject: [PATCH 8/8] Fix typo on test class name changing it to AdditionalDataIncludedModel --- src/testapp/models.py | 4 ++-- src/testapp/tests.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/testapp/models.py b/src/testapp/models.py index 055ede1..96c0a9d 100644 --- a/src/testapp/models.py +++ b/src/testapp/models.py @@ -82,7 +82,7 @@ class SimpleExcludeModel(models.Model): history = AuditlogHistoryField() -class AdditionDataIncludedModel(models.Model): +class AdditionalDataIncludedModel(models.Model): """ A model where get_additional_data is defined which allows for logging extra information about the model in JSON @@ -111,4 +111,4 @@ auditlog.register(AltPrimaryKeyModel) auditlog.register(ProxyModel) auditlog.register(SimpleIncludeModel, include_fields=['label', ]) auditlog.register(SimpleExcludeModel, exclude_fields=['label', ]) -auditlog.register(AdditionDataIncludedModel) +auditlog.register(AdditionalDataIncludedModel) diff --git a/src/testapp/tests.py b/src/testapp/tests.py index 6bfa01f..8d395bc 100644 --- a/src/testapp/tests.py +++ b/src/testapp/tests.py @@ -7,7 +7,7 @@ from django.test import TestCase, RequestFactory from auditlog.middleware import AuditlogMiddleware from auditlog.models import LogEntry from testapp.models import (SimpleModel, AltPrimaryKeyModel, ProxyModel, - SimpleIncludeModel, SimpleExcludeModel, AdditionDataIncludedModel,) + SimpleIncludeModel, SimpleExcludeModel, AdditionalDataIncludedModel,) class SimpleModelTest(TestCase): @@ -191,7 +191,7 @@ class AdditionalDataModelTest(TestCase): def test_model_with_additional_data(self): related_model = SimpleModel.objects.create(text='Log my reference') - obj_with_additional_data = AdditionDataIncludedModel( + obj_with_additional_data = AdditionalDataIncludedModel( label='Additional data to log entries', related=related_model) obj_with_additional_data.save() self.assertTrue(obj_with_additional_data.history.count() == 1,