Merge pull request #12 from vittoriozamboni/limitfields

Added include_fields and exclude_fields kwargs to register method
This commit is contained in:
Jan-Jelle Kester 2014-12-11 11:26:09 +01:00
commit 4fd8387df0
6 changed files with 106 additions and 12 deletions

View file

@ -1,11 +1,14 @@
from django.db.models import Model
def model_instance_diff(old, new):
def model_instance_diff(old, new, **kwargs):
"""
Calculate the differences between two model instances. One of the instances may be None (i.e., a newly
created model or deleted model). This will cause all fields with a value to have changed (from None).
"""
from auditlog.registry import auditlog
if not(old is None or isinstance(old, Model)):
raise TypeError('The supplied old instance is not a valid model instance.')
if not(new is None or isinstance(new, Model)):
@ -15,13 +18,29 @@ def model_instance_diff(old, new):
if old is not None and new is not None:
fields = set(old._meta.fields + new._meta.fields)
model_fields = auditlog.get_model_fields(new._meta.model)
elif old is not None:
fields = set(old._meta.fields)
model_fields = auditlog.get_model_fields(old._meta.model)
elif new is not None:
fields = set(new._meta.fields)
model_fields = auditlog.get_model_fields(new._meta.model)
else:
fields = set()
# Check if fields must be filtered
if (model_fields['include_fields'] or model_fields['exclude_fields']) and fields:
filtered_fields = []
if model_fields['include_fields']:
filtered_fields = [field for field in fields
if field.name in model_fields['include_fields']]
else:
filtered_fields = fields
if model_fields['exclude_fields']:
filtered_fields = [field for field in filtered_fields
if field.name not in model_fields['exclude_fields']]
fields = filtered_fields
for field in fields:
old_value = unicode(getattr(old, field.name, None))
new_value = unicode(getattr(new, field.name, None))

View file

@ -38,7 +38,7 @@ class LogEntryManager(models.Manager):
return self.create(**kwargs)
return None
def get_for_object(self, instance):
"""
Get log entries for the specified object.
@ -152,7 +152,7 @@ class LogEntry(models.Model):
substrings.append(substring)
return separator.join(substrings)
class AuditlogHistoryField(generic.GenericRelation):
"""

View file

@ -7,7 +7,7 @@ def log_create(sender, instance, created, **kwargs):
"""
Signal receiver that creates a log entry when a model instance is first saved to the database.
Direct use is discouraged, connect your model through auditlog.registry.registry instead.
Direct use is discouraged, connect your model through auditlog.registry.register instead.
"""
if created:
changes = model_instance_diff(None, instance)
@ -23,7 +23,7 @@ def log_update(sender, instance, **kwargs):
"""
Signal receiver that creates a log entry when a model instance is changed and saved to the database.
Direct use is discouraged, connect your model through auditlog.registry.registry instead.
Direct use is discouraged, connect your model through auditlog.registry.register instead.
"""
if instance.pk is not None:
try:
@ -48,7 +48,7 @@ def log_delete(sender, instance, **kwargs):
"""
Signal receiver that creates a log entry when a model instance is deleted from the database.
Direct use is discouraged, connect your model through auditlog.registry.registry instead.
Direct use is discouraged, connect your model through auditlog.registry.register instead.
"""
if instance.pk is not None:
changes = model_instance_diff(instance, None)

View file

@ -1,6 +1,5 @@
from django.db.models.signals import pre_save, post_save, post_delete
from django.db.models import Model
from auditlog.receivers import log_create, log_update, log_delete
class AuditLogModelRegistry(object):
@ -9,7 +8,8 @@ class AuditLogModelRegistry(object):
"""
def __init__(self, create=True, update=True, delete=True, custom=None):
self._registry = []
from auditlog.receivers import log_create, log_update, log_delete
self._registry = {}
self._signals = {}
if create:
@ -22,12 +22,19 @@ class AuditLogModelRegistry(object):
if custom is not None:
self._signals.update(custom)
def register(self, model):
def register(self, model, **kwargs):
"""
Register a model with auditlog. Auditlog will then track mutations on this model's instances.
Kwargs:
- `include_fields`: list of field names to include in diff
- `exclude_fields`: list of field names to exclude in diff
"""
if issubclass(model, Model):
self._registry.append(model)
self._registry[model] = {
'include_fields': kwargs.get('include_fields', []),
'exclude_fields': kwargs.get('exclude_fields', []),
}
self._connect_signals(model)
else:
raise TypeError('Supplied model is not a valid model.')
@ -43,7 +50,7 @@ class AuditLogModelRegistry(object):
Unregister a model with auditlog. This will not affect the database.
"""
try:
self._registry.pop(model)
del self._registry[model]
except KeyError:
pass
else:
@ -70,5 +77,10 @@ class AuditLogModelRegistry(object):
"""
return (self.__class__, model, signal)
def get_model_fields(self, model):
return {
'include_fields': self._registry[model]['include_fields'],
'exclude_fields': self._registry[model]['exclude_fields'],
}
auditlog = AuditLogModelRegistry()

View file

@ -60,6 +60,30 @@ class ManyRelatedModel(models.Model):
history = AuditlogHistoryField()
class SimpleIncludeModel(models.Model):
"""
A simple model used for register's include_fields kwarg
"""
label = models.CharField(max_length=100)
text = models.TextField(blank=True)
history = AuditlogHistoryField()
class SimpleExcludeModel(models.Model):
"""
A simple model used for register's exclude_fields kwarg
"""
label = models.CharField(max_length=100)
text = models.TextField(blank=True)
history = AuditlogHistoryField()
auditlog.register(SimpleModel)
auditlog.register(AltPrimaryKeyModel)
auditlog.register(ProxyModel)
auditlog.register(SimpleIncludeModel, include_fields=['label', ])
auditlog.register(SimpleExcludeModel, exclude_fields=['label', ])

View file

@ -6,7 +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
from testapp.models import SimpleModel, AltPrimaryKeyModel, ProxyModel, \
SimpleIncludeModel, SimpleExcludeModel
class SimpleModelTest(TestCase):
@ -139,3 +140,41 @@ class MiddlewareTest(TestCase):
# Validate result
self.assertFalse(pre_save.has_listeners(LogEntry))
class SimpeIncludeModelTest(TestCase):
"""Log only changes in include_fields"""
def test_register_include_fields(self):
sim = SimpleIncludeModel(label='Include model', text='Looong text')
sim.save()
self.assertTrue(sim.history.count() == 1, msg="There is one log entry")
# Change label, record
sim.label = 'Changed label'
sim.save()
self.assertTrue(sim.history.count() == 2, msg="There are two log entries")
# Change text, ignore
sim.text = 'Short text'
sim.save()
self.assertTrue(sim.history.count() == 2, msg="There are two log entries")
class SimpeExcludeModelTest(TestCase):
"""Log only changes that are not in exclude_fields"""
def test_register_exclude_fields(self):
sem = SimpleIncludeModel(label='Exclude model', text='Looong text')
sem.save()
self.assertTrue(sem.history.count() == 1, msg="There is one log entry")
# Change label, ignore
sem.label = 'Changed label'
sem.save()
self.assertTrue(sem.history.count() == 2, msg="There are two log entries")
# Change text, record
sem.text = 'Short text'
sem.save()
self.assertTrue(sem.history.count() == 2, msg="There are two log entries")