Repository: MongoEngine/mongoengine

Issue:
Improve error detection/message for invalid query operator
The story: I did something dumb -- I executed a query with an invalid query operator due to a typo in my code.  

The results had me scratching my head for quite a while until I discovered my error:

Consider the following snippet:

```
class Event(Document):
    title = StringField()
    date = DateTimeField()
    location = GeoPointField()

box = [(35.0, -125.0), (40.0, -100.0)]
# I *meant* to execute location__within_box=box 
events = Event.objects(location__within=box)
assert events.count() == 1
```

The call to `Event.objects()` succeeds with no error.
The call to `events.count()` results in a less-than-obvious error message:

```
AttributeError: 'GeoPointField' object has no attribute 'lookup_member'
```

I would have expected an exception raised in `Event.objects()` indicating use of an unknown query operator `location__within`.  A more relevant exception raised from that method call would have been 'friendlier', helping identify the source of the problem.

Per @rozza on Google Groups:

> This could definitely be improved, the `lookup_member` message is because fields should implement a way to query sub documents, `location__within` means the location field should be able to inspect itself and return the within value.

Given that fact, perhaps the only improvement possible is a more helpful exception than the `AttributeError` raised when realizing the query.

Full stack trace: 

```
Traceback (most recent call last):
  File "bug.py", line 47, in <module>
    assert events.count() == 1
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 1033, in count
    return self._cursor.count(with_limit_and_skip=True)
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 607, in _cursor
    self._cursor_obj = self._collection.find(self._query,
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 389, in _query
    self._mongo_query = self._query_obj.to_query(self._document)
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 212, in to_query
    query = query.accept(QueryCompilerVisitor(document))
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 277, in accept
    return visitor.visit_query(self)
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 169, in visit_query
    return QuerySet._transform_query(self.document, **query.query)
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 727, in _transform_query
    fields = QuerySet._lookup_field(_doc_cls, parts)
  File "/home/inactivist/.virtualenvs/foo/local/lib/python2.7/site-packages/mongoengine/queryset.py", line 673, in _lookup_field
    new_field = field.lookup_member(field_name)
AttributeError: 'GeoPointField' object has no attribute 'lookup_member'
```

(Taken from [this google groups post](https://groups.google.com/d/msg/mongoengine-users/jPqoJEeOtdc/ae3cG9zMSeQJ))



Discussion:
+1. And to see which lookup member was tried to be accessed.

I wonder if this has been fixed, but if not, it should, as #932 fixed it for delta


Resolving patch:
diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py
--- a/mongoengine/base/document.py
+++ b/mongoengine/base/document.py
@@ -983,8 +983,13 @@ def _lookup_field(cls, parts):
                 if hasattr(getattr(field, 'field', None), 'lookup_member'):
                     new_field = field.field.lookup_member(field_name)
                 else:
-                   # Look up subfield on the previous field
-                    new_field = field.lookup_member(field_name)
+                    # Look up subfield on the previous field or raise
+                    try:
+                        new_field = field.lookup_member(field_name)
+                    except AttributeError:
+                        raise LookUpError('Cannot resolve subfield or operator {} '
+                                          '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:
diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py
--- a/mongoengine/queryset/transform.py
+++ b/mongoengine/queryset/transform.py
@@ -44,7 +44,7 @@ def query(_doc_cls=None, _field_operation=False, **query):
         if len(parts) > 1 and parts[-1] in MATCH_OPERATORS:
             op = parts.pop()
 
-        #if user escape field name by __
+        # if user escape field name by __
         if len(parts) > 1 and parts[-1] == "":
             parts.pop()
 


Test patch:
diff --git a/tests/queryset/transform.py b/tests/queryset/transform.py
--- a/tests/queryset/transform.py
+++ b/tests/queryset/transform.py
@@ -224,6 +224,15 @@ class Doc(Document):
         self.assertEqual(1, Doc.objects(item__type__="axe").count())
         self.assertEqual(1, Doc.objects(item__name__="Heroic axe").count())
 
+    def test_understandable_error_raised(self):
+        class Event(Document):
+            title = StringField()
+            location = GeoPointField()
+
+        box = [(35.0, -125.0), (40.0, -100.0)]
+        # I *meant* to execute location__within_box=box
+        events = Event.objects(location__within=box)
+        self.assertRaises(InvalidQueryError, lambda: events.count())
 
 if __name__ == '__main__':
     unittest.main()