mongoengine
mongoengine copied to clipboard
Post save signal with the delta information
I am looking at logging changes to some of my documents for auditing. Based on Issue #589 the post_save signal is now being sent before the changed fields are being cleared. It would be nice if somehow the updates/removals (self._delta) could be passed to the signal. Right now I have to recompute the delta. I'm not quite sure how this could be done in a backwards compatible way unless everyone receiver is using **kwargs. Maybe a new signal could be added.
This is an example of what I would like to do. Would like to avoid having to call document._delta() because it has already been computed and it is a "protected" member of the Document class.
import json
from datetime import datetime
from bson import json_util
import mongoengine as me
class Change(me.EmbeddedDocument):
"""The change to the mongo record"""
op = me.StringField(required=True, choices=['insert', 'set', 'unset', 'delete']) # pylint: disable=invalid-name
data = me.DictField()
class AuditLog(me.Document):
"""Track changes to other documents"""
object = me.GenericReferenceField()
date = me.DateTimeField(required=True, default=datetime.utcnow)
user = me.StringField()
changes = me.EmbeddedDocumentListField(Change)
def audit(cls):
"""Class decorator to audit a Document subclass"""
me.signals.post_save.connect(_post_save_audit, sender=cls)
me.signals.post_delete.connect(_post_delete_audit, sender=cls)
return cls
def _post_save_audit(sender, document, created, **kwargs): # pylint: disable=unused-argument
changes = []
if created:
changes.append(Change(op='insert', data=document.to_mongo()))
else:
updates, removals = document._delta() # pylint: disable=protected-access
if updates:
changes.append(Change(op='set', data=updates))
if removals:
changes.append(Change(op='unset', data=removals))
log_entry = AuditLog(object=document, changes=changes)
log_entry.save()
def _post_delete_audit(sender, document, **kwargs): # pylint: disable=unused-argument
change = Change(op='delete', data=document.to_mongo())
log_entry = AuditLog(object=document, changes=[change])
log_entry.save()
BTW, you can do this in the pre_save*
signals by comparing the state of the not-yet-saved document
's fields to its previous state stored in Mongo. As an example:
def pre_save_post_validation(sender, document, **kwargs):
old_document = type(document).objects.get(id=str(document.id))
for changed_field_name in document._get_changed_fields():
value = getattr(document, changed_field_name)
old_value = getattr(old_document, changed_field_name)
# ...
I know this is ancient history, but... Is there any improvement to this pattern? I went looking and couldn't find anything. I'm a little skeptical about depending on an internal method of the library to do this sort of comparison.