Repository: MongoEngine/mongoengine

Issue:
error while using "db_field" parameter in multi-level embedded documents
The following code is an example to reconstruct what's going wrong. The main problem seems to be the use of `db_field` in the second level of the embedded document (In the example this is `class B`)

``` python
data = {'b': {'c': [{'x': 1.0, 'y': 2.0}]}}

class C(EmbeddedDocument):
    x = FloatField(db_field='fx')
    y = FloatField(db_field='fy')

class B(EmbeddedDocument):
    c = ListField(EmbeddedDocumentField(C), db_field='fc')

class A(EmbeddedDocument):
    b = EmbeddedDocumentField(B, db_field='fb')

a = A(**data)
a.validate()
```

If I run this, it throws an exception:

``` python
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/mongoengine/base/document.py", line 424, in validate
    raise ValidationError(message, errors=errors)
mongoengine.errors.ValidationError: ValidationError (A:None) (c.Invalid embedded document instance provided to an EmbeddedDocumentField: ['b'])
```

But it runs through if I remove the `db_field` attribute in `class B`:

``` python
class B(EmbeddedDocument):
    c = ListField(EmbeddedDocumentField(C))
```

While digging in to this problem I found out that the exception is actually correct when saying the Document has the wrong format. 

while running a simple print on the working an non working version, the type differs. 

``` python
print(a.b.c[0])
print(type(a.b.c[0]))

# wrong
{'x': 1.0, 'y': 2.0}
<class 'mongoengine.base.datastructures.BaseDict'>

# good
C object
<class '__main__.C'>
```



Discussion:
I narrowed it down a bit and found in `base/document.py` the following block around line 680

``` python
for field_name, field in fields.iteritems():
    field._auto_dereference = _auto_dereference
    if field.db_field in data:
        value = data[field.db_field]
```

As far as I understand whats going on the problem resides in the line:

``` python
if field.db_field in data:
```

where `db_field` contains the supposed `db_field` name (ex: `fc`) but in data we have only `c`. 

So i was looking up, where data is coming from and found:

``` python
data = dict(("%s" % key, value) for key, value in son.iteritems())
```

there is obviously no conversation from python field name to db field name. So I came up with a little patch:

``` python
         # class if unavailable
         class_name = son.get('_cls', cls._class_name)
-        data = dict(("%s" % key, value) for key, value in son.iteritems())
+        data = dict(("%s" % cls._db_field_map.get(key,key), value) for key, value in son.items())

         # Return correct subclass for document type
         if class_name != cls._class_name:
```

This seems to work for my short testing, but I'm actually not sure if I will break something later or if this is good practice. I'm also not sure, if I'm actually supposed to use `_db_field_map` here and if this data is reliable (= always correctly populated) 

Before I create a pull request, it would be nice if a mongoengine developer could give me some input. Thanks a lot!!

nice job @ms5 ! Would you eventually make a PR out of this? Formalising also the description above inside a test? It would be great !

ok, no problem. give me some time to write the tests, I'll then create a pull request. 


Resolving patch:
diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py
--- a/mongoengine/base/document.py
+++ b/mongoengine/base/document.py
@@ -684,8 +684,13 @@ def _from_son(cls, son, _auto_dereference=True, only_fields=None, created=False)
         # class if unavailable
         class_name = son.get('_cls', cls._class_name)
 
-        # Convert SON to a dict, making sure each key is a string
-        data = {str(key): value for key, value in son.iteritems()}
+        # Convert SON to a data dict, making sure each key is a string and
+        # corresponds to the right db field.
+        data = {}
+        for key, value in son.iteritems():
+            key = str(key)
+            key = cls._db_field_map.get(key, key)
+            data[key] = value
 
         # Return correct subclass for document type
         if class_name != cls._class_name:


Test patch:
diff --git a/tests/fields/fields.py b/tests/fields/fields.py
--- a/tests/fields/fields.py
+++ b/tests/fields/fields.py
@@ -1883,6 +1883,51 @@ class Test(Document):
         doc = self.db.test.find_one()
         self.assertEqual(doc['x']['i'], 2)
 
+    def test_double_embedded_db_field(self):
+        """Make sure multiple layers of embedded docs resolve db fields
+        properly and can be initialized using dicts.
+        """
+        class C(EmbeddedDocument):
+            txt = StringField()
+
+        class B(EmbeddedDocument):
+            c = EmbeddedDocumentField(C, db_field='fc')
+
+        class A(Document):
+            b = EmbeddedDocumentField(B, db_field='fb')
+
+        a = A(
+            b=B(
+                c=C(txt='hi')
+            )
+        )
+        a.validate()
+
+        a = A(b={'c': {'txt': 'hi'}})
+        a.validate()
+
+    def test_double_embedded_db_field_from_son(self):
+        """Make sure multiple layers of embedded docs resolve db fields
+        from SON properly.
+        """
+        class C(EmbeddedDocument):
+            txt = StringField()
+
+        class B(EmbeddedDocument):
+            c = EmbeddedDocumentField(C, db_field='fc')
+
+        class A(Document):
+            b = EmbeddedDocumentField(B, db_field='fb')
+
+        a = A._from_son(SON([
+            ('fb', SON([
+                ('fc', SON([
+                    ('txt', 'hi')
+                ]))
+            ]))
+        ]))
+        self.assertEqual(a.b.c.txt, 'hi')
+
     def test_embedded_document_validation(self):
         """Ensure that invalid embedded documents cannot be assigned to
         embedded document fields.