Hi Rozza,
I just implemented a first draft. It's available for testing and commenting on my github clone of mongoengine:
It's not exactly what I promised, but it does the job. Basically, the idea is that every field can have a list of version_locks, which are the names of IntFields that should be checked and incremented whenever that field is modified and the document is saved.
Here's an example:
class Task(EmbeddedDocument):
description = StringField()
class TodoList(Document):
name = StringField()
tasks_version = IntField(db_field = "tver")
tasks = ListField(EmbeddedDocumentField("Task", db_field="t"), version_locks = ["tasks_version"])
Now whenever a task list is saved, if the tasks field is modified (the list itself or any task in the list), then the tasks_version IntField is checked against the value in mongodb, and incremented if it has the expected value. If it's not the expected value, then a VersionLockError is raised. For example (taken from tests/test_version_locks.py):
todo_list1 = self.TodoList(name='Test').save()
self.assertIsNone(todo_list1.tasks_version)
self.assertIsNone(todo_list2.tasks_version)
todo_list1.save()
self.assertIsNone(todo_list1.tasks_version)
self.assertIsNone(todo_list2.tasks_version)
todo_list2.save()
self.assertIsNone(todo_list1.tasks_version)
self.assertIsNone(todo_list2.tasks_version)
todo_list1.tasks = [self.Task(description = "Buy presents")]
todo_list1.save()
self.assertEquals(todo_list1.tasks_version, 1)
self.assertIsNone(todo_list2.tasks_version)
todo_list2.save()
self.assertEquals(todo_list1.tasks_version, 1)
self.assertIsNone(todo_list2.tasks_version)
todo_list2.tasks = [self.Task(description = "Party")]
self.assertRaises(VersionLockError, todo_list2.save)
self.assertEquals(todo_list1.tasks_version, 1)
self.assertIsNone(todo_list2.tasks_version)
todo_list2.reload()
self.assertEquals(todo_list1.tasks_version, 1)
self.assertEquals(todo_list2.tasks_version, 1)
todo_list2.tasks.append(self.Task(description = "Party"))
todo_list2.save()
self.assertEquals(todo_list1.tasks_version, 1)
self.assertEquals(todo_list2.tasks_version, 2)
As a special shortcut, setting the version_locks attribute in the document class's _meta dictionary applies the version_locks to all fields. For example:
class Task(EmbeddedDocument):
description = StringField()
class TodoList(Document):
meta = { "version_locks": ["version"] }
name = StringField()
version = IntField(db_field = "ver")
tasks = ListField(EmbeddedDocumentField("Task", db_field="t"))
Now anytime the document is saved, the version lock is checked & incremented. If its value is unexpected, a VersionLockError is raised:
todo_list1 = self.TodoList(name='Test').save()
todo_list1.save()
self.assertRaises(VersionLockError, todo_list2.save)
This is a very quick implementation, I'm new to both mongodb and mongoengine, so I hope I didn't mess everything up.
Caveat: for the moment, the version_locks should only be set on fields in root document classes, not in EmbeddedDocuments or dynamic documents.
Could you please check this out and tell me what you think ? I'll be happy to send you a pull request if you think the code is good enough for your standards.
BTW, is there a specific reason why updates and removals were done in two different calls to the update method on the collection in the document's save method ? I now do both in a single update, the tests still pass, I hope I haven't broken the law ! ;-)
Cheers,
Aurélien