Thanks for this. I managed to get it a bit cleaner in three ways and have pasted by current script below:
(1) I found that, like Django's ORM, you can set "abstract=True" on a Document subclass, so it won't create a collection, then inherit other docs from that. This is undocumented but was in the source.
(2) Using the pre-save-post-validate signal
(3) using the builtin delta-tracking to find out of it has really changed.
There's still one annoying bit: I need to wire a signal to each of my concrete Document subclasses. If I had ten different kinds of document/model in my project, they would each need a signal. I can't wire it once to my "MyBase" class and make things "just work" yet by inheriting from it.
I suspect others might want to do this, and I wonder if there are any "hooks" I have missed - e.g. methods intended to be overridden?
Working script below....
"""Trying to create an abstract base class which causes stuff to happen
on saving, when the document has changed
Just need to eliminate the nasty signal
"""
import json
from pprint import pprint
from mongoengine import signals
from datetime import datetime
from mongoengine import *
class MyBase(Document):
"I want lots of classes to inherit from this"
modified = DateTimeField()
revision = IntField(default=0)
meta = {'abstract': True}
def _bump_revision_if_changed(self):
ch_set, ch_unset = self._delta()
if ch_set or ch_unset:
self.modified = datetime.utcnow()
self.revision += 1
print "saved revision %d, delta was: %s" % (self.revision, (ch_set, ch_unset))
else:
print "saved revision %d, no change" % self.revision
@classmethod
def pre_save_post_validate(cls, sender, document, **kwargs):
print "pre_save_post_validate fired"
cls._bump_revision_if_changed(document)
class Pet(EmbeddedDocument):
name = StringField()
kind = StringField()
class Person(MyBase):
gender = StringField(max_length=1,required=True, default="M")
first_name = StringField(max_length=30)
last_name = StringField(max_length=30)
pets = ListField(EmbeddedDocumentField(Pet))
signals.pre_save_post_validation.connect(MyBase.pre_save_post_validate, sender=Person)
if __name__=='__main__':
connect('mongorecalc')
print "connected"
Person.drop_collection()
p = Person(gender="M", first_name="Fred", last_name="Flintstone", pets=[
Pet(name="dino", kind="saurus")
])
print "creating"
p.save()
p.gender = "F"
p.first_name = "Wilma"
print "made changes to", p._get_changed_fields(), "..."
p.save()
p.pets[0].kind = "lizard"
print
print "made changes to embedded doc"
p.save()
print
print "save after no more changes"
p.save()
print
print "convert to json"
pprint(p.to_json())