Repository: MongoEngine/mongoengine

Issue:
Issues with MapField and _fields_to_dbfields
I have a model defined with a MapField.

``` python
class Race(Document):
    raceUID = StringField(primary_key=True)
    electionDate = StringField()
    statePostal = StringField()

    historical_reporting_units = MapField(ListField(EmbeddedDocumentField(ReportingUnit)))
    current_reporting_units = MapField(field=EmbeddedDocumentField(ReportingUnit))
```

When trying to project the result to only include a certain key within the MapField like so: `Race.objects(raceUID="2016-03-01-MA-24548").only("current_reporting_units.state-MA").first()`

I get the following stack trace. I believe it should skip trying to resolve the dbfield attribute for MapFields.

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-9098a246470f> in <module>()
      1 from electionbot2016.models import Race, ReportingUnit
----> 2 race = Race.objects(raceUID="2016-03-01-MA-24548").only("current_reporting_units.state-MA").first()
      3 for unit in race.current_reporting_units:
      4     print(unit)

/Users/johria/anaconda/lib/python3.5/site-packages/mongoengine/queryset/base.py in only(self, *fields)
    810         fields = dict([(f, QueryFieldList.ONLY) for f in fields])
    811         self.only_fields = list(fields.keys())
--> 812         return self.fields(True, **fields)
    813 
    814     def exclude(self, *fields):

/Users/johria/anaconda/lib/python3.5/site-packages/mongoengine/queryset/base.py in fields(self, _only_called, **kwargs)
    864         for value, group in itertools.groupby(fields, lambda x: x[1]):
    865             fields = [field for field, value in group]
--> 866             fields = queryset._fields_to_dbfields(fields)
    867             queryset._loaded_fields += QueryFieldList(
    868                 fields, value=value, _only_called=_only_called)

/Users/johria/anaconda/lib/python3.5/site-packages/mongoengine/queryset/base.py in _fields_to_dbfields(self, fields)
   1659             try:
   1660                 field = ".".join(f.db_field for f in
-> 1661                                  document._lookup_field(field.split('.')))
   1662                 ret.append(field)
   1663             except LookUpError as err:

/Users/johria/anaconda/lib/python3.5/site-packages/mongoengine/queryset/base.py in <genexpr>(.0)
   1658         for field in fields:
   1659             try:
-> 1660                 field = ".".join(f.db_field for f in
   1661                                  document._lookup_field(field.split('.')))
   1662                 ret.append(field)

AttributeError: 'str' object has no attribute 'db_field'
```

Am I using the `only` function wrong? Can I manually input a raw projection for the timebeing?

EDIT: added `current_reporting_units` to Race model



Discussion:
I worked around the issue for now by manually changing the `_loaded_fields` attribute.

``` python
query = Race.objects(raceUID="2016-03-01-MA-24548")
query._loaded_fields.fields.add('historical_reporting_units.state-MA')
race = query.first()
```

Hi.

`current_reporting_units` is not defined in the document. At least in the definition you're showing, there is only `historical_reporting_units`, no `current_reporting_units` field.

What do you expect from 

```
Race.objects(raceUID="2016-03-01-MA-24548").only("current_reporting_units.state-MA")
```

?

``` python
class Race(Document):
    raceUID = StringField(primary_key=True)
    electionDate = StringField()
    statePostal = StringField()

    historical_reporting_units = MapField(ListField(EmbeddedDocumentField(ReportingUnit)))
    current_reporting_units = MapField(field=EmbeddedDocumentField(ReportingUnit))
```

sorry I missed that line

Regardless, it would error out on:

`Race.objects(raceUID="2016-03-01-MA-24548").only("historical_reporting_units.state-MA")`

as well because they are both MapFields

@lafrech would you like me to reiterate the issue?

What do you mean by "reiterate"?

If you mean provide a complete test case, maybe even a branch with this test added to the automatic tests, you are more than welcome.

Thanks.


Resolving patch:
diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py
--- a/mongoengine/queryset/base.py
+++ b/mongoengine/queryset/base.py
@@ -1731,14 +1731,14 @@ def _fields_to_dbfields(self, fields):
                           for x in document._subclasses][1:]
         for field in fields:
             try:
-                field = '.'.join(f.db_field for f in
+                field = '.'.join(f if isinstance(f, six.string_types) else f.db_field for f in
                                  document._lookup_field(field.split('.')))
                 ret.append(field)
             except LookUpError as err:
                 found = False
                 for subdoc in subclasses:
                     try:
-                        subfield = '.'.join(f.db_field for f in
+                        subfield = '.'.join(f if isinstance(f, six.string_types) else f.db_field for f in
                                             subdoc._lookup_field(field.split('.')))
                         ret.append(subfield)
                         found = True


Test patch:
diff --git a/tests/queryset/field_list.py b/tests/queryset/field_list.py
--- a/tests/queryset/field_list.py
+++ b/tests/queryset/field_list.py
@@ -197,14 +197,18 @@ class Comment(EmbeddedDocument):
             title = StringField()
             text = StringField()
 
+        class VariousData(EmbeddedDocument):
+            some = BooleanField()
+
         class BlogPost(Document):
             content = StringField()
             author = EmbeddedDocumentField(User)
             comments = ListField(EmbeddedDocumentField(Comment))
+            various = MapField(field=EmbeddedDocumentField(VariousData))
 
         BlogPost.drop_collection()
 
-        post = BlogPost(content='Had a good coffee today...')
+        post = BlogPost(content='Had a good coffee today...', various={'test_dynamic':{'some': True}})
         post.author = User(name='Test User')
         post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
         post.save()
@@ -215,6 +219,9 @@ class BlogPost(Document):
         self.assertEqual(obj.author.name, 'Test User')
         self.assertEqual(obj.comments, [])
 
+        obj = BlogPost.objects.only('various.test_dynamic.some').get()
+        self.assertEqual(obj.various["test_dynamic"].some, True)
+
         obj = BlogPost.objects.only('content', 'comments.title',).get()
         self.assertEqual(obj.content, 'Had a good coffee today...')
         self.assertEqual(obj.author, None)