Repository: MongoEngine/mongoengine

Issue:
'str' object has no attribute 'lookup_member'
Hello! If the dynamic lookup is composed of more than two parts then it fails.

``` python
from mongoengine import *
connect('mongoengine')

class Page(DynamicDocument):
    title = StringField(max_length=200, required=True)

print Page.objects.filter(field1__field2__field3=1)
```

Traceback:

```
  File "test_mongo.py", line 12, in <module>
    print Page.objects.filter(field1__field2__field3=1)
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/queryset.py", line 58, in __repr__
    self._populate_cache()
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/queryset.py", line 93, in _populate_cache
    self._result_cache.append(self.next())
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 1137, in next
    raw_doc = self._cursor.next()
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 1182, in _cursor
    self._cursor_obj = self._collection.find(self._query,
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/base.py", line 1215, in _query
    self._mongo_query = self._query_obj.to_query(self._document)
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 92, in to_query
    query = query.accept(QueryCompilerVisitor(document))
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 157, in accept
    return visitor.visit_query(self)
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/visitor.py", line 80, in visit_query
    return transform.query(self.document, **query.query)
  File "/home/test/.virtualenvs/test_env/lib/python2.7/site-packages/mongoengine/queryset/transform.py", line 59, in query
    raise InvalidQueryError(e)
mongoengine.errors.InvalidQueryError: 'str' object has no attribute 'lookup_member'
```

This worked for me, but I don't know for sure.

``` python
class DynamicField(BaseField):
    ...
    def lookup_member(self, member_name):
        return DynamicField(db_field=member_name)
    ...
```



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
@@ -935,6 +935,7 @@ def _lookup_field(cls, parts):
         """
 
         ListField = _import_class("ListField")
+        DynamicField = _import_class('DynamicField')
 
         if not isinstance(parts, (list, tuple)):
             parts = [parts]
@@ -944,7 +945,6 @@ def _lookup_field(cls, parts):
         for field_name in parts:
             # Handle ListField indexing:
             if field_name.isdigit() and isinstance(field, ListField):
-                new_field = field.field
                 fields.append(field_name)
                 continue
 
@@ -956,11 +956,9 @@ def _lookup_field(cls, parts):
                 if field_name in cls._fields:
                     field = cls._fields[field_name]
                 elif cls._dynamic:
-                    DynamicField = _import_class('DynamicField')
                     field = DynamicField(db_field=field_name)
                 elif cls._meta.get("allow_inheritance", False) or cls._meta.get("abstract", False):
                     # 744: in case the field is defined in a subclass
-                    field = None
                     for subcls in cls.__subclasses__():
                         try:
                             field = subcls._lookup_field([field_name])[0]
@@ -982,6 +980,9 @@ def _lookup_field(cls, parts):
                                       '__'.join(parts))
                 if hasattr(getattr(field, 'field', None), 'lookup_member'):
                     new_field = field.field.lookup_member(field_name)
+                elif cls._dynamic and (isinstance(field, DynamicField) or
+                                           getattr(getattr(field, 'document_type'), '_dynamic')):
+                    new_field = DynamicField(db_field=field_name)
                 else:
                     # Look up subfield on the previous field or raise
                     try:
@@ -991,17 +992,8 @@ def _lookup_field(cls, parts):
                                           'on the field {}'.format(
                                               field_name, field.name))
                 if not new_field and isinstance(field, ComplexBaseField):
-                    if hasattr(field.field, 'document_type') and cls._dynamic \
-                            and field.field.document_type._dynamic:
-                        DynamicField = _import_class('DynamicField')
-                        new_field = DynamicField(db_field=field_name)
-                    else:
-                        fields.append(field_name)
-                        continue
-                elif not new_field and hasattr(field, 'document_type') and cls._dynamic \
-                        and field.document_type._dynamic:
-                    DynamicField = _import_class('DynamicField')
-                    new_field = DynamicField(db_field=field_name)
+                    fields.append(field_name)
+                    continue
                 elif not new_field:
                     raise LookUpError('Cannot resolve field "%s"'
                                       % field_name)


Test patch:
diff --git a/tests/document/dynamic.py b/tests/document/dynamic.py
--- a/tests/document/dynamic.py
+++ b/tests/document/dynamic.py
@@ -129,6 +129,15 @@ def test_complex_data_lookups(self):
 
         self.assertEqual(1, self.Person.objects(misc__hello='world').count())
 
+    def test_three_level_complex_data_lookups(self):
+        """Ensure you can query three level document dynamic fields"""
+        p = self.Person()
+        p.misc = {'hello': {'hello2': 'world'}}
+        p.save()
+        # from pprint import pprint as pp; import pdb; pdb.set_trace();
+        print self.Person.objects(misc__hello__hello2='world')
+        self.assertEqual(1, self.Person.objects(misc__hello__hello2='world').count())
+
     def test_complex_embedded_document_validation(self):
         """Ensure embedded dynamic documents may be validated"""
         class Embedded(DynamicEmbeddedDocument):