Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_dictfield_validation
This commit is contained in:
commit
87512246cb
13
.travis.yml
13
.travis.yml
@ -3,7 +3,7 @@
|
|||||||
# with a very large number of jobs, hence we only test a subset of all the
|
# with a very large number of jobs, hence we only test a subset of all the
|
||||||
# combinations:
|
# combinations:
|
||||||
# * MongoDB v3.4 & the latest PyMongo v3.x is currently the "main" setup,
|
# * MongoDB v3.4 & the latest PyMongo v3.x is currently the "main" setup,
|
||||||
# tested against Python v2.7, v3.5, v3.6, v3.7, v3.8 and PyPy.
|
# tested against Python v2.7, v3.5, v3.6, v3.7, v3.8, PyPy and PyPy3.
|
||||||
# * Besides that, we test the lowest actively supported Python/MongoDB/PyMongo
|
# * Besides that, we test the lowest actively supported Python/MongoDB/PyMongo
|
||||||
# combination: MongoDB v3.4, PyMongo v3.4, Python v2.7.
|
# combination: MongoDB v3.4, PyMongo v3.4, Python v2.7.
|
||||||
# * MongoDB v3.6 is tested against Python v3.6, and PyMongo v3.6, v3.7, v3.8.
|
# * MongoDB v3.6 is tested against Python v3.6, and PyMongo v3.6, v3.7, v3.8.
|
||||||
@ -33,15 +33,16 @@ env:
|
|||||||
global:
|
global:
|
||||||
- MONGODB_3_4=3.4.17
|
- MONGODB_3_4=3.4.17
|
||||||
- MONGODB_3_6=3.6.12
|
- MONGODB_3_6=3.6.12
|
||||||
- PYMONGO_3_10=3.10
|
- MONGODB_4_0=4.0.13
|
||||||
- PYMONGO_3_9=3.9
|
|
||||||
- PYMONGO_3_6=3.6
|
|
||||||
- PYMONGO_3_4=3.4
|
- PYMONGO_3_4=3.4
|
||||||
|
- PYMONGO_3_6=3.6
|
||||||
|
- PYMONGO_3_9=3.9
|
||||||
|
- PYMONGO_3_10=3.10
|
||||||
matrix:
|
matrix:
|
||||||
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_10}
|
- MONGODB=${MONGODB_3_4} PYMONGO=${PYMONGO_3_10}
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
|
||||||
# Finish the build as soon as one job fails
|
# Finish the build as soon as one job fails
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
@ -54,6 +55,8 @@ matrix:
|
|||||||
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_9}
|
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_9}
|
||||||
- python: 3.7
|
- python: 3.7
|
||||||
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_10}
|
env: MONGODB=${MONGODB_3_6} PYMONGO=${PYMONGO_3_10}
|
||||||
|
- python: 3.8
|
||||||
|
env: MONGODB=${MONGODB_4_0} PYMONGO=${PYMONGO_3_10}
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Install Mongo
|
# Install Mongo
|
||||||
|
1
AUTHORS
1
AUTHORS
@ -255,3 +255,4 @@ that much better:
|
|||||||
* Filip Kucharczyk (https://github.com/Pacu2)
|
* Filip Kucharczyk (https://github.com/Pacu2)
|
||||||
* Eric Timmons (https://github.com/daewok)
|
* Eric Timmons (https://github.com/daewok)
|
||||||
* Matthew Simpson (https://github.com/mcsimps2)
|
* Matthew Simpson (https://github.com/mcsimps2)
|
||||||
|
* Leonardo Domingues (https://github.com/leodmgs)
|
||||||
|
@ -26,10 +26,10 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
|
|||||||
|
|
||||||
Supported MongoDB Versions
|
Supported MongoDB Versions
|
||||||
==========================
|
==========================
|
||||||
MongoEngine is currently tested against MongoDB v3.4 and v3.6. Future versions
|
MongoEngine is currently tested against MongoDB v3.4, v3.6 and v4.0. Future versions
|
||||||
should be supported as well, but aren't actively tested at the moment. Make
|
should be supported as well, but aren't actively tested at the moment. Make
|
||||||
sure to open an issue or submit a pull request if you experience any problems
|
sure to open an issue or submit a pull request if you experience any problems
|
||||||
with MongoDB version > 3.6.
|
with MongoDB version > 4.0.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
@ -6,6 +6,9 @@ Changelog
|
|||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop your features).
|
- (Fill this out as you fix issues and develop your features).
|
||||||
|
- Add Mongo 4.0 to Travis
|
||||||
|
- Fixed a bug causing inaccurate query results, while combining ``__raw__`` and regular filters for the same field #2264
|
||||||
|
- Add support for the `elemMatch` projection operator in .fields (e.g BlogPost.objects.fields(elemMatch__comments="test")) #2267
|
||||||
|
|
||||||
Changes in 0.19.2
|
Changes in 0.19.2
|
||||||
=================
|
=================
|
||||||
|
@ -56,7 +56,7 @@ class InvalidCollectionError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
||||||
"""A :class:`~mongoengine.Document` that isn't stored in its own
|
r"""A :class:`~mongoengine.Document` that isn't stored in its own
|
||||||
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
|
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
|
||||||
fields on :class:`~mongoengine.Document`\ s through the
|
fields on :class:`~mongoengine.Document`\ s through the
|
||||||
:class:`~mongoengine.EmbeddedDocumentField` field type.
|
:class:`~mongoengine.EmbeddedDocumentField` field type.
|
||||||
@ -332,7 +332,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
):
|
):
|
||||||
"""Save the :class:`~mongoengine.Document` to the database. If the
|
"""Save the :class:`~mongoengine.Document` to the database. If the
|
||||||
document already exists, it will be updated, otherwise it will be
|
document already exists, it will be updated, otherwise it will be
|
||||||
created.
|
created. Returns the saved object instance.
|
||||||
|
|
||||||
:param force_insert: only try to create a new document, don't allow
|
:param force_insert: only try to create a new document, don't allow
|
||||||
updates of existing documents.
|
updates of existing documents.
|
||||||
|
@ -694,8 +694,8 @@ class BaseQuerySet(object):
|
|||||||
def in_bulk(self, object_ids):
|
def in_bulk(self, object_ids):
|
||||||
"""Retrieve a set of documents by their ids.
|
"""Retrieve a set of documents by their ids.
|
||||||
|
|
||||||
:param object_ids: a list or tuple of ``ObjectId``\ s
|
:param object_ids: a list or tuple of ObjectId's
|
||||||
:rtype: dict of ObjectIds as keys and collection-specific
|
:rtype: dict of ObjectId's as keys and collection-specific
|
||||||
Document subclasses as values.
|
Document subclasses as values.
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
@ -1026,9 +1026,11 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
posts = BlogPost.objects(...).fields(comments=0)
|
posts = BlogPost.objects(...).fields(comments=0)
|
||||||
|
|
||||||
To retrieve a subrange of array elements:
|
To retrieve a subrange or sublist of array elements,
|
||||||
|
support exist for both the `slice` and `elemMatch` projection operator:
|
||||||
|
|
||||||
posts = BlogPost.objects(...).fields(slice__comments=5)
|
posts = BlogPost.objects(...).fields(slice__comments=5)
|
||||||
|
posts = BlogPost.objects(...).fields(elemMatch__comments="test")
|
||||||
|
|
||||||
:param kwargs: A set of keyword arguments identifying what to
|
:param kwargs: A set of keyword arguments identifying what to
|
||||||
include, exclude, or slice.
|
include, exclude, or slice.
|
||||||
@ -1037,7 +1039,7 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Check for an operator and transform to mongo-style if there is
|
# Check for an operator and transform to mongo-style if there is
|
||||||
operators = ["slice"]
|
operators = ["slice", "elemMatch"]
|
||||||
cleaned_fields = []
|
cleaned_fields = []
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
parts = key.split("__")
|
parts = key.split("__")
|
||||||
@ -1140,7 +1142,7 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
def explain(self):
|
def explain(self):
|
||||||
"""Return an explain plan record for the
|
"""Return an explain plan record for the
|
||||||
:class:`~mongoengine.queryset.QuerySet`\ 's cursor.
|
:class:`~mongoengine.queryset.QuerySet` cursor.
|
||||||
"""
|
"""
|
||||||
return self._cursor.explain()
|
return self._cursor.explain()
|
||||||
|
|
||||||
|
@ -169,9 +169,9 @@ def query(_doc_cls=None, **kwargs):
|
|||||||
|
|
||||||
key = ".".join(parts)
|
key = ".".join(parts)
|
||||||
|
|
||||||
if op is None or key not in mongo_query:
|
if key not in mongo_query:
|
||||||
mongo_query[key] = value
|
mongo_query[key] = value
|
||||||
elif key in mongo_query:
|
else:
|
||||||
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
|
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
|
||||||
mongo_query[key].update(value)
|
mongo_query[key].update(value)
|
||||||
# $max/minDistance needs to come last - convert to SON
|
# $max/minDistance needs to come last - convert to SON
|
||||||
|
7
setup.py
7
setup.py
@ -108,6 +108,10 @@ CLASSIFIERS = [
|
|||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PYTHON_VERSION = sys.version_info[0]
|
||||||
|
PY3 = PYTHON_VERSION == 3
|
||||||
|
PY2 = PYTHON_VERSION == 2
|
||||||
|
|
||||||
extra_opts = {
|
extra_opts = {
|
||||||
"packages": find_packages(exclude=["tests", "tests.*"]),
|
"packages": find_packages(exclude=["tests", "tests.*"]),
|
||||||
"tests_require": [
|
"tests_require": [
|
||||||
@ -116,9 +120,10 @@ extra_opts = {
|
|||||||
"coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls
|
"coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls
|
||||||
"blinker",
|
"blinker",
|
||||||
"Pillow>=2.0.0, <7.0.0", # 7.0.0 dropped Python2 support
|
"Pillow>=2.0.0, <7.0.0", # 7.0.0 dropped Python2 support
|
||||||
|
"zipp<2.0.0", # (dependency of pytest) dropped python2 support
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
if sys.version_info[0] == 3:
|
if PY3:
|
||||||
extra_opts["use_2to3"] = True
|
extra_opts["use_2to3"] = True
|
||||||
if "test" in sys.argv:
|
if "test" in sys.argv:
|
||||||
extra_opts["packages"] = find_packages()
|
extra_opts["packages"] = find_packages()
|
||||||
|
@ -65,7 +65,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
|
|||||||
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
|
for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond):
|
||||||
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()["date"]
|
stored = LogEntry(date=datetime.datetime(*values)).to_mongo()["date"]
|
||||||
assert (
|
assert (
|
||||||
re.match("^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored)
|
re.match(r"^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$", stored)
|
||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class ComplexDateTimeFieldTest(MongoDBTestCase):
|
|||||||
"date_with_dots"
|
"date_with_dots"
|
||||||
]
|
]
|
||||||
assert (
|
assert (
|
||||||
re.match("^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$", stored) is not None
|
re.match(r"^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$", stored) is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_complexdatetime_usage(self):
|
def test_complexdatetime_usage(self):
|
||||||
|
@ -4476,6 +4476,74 @@ class TestQueryset(unittest.TestCase):
|
|||||||
expected = "[u'A1', u'A2']"
|
expected = "[u'A1', u'A2']"
|
||||||
assert expected == "%s" % sorted(names)
|
assert expected == "%s" % sorted(names)
|
||||||
|
|
||||||
|
def test_fields(self):
|
||||||
|
class Bar(EmbeddedDocument):
|
||||||
|
v = StringField()
|
||||||
|
z = StringField()
|
||||||
|
|
||||||
|
class Foo(Document):
|
||||||
|
x = StringField()
|
||||||
|
y = IntField()
|
||||||
|
items = EmbeddedDocumentListField(Bar)
|
||||||
|
|
||||||
|
Foo.drop_collection()
|
||||||
|
|
||||||
|
Foo(x="foo1", y=1).save()
|
||||||
|
Foo(x="foo2", y=2, items=[]).save()
|
||||||
|
Foo(x="foo3", y=3, items=[Bar(z="a", v="V")]).save()
|
||||||
|
Foo(
|
||||||
|
x="foo4",
|
||||||
|
y=4,
|
||||||
|
items=[
|
||||||
|
Bar(z="a", v="V"),
|
||||||
|
Bar(z="b", v="W"),
|
||||||
|
Bar(z="b", v="X"),
|
||||||
|
Bar(z="c", v="V"),
|
||||||
|
],
|
||||||
|
).save()
|
||||||
|
Foo(
|
||||||
|
x="foo5",
|
||||||
|
y=5,
|
||||||
|
items=[
|
||||||
|
Bar(z="b", v="X"),
|
||||||
|
Bar(z="c", v="V"),
|
||||||
|
Bar(z="d", v="V"),
|
||||||
|
Bar(z="e", v="V"),
|
||||||
|
],
|
||||||
|
).save()
|
||||||
|
|
||||||
|
foos_with_x = list(Foo.objects.order_by("y").fields(x=1))
|
||||||
|
|
||||||
|
assert all(o.x is not None for o in foos_with_x)
|
||||||
|
|
||||||
|
foos_without_y = list(Foo.objects.order_by("y").fields(y=0))
|
||||||
|
|
||||||
|
assert all(o.y is None for o in foos_with_x)
|
||||||
|
|
||||||
|
foos_with_sliced_items = list(Foo.objects.order_by("y").fields(slice__items=1))
|
||||||
|
|
||||||
|
assert foos_with_sliced_items[0].items == []
|
||||||
|
assert foos_with_sliced_items[1].items == []
|
||||||
|
assert len(foos_with_sliced_items[2].items) == 1
|
||||||
|
assert foos_with_sliced_items[2].items[0].z == "a"
|
||||||
|
assert len(foos_with_sliced_items[3].items) == 1
|
||||||
|
assert foos_with_sliced_items[3].items[0].z == "a"
|
||||||
|
assert len(foos_with_sliced_items[4].items) == 1
|
||||||
|
assert foos_with_sliced_items[4].items[0].z == "b"
|
||||||
|
|
||||||
|
foos_with_elem_match_items = list(
|
||||||
|
Foo.objects.order_by("y").fields(elemMatch__items={"z": "b"})
|
||||||
|
)
|
||||||
|
|
||||||
|
assert foos_with_elem_match_items[0].items == []
|
||||||
|
assert foos_with_elem_match_items[1].items == []
|
||||||
|
assert foos_with_elem_match_items[2].items == []
|
||||||
|
assert len(foos_with_elem_match_items[3].items) == 1
|
||||||
|
assert foos_with_elem_match_items[3].items[0].z == "b"
|
||||||
|
assert foos_with_elem_match_items[3].items[0].v == "W"
|
||||||
|
assert len(foos_with_elem_match_items[4].items) == 1
|
||||||
|
assert foos_with_elem_match_items[4].items[0].z == "b"
|
||||||
|
|
||||||
def test_elem_match(self):
|
def test_elem_match(self):
|
||||||
class Foo(EmbeddedDocument):
|
class Foo(EmbeddedDocument):
|
||||||
shape = StringField()
|
shape = StringField()
|
||||||
|
@ -24,6 +24,12 @@ class TestTransform(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
assert transform.query(friend__age__gte=30) == {"friend.age": {"$gte": 30}}
|
assert transform.query(friend__age__gte=30) == {"friend.age": {"$gte": 30}}
|
||||||
assert transform.query(name__exists=True) == {"name": {"$exists": True}}
|
assert transform.query(name__exists=True) == {"name": {"$exists": True}}
|
||||||
|
assert transform.query(name=["Mark"], __raw__={"name": {"$in": "Tom"}}) == {
|
||||||
|
"$and": [{"name": ["Mark"]}, {"name": {"$in": "Tom"}}]
|
||||||
|
}
|
||||||
|
assert transform.query(name__in=["Tom"], __raw__={"name": "Mark"}) == {
|
||||||
|
"$and": [{"name": {"$in": ["Tom"]}}, {"name": "Mark"}]
|
||||||
|
}
|
||||||
|
|
||||||
def test_transform_update(self):
|
def test_transform_update(self):
|
||||||
class LisDoc(Document):
|
class LisDoc(Document):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user