Repository: MongoEngine/mongoengine

Issue:
Modification to a generic-referenced document is not ignored when computing the changed_fields
Came across this while I was fixing something else. If you do modification to a referenced document that is stored as a GenericReferenceField, the changes will actually be tracked in the parent document. It is currently harmless (as it will try to unset a field that does not exist) but better to fix this. See below:
```
class Doc1(me.Document):
    name = me.StringField()

class Doc2(me.Document):
    refs = me.ListField(me.GenericReferenceField())

doc1 = Doc1(name='garbage1').save()
doc2 = Doc2(refs=[doc1]).save()

doc2.refs[0].name = 'garbage2'    # triggers the issue

print doc2._get_changed_fields()    # ['refs.0.name'] <-- should be an empty list
print doc2._delta()                 # ({}, {'refs.0.name': 1})
```
I'll push a PR shortly to fix this


Discussion:

Resolving patch:
diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py
--- a/mongoengine/base/document.py
+++ b/mongoengine/base/document.py
@@ -528,6 +528,7 @@ def _get_changed_fields(self, inspected=None):
         EmbeddedDocument = _import_class('EmbeddedDocument')
         DynamicEmbeddedDocument = _import_class('DynamicEmbeddedDocument')
         ReferenceField = _import_class('ReferenceField')
+        GenericReferenceField = _import_class('GenericReferenceField')
         SortedListField = _import_class('SortedListField')
 
         changed_fields = []
@@ -560,7 +561,7 @@ def _get_changed_fields(self, inspected=None):
             elif (isinstance(data, (list, tuple, dict)) and
                     db_field_name not in changed_fields):
                 if (hasattr(field, 'field') and
-                        isinstance(field.field, ReferenceField)):
+                        isinstance(field.field, (ReferenceField, GenericReferenceField))):
                     continue
                 elif isinstance(field, SortedListField) and field._ordering:
                     # if ordering is affected whole list is changed
diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py
--- a/mongoengine/queryset/base.py
+++ b/mongoengine/queryset/base.py
@@ -1872,8 +1872,8 @@ def field_path_sub(match):
             # Substitute the correct name for the field into the javascript
             return '.'.join([f.db_field for f in fields])
 
-        code = re.sub(u'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
-        code = re.sub(u'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
+        code = re.sub(r'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
+        code = re.sub(r'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
                       code)
         return code
 


Test patch:
diff --git a/tests/fields/fields.py b/tests/fields/fields.py
--- a/tests/fields/fields.py
+++ b/tests/fields/fields.py
@@ -1187,7 +1187,7 @@ def reset_post():
         # aka 'del list[index]'
         # aka 'operator.delitem(list, index)'
         reset_post()
-        del post.info[2] # del from middle ('2')
+        del post.info[2]  # del from middle ('2')
         self.assertEqual(post.info, ['0', '1', '3', '4', '5'])
         post.save()
         post.reload()
@@ -1197,7 +1197,7 @@ def reset_post():
         # aka 'del list[i:j]'
         # aka 'operator.delitem(list, slice(i,j))'
         reset_post()
-        del post.info[1:3] # removes '1', '2'
+        del post.info[1:3]  # removes '1', '2'
         self.assertEqual(post.info, ['0', '3', '4', '5'])
         post.save()
         post.reload()
@@ -2816,7 +2816,32 @@ class Doc(Document):
         doc = Doc.objects.get(ref=DBRef('doc', doc1.pk))
         self.assertEqual(doc, doc2)
 
-    def test_generic_reference_filter_by_objectid(self):
+    def test_generic_reference_is_not_tracked_in_parent_doc(self):
+        """Ensure that modifications of related documents (through generic reference) don't influence
+        the owner changed fields (#1934)
+        """
+        class Doc1(Document):
+            name = StringField()
+
+        class Doc2(Document):
+            ref = GenericReferenceField()
+            refs = ListField(GenericReferenceField())
+
+        Doc1.drop_collection()
+        Doc2.drop_collection()
+
+        doc1 = Doc1(name='garbage1').save()
+        doc11 = Doc1(name='garbage11').save()
+        doc2 = Doc2(ref=doc1, refs=[doc11]).save()
+
+        doc2.ref.name = 'garbage2'
+        self.assertEqual(doc2._get_changed_fields(), [])
+
+        doc2.refs[0].name = 'garbage3'
+        self.assertEqual(doc2._get_changed_fields(), [])
+        self.assertEqual(doc2._delta(), ({}, {}))
+
+    def test_generic_reference_field(self):
         """Ensure we can search for a specific generic reference by
         providing its DBRef.
         """
@@ -4345,6 +4370,7 @@ class MyFailingDoc(Document):
             class MyFailingdoc2(Document):
                 emb = EmbeddedDocumentField('MyDoc')
 
+
 class CachedReferenceFieldTest(MongoDBTestCase):
 
     def test_cached_reference_field_get_and_save(self):