Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_dictfield_validation
This commit is contained in:
		
							
								
								
									
										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): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user