Repository: MongoEngine/mongoengine

Issue:
no_dereference() disables dereferencing in other documents referencing document
As in this example:

```python
import os

from mongoengine import Document, ReferenceField, connect


connect(host=os.getenv('DB'))


class A(Document):
    pass


class B(Document):
    a = ReferenceField(A)


class C(Document):
    b = ReferenceField(B)


a = A().save()
b = B(a=a).save()
c = C(b=b).save()

c = C.objects.get(id=c.id)

B.objects.no_dereference().get(id=c.b.id)
print(isinstance(c.b.a, A))  # prints False

B.objects.get(id=c.b.id)
print(isinstance(c.b.a, A))  # prints True
```

Is this expected behavior? Outside of the context manager, the documentation says nothing about `no_dereference()` affecting other querysets.


Discussion:
The `no_dereference()` example above works as I expect if I query using `b.id` instead:

```python
c = C.objects.get(id=c.id)
B.objects.no_dereference().get(id=b.id)
print(isinstance(c.b.a, A))  # prints True
```

It looks like mongoengine is performing some kind of optimization and incidentally changing the object I'm using to query.

Resolving patch:
diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py
--- a/mongoengine/base/document.py
+++ b/mongoengine/base/document.py
@@ -700,7 +700,7 @@ def _from_son(cls, son, _auto_dereference=True, only_fields=None, created=False)
 
         fields = cls._fields
         if not _auto_dereference:
-            fields = copy.copy(fields)
+            fields = copy.deepcopy(fields)
 
         for field_name, field in fields.iteritems():
             field._auto_dereference = _auto_dereference
diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py
--- a/mongoengine/base/fields.py
+++ b/mongoengine/base/fields.py
@@ -266,13 +266,15 @@ def __get__(self, instance, owner):
         ReferenceField = _import_class('ReferenceField')
         GenericReferenceField = _import_class('GenericReferenceField')
         EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
-        dereference = (self._auto_dereference and
+
+        auto_dereference = instance._fields[self.name]._auto_dereference
+
+        dereference = (auto_dereference and
                        (self.field is None or isinstance(self.field,
                                                          (GenericReferenceField, ReferenceField))))
 
         _dereference = _import_class('DeReference')()
 
-        self._auto_dereference = instance._fields[self.name]._auto_dereference
         if instance._initialised and dereference and instance._data.get(self.name):
             instance._data[self.name] = _dereference(
                 instance._data.get(self.name), max_depth=1, instance=instance,
@@ -293,7 +295,7 @@ def __get__(self, instance, owner):
             value = BaseDict(value, instance, self.name)
             instance._data[self.name] = value
 
-        if (self._auto_dereference and instance._initialised and
+        if (auto_dereference and instance._initialised and
                 isinstance(value, (BaseList, BaseDict)) and
                 not value._dereferenced):
             value = _dereference(
diff --git a/mongoengine/fields.py b/mongoengine/fields.py
--- a/mongoengine/fields.py
+++ b/mongoengine/fields.py
@@ -1104,9 +1104,9 @@ def __get__(self, instance, owner):
 
         # Get value from document instance if available
         value = instance._data.get(self.name)
-        self._auto_dereference = instance._fields[self.name]._auto_dereference
+        auto_dereference = instance._fields[self.name]._auto_dereference
         # Dereference DBRefs
-        if self._auto_dereference and isinstance(value, DBRef):
+        if auto_dereference and isinstance(value, DBRef):
             if hasattr(value, 'cls'):
                 # Dereference using the class type specified in the reference
                 cls = get_document(value.cls)
@@ -1267,6 +1267,7 @@ def __get__(self, instance, owner):
         # Get value from document instance if available
         value = instance._data.get(self.name)
         self._auto_dereference = instance._fields[self.name]._auto_dereference
+
         # Dereference DBRefs
         if self._auto_dereference and isinstance(value, DBRef):
             dereferenced = self.document_type._get_db().dereference(value)


Test patch:
diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py
--- a/tests/queryset/queryset.py
+++ b/tests/queryset/queryset.py
@@ -4674,11 +4674,69 @@ class User(Document):
         User(name="Bob Dole", organization=whitehouse).save()
 
         qs = User.objects()
+        qs_user = qs.first()
+
         self.assertIsInstance(qs.first().organization, Organization)
-        self.assertNotIsInstance(qs.no_dereference().first().organization, Organization)
-        self.assertNotIsInstance(qs.no_dereference().get().organization, Organization)
+
+        self.assertIsInstance(qs.no_dereference().first().organization, DBRef)
+
+        self.assertIsInstance(qs_user.organization, Organization)
         self.assertIsInstance(qs.first().organization, Organization)
 
+    def test_no_dereference_internals(self):
+        # Test the internals on which queryset.no_dereference relies on
+        class Organization(Document):
+            name = StringField()
+
+        class User(Document):
+            organization = ReferenceField(Organization)
+
+        User.drop_collection()
+        Organization.drop_collection()
+
+        cls_organization_field = User.organization
+        self.assertTrue(cls_organization_field._auto_dereference, True)  # default
+
+        org = Organization(name="whatever").save()
+        User(organization=org).save()
+
+        qs_no_deref = User.objects().no_dereference()
+        user_no_deref = qs_no_deref.first()
+        self.assertFalse(qs_no_deref._auto_dereference)
+
+        # Make sure the instance field is different from the class field
+        instance_org_field = user_no_deref._fields['organization']
+        self.assertIsNot(instance_org_field, cls_organization_field)
+        self.assertFalse(instance_org_field._auto_dereference)
+
+        self.assertIsInstance(user_no_deref.organization, DBRef)
+        self.assertTrue(cls_organization_field._auto_dereference, True)  # Make sure the class Field wasn't altered
+
+    def test_no_dereference_no_side_effect_on_existing_instance(self):
+        # Relates to issue #1677 - ensures no regression of the bug
+
+        class Organization(Document):
+            name = StringField()
+
+        class User(Document):
+            organization = ReferenceField(Organization)
+
+        User.drop_collection()
+        Organization.drop_collection()
+
+        org = Organization(name="whatever").save()
+        User(organization=org).save()
+
+        qs = User.objects()
+        user = qs.first()
+
+        qs_no_deref = User.objects().no_dereference()
+        user_no_deref = qs_no_deref.first()
+
+        no_derf_org = user_no_deref.organization    # was triggering the bug
+        self.assertIsInstance(no_derf_org, DBRef)
+        self.assertIsInstance(user.organization, Organization)
+
     def test_no_dereference_embedded_doc(self):
 
         class User(Document):
@@ -4693,7 +4751,7 @@ class Organization(Document):
             members = ListField(EmbeddedDocumentField(Member))
             ceo = ReferenceField(User)
             member = EmbeddedDocumentField(Member)
-            admin = ListField(ReferenceField(User))
+            admins = ListField(ReferenceField(User))
 
         Organization.drop_collection()
         User.drop_collection()
@@ -4703,16 +4761,22 @@ class Organization(Document):
 
         member = Member(name="Flash", user=user)
 
-        company = Organization(name="Mongo Inc", ceo=user, member=member)
-        company.admin.append(user)
-        company.members.append(member)
+        company = Organization(name="Mongo Inc",
+                               ceo=user,
+                               member=member,
+                               admins=[user],
+                               members=[member])
         company.save()
 
-        result = Organization.objects().no_dereference().first()
+        org = Organization.objects().no_dereference().first()
+
+        self.assertNotEqual(id(org._fields['admins']), id(Organization.admins))
+        self.assertFalse(org._fields['admins']._auto_dereference)
 
-        self.assertIsInstance(result.admin[0], (DBRef, ObjectId))
-        self.assertIsInstance(result.member.user, (DBRef, ObjectId))
-        self.assertIsInstance(result.members[0].user, (DBRef, ObjectId))
+        admin = org.admins[0]
+        self.assertIsInstance(admin, DBRef)
+        self.assertIsInstance(org.member.user, DBRef)
+        self.assertIsInstance(org.members[0].user, DBRef)
 
     def test_cached_queryset(self):
         class Person(Document):