Added tests, documentation and simplified code
This commit is contained in:
parent
2d57dc0565
commit
5efabdcea3
@ -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
|
||||||
================
|
================
|
||||||
|
@ -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
|
||||||
--------------
|
--------------
|
||||||
|
@ -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')
|
||||||
|
|
||||||
@ -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))
|
||||||
|
@ -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)
|
||||||
@ -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)
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user