From 175659628d2150556ee33e19dec088816802ddbe Mon Sep 17 00:00:00 2001 From: Catstyle Date: Fri, 10 Apr 2015 11:31:31 +0800 Subject: [PATCH] fix mark_as_changed: handle higher/lower level changed fields correctly to avoid conflict update error --- AUTHORS | 1 + docs/changelog.rst | 1 + mongoengine/base/document.py | 14 +++++++++- tests/document/delta.py | 50 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 0fd40b51..af87d503 100644 --- a/AUTHORS +++ b/AUTHORS @@ -220,3 +220,4 @@ that much better: * J. Fernando Sánchez (https://github.com/balkian) * Michael Chase (https://github.com/rxsegrxup) * Eremeev Danil (https://github.com/elephanter) + * Catstyle Lee (https://github.com/Catstyle) diff --git a/docs/changelog.rst b/docs/changelog.rst index ba952d8e..188247e6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Changes in 0.9.X - DEV ====================== +- Fixed mark_as_changed to handle higher/lower level fields changed. #927 - ListField of embedded docs doesn't set the _instance attribute when iterating over it #914 - Support += and *= for ListField #595 - Use sets for populating dbrefs to dereference diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index b2310439..33a6d826 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -483,7 +483,19 @@ class BaseDocument(object): key = self._db_field_map.get(key, key) if key not in self._changed_fields: - self._changed_fields.append(key) + levels, idx = key.split('.'), 1 + while idx <= len(levels): + if '.'.join(levels[:idx]) in self._changed_fields: + break + idx += 1 + else: + self._changed_fields.append(key) + # remove lower level changed fields + level = '.'.join(levels[:idx]) + '.' + remove = self._changed_fields.remove + for field in self._changed_fields: + if field.startswith(level): + remove(field) def _clear_changed_fields(self): """Using get_changed_fields iterate and remove any fields that are diff --git a/tests/document/delta.py b/tests/document/delta.py index 58df2c82..7ec6ee56 100644 --- a/tests/document/delta.py +++ b/tests/document/delta.py @@ -735,6 +735,56 @@ class DeltaTest(unittest.TestCase): mydoc._clear_changed_fields() self.assertEqual([], mydoc._get_changed_fields()) + def test_lower_level_mark_as_changed(self): + class EmbeddedDoc(EmbeddedDocument): + name = StringField() + + class MyDoc(Document): + subs = MapField(EmbeddedDocumentField(EmbeddedDoc)) + + MyDoc.drop_collection() + + MyDoc().save() + + mydoc = MyDoc.objects.first() + mydoc.subs['a'] = EmbeddedDoc() + self.assertEqual(["subs.a"], mydoc._get_changed_fields()) + + subdoc = mydoc.subs['a'] + subdoc.name = 'bar' + + self.assertEqual(["name"], subdoc._get_changed_fields()) + self.assertEqual(["subs.a"], mydoc._get_changed_fields()) + mydoc.save() + + mydoc._clear_changed_fields() + self.assertEqual([], mydoc._get_changed_fields()) + + def test_upper_level_mark_as_changed(self): + class EmbeddedDoc(EmbeddedDocument): + name = StringField() + + class MyDoc(Document): + subs = MapField(EmbeddedDocumentField(EmbeddedDoc)) + + MyDoc.drop_collection() + + MyDoc(subs={'a': EmbeddedDoc(name='foo')}).save() + + mydoc = MyDoc.objects.first() + subdoc = mydoc.subs['a'] + subdoc.name = 'bar' + + self.assertEqual(["name"], subdoc._get_changed_fields()) + self.assertEqual(["subs.a.name"], mydoc._get_changed_fields()) + + mydoc.subs['a'] = EmbeddedDoc() + self.assertEqual(["subs.a"], mydoc._get_changed_fields()) + mydoc.save() + + mydoc._clear_changed_fields() + self.assertEqual([], mydoc._get_changed_fields()) + def test_referenced_object_changed_attributes(self): """Ensures that when you save a new reference to a field, the referenced object isn't altered"""