Added tests, documentation and simplified code

This commit is contained in:
Matthieu Rigal 2015-06-21 03:03:50 +02:00
parent 2d57dc0565
commit 5efabdcea3
5 changed files with 70 additions and 32 deletions

View File

@ -29,6 +29,7 @@ Changes in 0.9.X - DEV
- Added `BaseQuerySet.aggregate_sum` and `BaseQuerySet.aggregate_average` methods. - Added `BaseQuerySet.aggregate_sum` and `BaseQuerySet.aggregate_average` methods.
- Fix for delete with write_concern {'w': 0}. #1008 - Fix for delete with write_concern {'w': 0}. #1008
- Allow dynamic lookup for more than two parts. #882 - Allow dynamic lookup for more than two parts. #882
- Added support for min_distance on geo queries. #831
Changes in 0.9.0 Changes in 0.9.0
================ ================

View File

@ -146,9 +146,10 @@ The following were added in MongoEngine 0.8 for
loc.objects(point__near=[40, 5]) loc.objects(point__near=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [40, 5]}) loc.objects(point__near={"type": "Point", "coordinates": [40, 5]})
You can also set the maximum distance in meters as well:: You can also set the maximum and/or the minimum distance in meters as well::
loc.objects(point__near=[40, 5], point__max_distance=1000) loc.objects(point__near=[40, 5], point__max_distance=1000)
loc.objects(point__near=[40, 5], point__min_distance=100)
The older 2D indexes are still supported with the The older 2D indexes are still supported with the
:class:`~mongoengine.fields.GeoPointField`: :class:`~mongoengine.fields.GeoPointField`:
@ -168,7 +169,8 @@ The older 2D indexes are still supported with the
* ``max_distance`` -- can be added to your location queries to set a maximum * ``max_distance`` -- can be added to your location queries to set a maximum
distance. distance.
* ``min_distance`` -- can be added to your location queries to set a minimum
distance.
Querying lists Querying lists
-------------- --------------

View File

@ -6,7 +6,7 @@ from bson import SON
from mongoengine.base.fields import UPDATE_OPERATORS from mongoengine.base.fields import UPDATE_OPERATORS
from mongoengine.connection import get_connection from mongoengine.connection import get_connection
from mongoengine.common import _import_class from mongoengine.common import _import_class
from mongoengine.errors import InvalidQueryError from mongoengine.errors import InvalidQueryError, LookUpError
__all__ = ('query', 'update') __all__ = ('query', 'update')
@ -44,8 +44,8 @@ def query(_doc_cls=None, _field_operation=False, **query):
if len(parts) > 1 and parts[-1] in MATCH_OPERATORS: if len(parts) > 1 and parts[-1] in MATCH_OPERATORS:
op = parts.pop() op = parts.pop()
# if user escape field name by __ #if user escape field name by __
if len(parts) > 1 and parts[-1] == "": if len(parts) > 1 and parts[-1]=="":
parts.pop() parts.pop()
negate = False negate = False
@ -126,32 +126,28 @@ def query(_doc_cls=None, _field_operation=False, **query):
elif key in mongo_query: elif key in mongo_query:
if key in mongo_query and isinstance(mongo_query[key], dict): if key in mongo_query and isinstance(mongo_query[key], dict):
mongo_query[key].update(value) mongo_query[key].update(value)
# $maxDistance needs to come last - convert to SON # $max/minDistance needs to come last - convert to SON
value_dict = mongo_query[key] value_dict = mongo_query[key]
if ('$maxDistance' in value_dict and '$near' in value_dict): if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and '$near' in value_dict:
value_son = SON() value_son = SON()
if isinstance(value_dict['$near'], dict):
for k, v in value_dict.iteritems(): for k, v in value_dict.iteritems():
if k == '$maxDistance' or k == '$minDistance': if k == '$maxDistance' or k == '$minDistance':
continue continue
value_son[k] = v value_son[k] = v
if (get_connection().max_wire_version <= 1): if isinstance(value_dict['$near'], dict) and\
value_son['$maxDistance'] = value_dict[ get_connection().max_wire_version > 1:
'$maxDistance']
else:
value_son['$near'] = SON(value_son['$near']) value_son['$near'] = SON(value_son['$near'])
if '$maxDistance' in value_dict:
value_son['$near'][ value_son['$near'][
'$maxDistance'] = value_dict['$maxDistance'] '$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict:
value_son['$near'][
'$minDistance'] = value_dict['$minDistance']
else: else:
for k, v in value_dict.iteritems():
if k == '$maxDistance' or k == '$minDistance':
continue
value_son[k] = v
if '$maxDistance' in value_dict: if '$maxDistance' in value_dict:
value_son['$maxDistance'] = value_dict['$maxDistance'] value_son['$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict: if '$minDistance' in value_dict:
value_son['$minDistance'] = value_dict['$minDistance'] value_son['$minDistance'] = value_dict['$minDistance']
mongo_query[key] = value_son mongo_query[key] = value_son
else: else:
# Store for manually merging later # Store for manually merging later
@ -305,7 +301,11 @@ def update(_doc_cls=None, **update):
def _geo_operator(field, op, value): def _geo_operator(field, op, value):
"""Helper to return the query for a given geo query""" """Helper to return the query for a given geo query"""
if field._geo_index == pymongo.GEO2D: if op == "max_distance":
value = {'$maxDistance': value}
elif op == "min_distance":
value = {'$minDistance': value}
elif field._geo_index == pymongo.GEO2D:
if op == "within_distance": if op == "within_distance":
value = {'$within': {'$center': value}} value = {'$within': {'$center': value}}
elif op == "within_spherical_distance": elif op == "within_spherical_distance":
@ -318,10 +318,6 @@ def _geo_operator(field, op, value):
value = {'$nearSphere': value} value = {'$nearSphere': value}
elif op == 'within_box': elif op == 'within_box':
value = {'$within': {'$box': value}} value = {'$within': {'$box': value}}
elif op == "max_distance":
value = {'$maxDistance': value}
elif op == "min_distance":
value = {'$minDistance': value}
else: else:
raise NotImplementedError("Geo method '%s' has not " raise NotImplementedError("Geo method '%s' has not "
"been implemented for a GeoPointField" % op) "been implemented for a GeoPointField" % op)
@ -340,10 +336,6 @@ def _geo_operator(field, op, value):
value = {"$geoIntersects": _infer_geometry(value)} value = {"$geoIntersects": _infer_geometry(value)}
elif op == "near": elif op == "near":
value = {'$near': _infer_geometry(value)} value = {'$near': _infer_geometry(value)}
elif op == "max_distance":
value = {'$maxDistance': value}
elif op == "min_distance":
value = {'$minDistance': value}
else: else:
raise NotImplementedError("Geo method '%s' has not " raise NotImplementedError("Geo method '%s' has not "
"been implemented for a %s " % (op, field._name)) "been implemented for a %s " % (op, field._name))

View File

@ -70,6 +70,11 @@ class GeoQueriesTest(unittest.TestCase):
self.assertEqual(events.count(), 1) self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2) self.assertEqual(events[0], event2)
# find events at least 10 degrees away of san francisco
point = [-122.415579, 37.7566023]
events = Event.objects(location__near=point, location__min_distance=10)
self.assertEqual(events.count(), 2)
# find events within 10 degrees of san francisco # find events within 10 degrees of san francisco
point_and_distance = [[-122.415579, 37.7566023], 10] point_and_distance = [[-122.415579, 37.7566023], 10]
events = Event.objects(location__within_distance=point_and_distance) events = Event.objects(location__within_distance=point_and_distance)
@ -171,7 +176,7 @@ class GeoQueriesTest(unittest.TestCase):
# Same behavior for _within_spherical_distance # Same behavior for _within_spherical_distance
points = Point.objects( points = Point.objects(
location__within_spherical_distance=[[-122, 37.5], 60/earth_radius] location__within_spherical_distance=[[-122, 37.5], 60 / earth_radius]
) )
self.assertEqual(points.count(), 2) self.assertEqual(points.count(), 2)
@ -186,6 +191,16 @@ class GeoQueriesTest(unittest.TestCase):
self.assertEqual(points.count(), 2) self.assertEqual(points.count(), 2)
# Test query works with max_distance being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__min_distance=60 / earth_radius)
self.assertEqual(points.count(), 1)
# Test query works with min_distance being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__min_distance=60 / earth_radius)
self.assertEqual(points.count(), 1)
# Finds both points, but orders the north point first because it's # Finds both points, but orders the north point first because it's
# closer to the reference point to the north. # closer to the reference point to the north.
points = Point.objects(location__near_sphere=[-122, 38.5]) points = Point.objects(location__near_sphere=[-122, 38.5])
@ -268,6 +283,20 @@ class GeoQueriesTest(unittest.TestCase):
self.assertEqual(events.count(), 2) self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3) self.assertEqual(events[0], event3)
# ensure min_distance and max_distance combine well
events = Event.objects(location__near=[-87.67892, 41.9120459],
location__min_distance=1000,
location__max_distance=10000).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event3)
# ensure ordering is respected by "near"
events = Event.objects(location__near=[-87.67892, 41.9120459],
# location__min_distance=10000
location__min_distance=10000).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# check that within_box works # check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)] box = [(-125.0, 35.0), (-100.0, 40.0)]
events = Event.objects(location__geo_within_box=box) events = Event.objects(location__geo_within_box=box)

View File

@ -4763,5 +4763,19 @@ class QuerySetTest(unittest.TestCase):
for p in Person.objects(): for p in Person.objects():
self.assertEqual(p.name, 'a') self.assertEqual(p.name, 'a')
def test_last_field_name_like_operator(self):
class EmbeddedItem(EmbeddedDocument):
type = StringField()
class Doc(Document):
item = EmbeddedDocumentField(EmbeddedItem)
Doc.drop_collection()
doc = Doc(item=EmbeddedItem(type="axe"))
doc.save()
self.assertEqual(1, Doc.objects(item__type__="axe").count())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()