From b392e3102e94657fd663ba4e2b188ec648fe1c1b Mon Sep 17 00:00:00 2001 From: Agustin Barto Date: Fri, 17 May 2019 13:41:02 -0300 Subject: [PATCH 1/8] Add support to transform. Add pull tests for and . --- mongoengine/queryset/transform.py | 2 +- tests/queryset/transform.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 3de10a69..7241efbd 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -281,7 +281,7 @@ def update(_doc_cls=None, **update): if op == 'pull': if field.required or value is not None: - if match == 'in' and not isinstance(value, dict): + if match in ('in', 'nin') and not isinstance(value, dict): value = _prepare_query_for_iterable(field, op, value) else: value = field.prepare_query_value(op, value) diff --git a/tests/queryset/transform.py b/tests/queryset/transform.py index 8064f09c..3c7c945f 100644 --- a/tests/queryset/transform.py +++ b/tests/queryset/transform.py @@ -276,13 +276,18 @@ class TransformTest(unittest.TestCase): title = StringField() content = EmbeddedDocumentField(SubDoc) - word = Word(word='abc', index=1) - update = transform.update(MainDoc, pull__content__text=word) - self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}}) + # word = Word(word='abc', index=1) + # update = transform.update(MainDoc, pull__content__text=word) + # self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}}) - update = transform.update(MainDoc, pull__content__heading='xyz') - self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}}) + # update = transform.update(MainDoc, pull__content__heading='xyz') + # self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}}) + # update = transform.update(MainDoc, pull__content__text__word__in=['foo', 'bar']) + # self.assertEqual(update, {'$pull': {'content.text': {'word': {'$in': ['foo', 'bar']}}}}) + + update = transform.update(MainDoc, pull__content__text__word__nin=['foo', 'bar']) + self.assertEqual(update, {'$pull': {'content.text': {'word': {'$nin': ['foo', 'bar']}}}}) if __name__ == '__main__': unittest.main() From 2b17985a11b5ff9baa206d513f2d05e502122039 Mon Sep 17 00:00:00 2001 From: Agustin Barto Date: Fri, 17 May 2019 13:55:00 -0300 Subject: [PATCH 2/8] Uncomment tests. --- tests/queryset/transform.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/queryset/transform.py b/tests/queryset/transform.py index 3c7c945f..b2bc1d6c 100644 --- a/tests/queryset/transform.py +++ b/tests/queryset/transform.py @@ -276,15 +276,15 @@ class TransformTest(unittest.TestCase): title = StringField() content = EmbeddedDocumentField(SubDoc) - # word = Word(word='abc', index=1) - # update = transform.update(MainDoc, pull__content__text=word) - # self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}}) + word = Word(word='abc', index=1) + update = transform.update(MainDoc, pull__content__text=word) + self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}}) - # update = transform.update(MainDoc, pull__content__heading='xyz') - # self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}}) + update = transform.update(MainDoc, pull__content__heading='xyz') + self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}}) - # update = transform.update(MainDoc, pull__content__text__word__in=['foo', 'bar']) - # self.assertEqual(update, {'$pull': {'content.text': {'word': {'$in': ['foo', 'bar']}}}}) + update = transform.update(MainDoc, pull__content__text__word__in=['foo', 'bar']) + self.assertEqual(update, {'$pull': {'content.text': {'word': {'$in': ['foo', 'bar']}}}}) update = transform.update(MainDoc, pull__content__text__word__nin=['foo', 'bar']) self.assertEqual(update, {'$pull': {'content.text': {'word': {'$nin': ['foo', 'bar']}}}}) From f28e1b8c90eeb977a1ee8be625bdc3ae5143ad51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 11 May 2019 22:31:32 +0200 Subject: [PATCH 3/8] improve coverage of lazy ref field --- tests/fields/test_lazy_reference_field.py | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/fields/test_lazy_reference_field.py b/tests/fields/test_lazy_reference_field.py index a72e8cbe..d8031409 100644 --- a/tests/fields/test_lazy_reference_field.py +++ b/tests/fields/test_lazy_reference_field.py @@ -13,6 +13,35 @@ class TestLazyReferenceField(MongoDBTestCase): # with a document class name. self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument) + def test___repr__(self): + class Animal(Document): + pass + + class Ocurrence(Document): + animal = LazyReferenceField(Animal) + + Animal.drop_collection() + Ocurrence.drop_collection() + + animal = Animal() + oc = Ocurrence(animal=animal) + self.assertIn('LazyReference', repr(oc.animal)) + + def test___getattr___unknown_attr_raises_attribute_error(self): + class Animal(Document): + pass + + class Ocurrence(Document): + animal = LazyReferenceField(Animal) + + Animal.drop_collection() + Ocurrence.drop_collection() + + animal = Animal().save() + oc = Ocurrence(animal=animal) + with self.assertRaises(AttributeError): + oc.animal.not_exist + def test_lazy_reference_simple(self): class Animal(Document): name = StringField() From 00d2fd685af10f3cebb2c61a3d04c2ac341c7092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 12 May 2019 22:58:17 +0200 Subject: [PATCH 4/8] more test cov --- mongoengine/base/metaclasses.py | 3 --- mongoengine/common.py | 6 +----- mongoengine/fields.py | 6 ++---- mongoengine/queryset/base.py | 2 +- mongoengine/queryset/transform.py | 18 +++--------------- tests/document/indexes.py | 29 ++++++++++++++++++++++++----- tests/document/instance.py | 6 ++++++ tests/fields/geo.py | 5 +++++ tests/test_common.py | 0 9 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 tests/test_common.py diff --git a/mongoengine/base/metaclasses.py b/mongoengine/base/metaclasses.py index a1970825..6f507eaa 100644 --- a/mongoengine/base/metaclasses.py +++ b/mongoengine/base/metaclasses.py @@ -184,9 +184,6 @@ class DocumentMetaclass(type): if issubclass(new_class, EmbeddedDocument): raise InvalidDocumentError('CachedReferenceFields is not ' 'allowed in EmbeddedDocuments') - if not f.document_type: - raise InvalidDocumentError( - 'Document is not available to sync') if f.auto_sync: f.start_listener() diff --git a/mongoengine/common.py b/mongoengine/common.py index bde7e78c..bcdea194 100644 --- a/mongoengine/common.py +++ b/mongoengine/common.py @@ -31,7 +31,6 @@ def _import_class(cls_name): field_classes = _field_list_cache - queryset_classes = ('OperationError',) deref_classes = ('DeReference',) if cls_name == 'BaseDocument': @@ -43,14 +42,11 @@ def _import_class(cls_name): elif cls_name in field_classes: from mongoengine import fields as module import_classes = field_classes - elif cls_name in queryset_classes: - from mongoengine import queryset as module - import_classes = queryset_classes elif cls_name in deref_classes: from mongoengine import dereference as module import_classes = deref_classes else: - raise ValueError('No import set for: ' % cls_name) + raise ValueError('No import set for: %s' % cls_name) for cls in import_classes: _class_registry_cache[cls] = getattr(module, cls) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 7e119721..1cd6be11 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -152,12 +152,10 @@ class URLField(StringField): scheme = value.split('://')[0].lower() if scheme not in self.schemes: self.error(u'Invalid scheme {} in URL: {}'.format(scheme, value)) - return # Then check full URL if not self.url_regex.match(value): self.error(u'Invalid URL: {}'.format(value)) - return class EmailField(StringField): @@ -259,10 +257,10 @@ class EmailField(StringField): try: domain_part = domain_part.encode('idna').decode('ascii') except UnicodeError: - self.error(self.error_msg % value) + self.error("%s %s" % (self.error_msg % value, "(domain failed IDN encoding)")) else: if not self.validate_domain_part(domain_part): - self.error(self.error_msg % value) + self.error("%s %s" % (self.error_msg % value, "(domain validation failed)")) class IntField(BaseField): diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 66e43514..eec73f91 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -197,7 +197,7 @@ class BaseQuerySet(object): only_fields=self.only_fields ) - raise AttributeError('Provide a slice or an integer index') + raise TypeError('Provide a slice or an integer index') def __iter__(self): raise NotImplementedError diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 3de10a69..ef5b1ea3 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -88,18 +88,10 @@ def query(_doc_cls=None, **kwargs): singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte', 'not'] singular_ops += STRING_OPERATORS if op in singular_ops: - if isinstance(field, six.string_types): - if (op in STRING_OPERATORS and - isinstance(value, six.string_types)): - StringField = _import_class('StringField') - value = StringField.prepare_query_value(op, value) - else: - value = field - else: - value = field.prepare_query_value(op, value) + value = field.prepare_query_value(op, value) - if isinstance(field, CachedReferenceField) and value: - value = value['_id'] + if isinstance(field, CachedReferenceField) and value: + value = value['_id'] elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict): # Raise an error if the in/nin/all/near param is not iterable. @@ -308,10 +300,6 @@ def update(_doc_cls=None, **update): key = '.'.join(parts) - if not op: - raise InvalidQueryError('Updates must supply an operation ' - 'eg: set__FIELD=value') - if 'pull' in op and '.' in key: # Dot operators don't work on pull operations # unless they point to a list field diff --git a/tests/document/indexes.py b/tests/document/indexes.py index be344d32..bbe3dc5a 100644 --- a/tests/document/indexes.py +++ b/tests/document/indexes.py @@ -593,8 +593,9 @@ class IndexesTest(unittest.TestCase): # Two posts with the same slug is not allowed post2 = BlogPost(title='test2', slug='test') self.assertRaises(NotUniqueError, post2.save) + self.assertRaises(NotUniqueError, BlogPost.objects.insert, post2) - # Ensure backwards compatibilty for errors + # Ensure backwards compatibility for errors self.assertRaises(OperationError, post2.save) @requires_mongodb_gte_34 @@ -826,6 +827,19 @@ class IndexesTest(unittest.TestCase): self.assertEqual(3600, info['created_1']['expireAfterSeconds']) + def test_index_drop_dups_silently_ignored(self): + class Customer(Document): + cust_id = IntField(unique=True, required=True) + meta = { + 'indexes': ['cust_id'], + 'index_drop_dups': True, + 'allow_inheritance': False, + } + + Customer.drop_collection() + Customer.objects.first() + + def test_unique_and_indexes(self): """Ensure that 'unique' constraints aren't overridden by meta.indexes. @@ -842,11 +856,16 @@ class IndexesTest(unittest.TestCase): cust.save() cust_dupe = Customer(cust_id=1) - try: + with self.assertRaises(NotUniqueError): cust_dupe.save() - raise AssertionError("We saved a dupe!") - except NotUniqueError: - pass + + cust = Customer(cust_id=2) + cust.save() + + # duplicate key on update + with self.assertRaises(NotUniqueError): + cust.cust_id = 1 + cust.save() def test_primary_save_duplicate_update_existing_object(self): """If you set a field as primary, then unexpected behaviour can occur. diff --git a/tests/document/instance.py b/tests/document/instance.py index 9b28f1b4..e1379a5d 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -420,6 +420,12 @@ class InstanceTest(MongoDBTestCase): person.save() person.to_dbref() + def test_key_like_attribute_access(self): + person = self.Person(age=30) + self.assertEqual(person['age'], 30) + with self.assertRaises(KeyError): + person['unknown_attr'] + def test_save_abstract_document(self): """Saving an abstract document should fail.""" class Doc(Document): diff --git a/tests/fields/geo.py b/tests/fields/geo.py index 754f4203..37ed97f5 100644 --- a/tests/fields/geo.py +++ b/tests/fields/geo.py @@ -40,6 +40,11 @@ class GeoFieldTest(unittest.TestCase): expected = "Both values (%s) in point must be float or int" % repr(coord) self._test_for_expected_error(Location, coord, expected) + invalid_coords = [21, 4, 'a'] + for coord in invalid_coords: + expected = "GeoPointField can only accept tuples or lists of (x, y)" + self._test_for_expected_error(Location, coord, expected) + def test_point_validation(self): class Location(Document): loc = PointField() diff --git a/tests/test_common.py b/tests/test_common.py new file mode 100644 index 00000000..e69de29b From c82f0c937d50b2f4c27af7d59ba0069f93686baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 14 May 2019 21:46:57 +0200 Subject: [PATCH 5/8] more work on coverage --- mongoengine/errors.py | 3 -- tests/document/indexes.py | 1 - tests/fields/test_email_field.py | 10 +++++ tests/queryset/queryset.py | 67 ++++++++++++++++++++++++++++++++ tests/queryset/transform.py | 8 ++++ tests/test_common.py | 15 +++++++ tests/test_context_managers.py | 8 ++++ tests/test_datastructures.py | 15 +++++++ 8 files changed, 123 insertions(+), 4 deletions(-) diff --git a/mongoengine/errors.py b/mongoengine/errors.py index 0e92a8c4..b0009cbc 100644 --- a/mongoengine/errors.py +++ b/mongoengine/errors.py @@ -110,9 +110,6 @@ class ValidationError(AssertionError): def build_dict(source): errors_dict = {} - if not source: - return errors_dict - if isinstance(source, dict): for field_name, error in iteritems(source): errors_dict[field_name] = build_dict(error) diff --git a/tests/document/indexes.py b/tests/document/indexes.py index bbe3dc5a..b0b78923 100644 --- a/tests/document/indexes.py +++ b/tests/document/indexes.py @@ -839,7 +839,6 @@ class IndexesTest(unittest.TestCase): Customer.drop_collection() Customer.objects.first() - def test_unique_and_indexes(self): """Ensure that 'unique' constraints aren't overridden by meta.indexes. diff --git a/tests/fields/test_email_field.py b/tests/fields/test_email_field.py index d8410354..3ce49d62 100644 --- a/tests/fields/test_email_field.py +++ b/tests/fields/test_email_field.py @@ -75,6 +75,16 @@ class TestEmailField(MongoDBTestCase): user = User(email='me@localhost') user.validate() + def test_email_domain_validation_fails_if_invalid_idn(self): + class User(Document): + email = EmailField() + + invalid_idn = '.google.com' + user = User(email='me@%s' % invalid_idn) + with self.assertRaises(ValidationError) as ctx_err: + user.validate() + self.assertIn("domain failed IDN encoding", str(ctx_err.exception)) + def test_email_field_ip_domain(self): class User(Document): email = EmailField() diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 31b1641e..83b19ef8 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -158,6 +158,11 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(person.name, 'User B') self.assertEqual(person.age, None) + def test___getitem___invalid_index(self): + """Ensure slicing a queryset works as expected.""" + with self.assertRaises(TypeError): + self.Person.objects()['a'] + def test_slice(self): """Ensure slicing a queryset works as expected.""" user_a = self.Person.objects.create(name='User A', age=20) @@ -986,6 +991,29 @@ class QuerySetTest(unittest.TestCase): inserted_comment_id = Comment.objects.insert(comment, load_bulk=False) self.assertEqual(comment.id, inserted_comment_id) + def test_bulk_insert_accepts_doc_with_ids(self): + class Comment(Document): + id = IntField(primary_key=True) + + Comment.drop_collection() + + com1 = Comment(id=0) + com2 = Comment(id=1) + Comment.objects.insert([com1, com2]) + + def test_insert_raise_if_duplicate_in_constraint(self): + class Comment(Document): + id = IntField(primary_key=True) + + Comment.drop_collection() + + com1 = Comment(id=0) + + Comment.objects.insert(com1) + + with self.assertRaises(NotUniqueError): + Comment.objects.insert(com1) + def test_get_changed_fields_query_count(self): """Make sure we don't perform unnecessary db operations when none of document's fields were updated. @@ -3570,6 +3598,11 @@ class QuerySetTest(unittest.TestCase): opts = {"deleted": False} return qryset(**opts) + @queryset_manager + def objects_1_arg(qryset): + opts = {"deleted": False} + return qryset(**opts) + @queryset_manager def music_posts(doc_cls, queryset, deleted=False): return queryset(tags='music', @@ -3584,6 +3617,8 @@ class QuerySetTest(unittest.TestCase): self.assertEqual([p.id for p in BlogPost.objects()], [post1.id, post2.id, post3.id]) + self.assertEqual([p.id for p in BlogPost.objects_1_arg()], + [post1.id, post2.id, post3.id]) self.assertEqual([p.id for p in BlogPost.music_posts()], [post1.id, post2.id]) @@ -4968,6 +5003,38 @@ class QuerySetTest(unittest.TestCase): people.count() self.assertEqual(q, 3) + def test_no_cached_queryset__repr__(self): + class Person(Document): + name = StringField() + + Person.drop_collection() + qs = Person.objects.no_cache() + self.assertEqual(repr(qs), '[]') + + def test_no_cached_on_a_cached_queryset_raise_error(self): + class Person(Document): + name = StringField() + + Person.drop_collection() + Person(name='a').save() + qs = Person.objects() + _ = list(qs) + with self.assertRaises(OperationError) as ctx_err: + qs.no_cache() + self.assertEqual("QuerySet already cached", str(ctx_err.exception)) + + def test_no_cached_queryset_no_cache_back_to_cache(self): + class Person(Document): + name = StringField() + + Person.drop_collection() + qs = Person.objects() + self.assertIsInstance(qs, QuerySet) + qs = qs.no_cache() + self.assertIsInstance(qs, QuerySetNoCache) + qs = qs.cache() + self.assertIsInstance(qs, QuerySet) + def test_cache_not_cloned(self): class User(Document): diff --git a/tests/queryset/transform.py b/tests/queryset/transform.py index 8064f09c..c0da1a52 100644 --- a/tests/queryset/transform.py +++ b/tests/queryset/transform.py @@ -71,6 +71,14 @@ class TransformTest(unittest.TestCase): update = transform.update(BlogPost, push_all__tags=['mongo', 'db']) self.assertEqual(update, {'$push': {'tags': {'$each': ['mongo', 'db']}}}) + def test_transform_update_no_operator_default_to_set(self): + """Ensure the differences in behvaior between 'push' and 'push_all'""" + class BlogPost(Document): + tags = ListField(StringField()) + + update = transform.update(BlogPost, tags=['mongo', 'db']) + self.assertEqual(update, {'$set': {'tags': ['mongo', 'db']}}) + def test_query_field_name(self): """Ensure that the correct field name is used when querying. """ diff --git a/tests/test_common.py b/tests/test_common.py index e69de29b..04ad5b34 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -0,0 +1,15 @@ +import unittest + +from mongoengine.common import _import_class +from mongoengine import Document + + +class TestCommon(unittest.TestCase): + + def test__import_class(self): + doc_cls = _import_class("Document") + self.assertIs(doc_cls, Document) + + def test__import_class_raise_if_not_known(self): + with self.assertRaises(ValueError): + _import_class("UnknownClass") diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index 22c33b01..529032fe 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -270,6 +270,14 @@ class ContextManagersTest(unittest.TestCase): counter += 1 self.assertEqual(q, counter) + self.assertEqual(int(q), counter) # test __int__ + self.assertEqual(repr(q), str(int(q))) # test __repr__ + self.assertGreater(q, -1) # test __gt__ + self.assertGreaterEqual(q, int(q)) # test __gte__ + self.assertNotEqual(q, -1) + self.assertLess(q, 1000) + self.assertLessEqual(q, int(q)) + def test_query_counter_counts_getmore_queries(self): connect('mongoenginetest') db = get_db() diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 4fb21d21..a9ef98e7 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -1,4 +1,5 @@ import unittest +from six import iterkeys from mongoengine import Document from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict @@ -368,6 +369,20 @@ class TestStrictDict(unittest.TestCase): d = self.dtype(a=1, b=1, c=1) self.assertEqual((d.a, d.b, d.c), (1, 1, 1)) + def test_iterkeys(self): + d = self.dtype(a=1) + self.assertEqual(list(iterkeys(d)), ['a']) + + def test_len(self): + d = self.dtype(a=1) + self.assertEqual(len(d), 1) + + def test_pop(self): + d = self.dtype(a=1) + self.assertIn('a', d) + d.pop('a') + self.assertNotIn('a', d) + def test_repr(self): d = self.dtype(a=1, b=2, c=3) self.assertEqual(repr(d), '{"a": 1, "b": 2, "c": 3}') From bb1089e03d3d2a4e4e7929a377e917d056f28517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 16 May 2019 22:31:24 +0200 Subject: [PATCH 6/8] Improve coverage in fields test --- tests/fields/test_cached_reference_field.py | 11 +++++++---- tests/fields/test_datetime_field.py | 9 +++++++++ tests/fields/test_lazy_reference_field.py | 17 +++++++++++++++++ tests/fields/test_long_field.py | 4 ++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/fields/test_cached_reference_field.py b/tests/fields/test_cached_reference_field.py index 989cea6d..470ecc5d 100644 --- a/tests/fields/test_cached_reference_field.py +++ b/tests/fields/test_cached_reference_field.py @@ -208,10 +208,7 @@ class TestCachedReferenceField(MongoDBTestCase): ('pj', "PJ") ) name = StringField() - tp = StringField( - choices=TYPES - ) - + tp = StringField(choices=TYPES) father = CachedReferenceField('self', fields=('tp',)) Person.drop_collection() @@ -222,6 +219,9 @@ class TestCachedReferenceField(MongoDBTestCase): a2 = Person(name='Wilson Junior', tp='pf', father=a1) a2.save() + a2 = Person.objects.with_id(a2.id) + self.assertEqual(a2.father.tp, a1.tp) + self.assertEqual(dict(a2.to_mongo()), { "_id": a2.pk, "name": u"Wilson Junior", @@ -374,6 +374,9 @@ class TestCachedReferenceField(MongoDBTestCase): self.assertEqual(o.to_mongo()['animal']['tag'], 'heavy') self.assertEqual(o.to_mongo()['animal']['owner']['t'], 'u') + # Check to_mongo with fields + self.assertNotIn('animal', o.to_mongo(fields=['person'])) + # counts Ocorrence(person="teste 2").save() Ocorrence(person="teste 3").save() diff --git a/tests/fields/test_datetime_field.py b/tests/fields/test_datetime_field.py index c6253043..5af6a011 100644 --- a/tests/fields/test_datetime_field.py +++ b/tests/fields/test_datetime_field.py @@ -172,6 +172,9 @@ class TestDateTimeField(MongoDBTestCase): log.time = datetime.datetime.now().isoformat(' ') log.validate() + log.time = '2019-05-16 21:42:57.897847' + log.validate() + if dateutil: log.time = datetime.datetime.now().isoformat('T') log.validate() @@ -180,6 +183,12 @@ class TestDateTimeField(MongoDBTestCase): self.assertRaises(ValidationError, log.validate) log.time = 'ABC' self.assertRaises(ValidationError, log.validate) + log.time = '2019-05-16 21:GARBAGE:12' + self.assertRaises(ValidationError, log.validate) + log.time = '2019-05-16 21:42:57.GARBAGE' + self.assertRaises(ValidationError, log.validate) + log.time = '2019-05-16 21:42:57.123.456' + self.assertRaises(ValidationError, log.validate) class TestDateTimeTzAware(MongoDBTestCase): diff --git a/tests/fields/test_lazy_reference_field.py b/tests/fields/test_lazy_reference_field.py index d8031409..b10506e7 100644 --- a/tests/fields/test_lazy_reference_field.py +++ b/tests/fields/test_lazy_reference_field.py @@ -508,6 +508,23 @@ class TestGenericLazyReferenceField(MongoDBTestCase): p = Ocurrence.objects.get() self.assertIs(p.animal, None) + def test_generic_lazy_reference_accepts_string_instead_of_class(self): + class Animal(Document): + name = StringField() + tag = StringField() + + class Ocurrence(Document): + person = StringField() + animal = GenericLazyReferenceField('Animal') + + Animal.drop_collection() + Ocurrence.drop_collection() + + animal = Animal().save() + Ocurrence(animal=animal).save() + p = Ocurrence.objects.get() + self.assertEqual(p.animal, animal) + def test_generic_lazy_reference_embedded(self): class Animal(Document): name = StringField() diff --git a/tests/fields/test_long_field.py b/tests/fields/test_long_field.py index 4ab7403d..3f307809 100644 --- a/tests/fields/test_long_field.py +++ b/tests/fields/test_long_field.py @@ -39,9 +39,9 @@ class TestLongField(MongoDBTestCase): doc.value = -1 self.assertRaises(ValidationError, doc.validate) - doc.age = 120 + doc.value = 120 self.assertRaises(ValidationError, doc.validate) - doc.age = 'ten' + doc.value = 'ten' self.assertRaises(ValidationError, doc.validate) def test_long_ne_operator(self): From 6b9d71554e397645071eacfe0b4f1fce7f834e46 Mon Sep 17 00:00:00 2001 From: Agustin Barto Date: Fri, 17 May 2019 17:23:52 -0300 Subject: [PATCH 7/8] Add integration tests --- tests/queryset/queryset.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 31b1641e..0b88193e 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -2193,6 +2193,40 @@ class QuerySetTest(unittest.TestCase): Site.objects(id=s.id).update_one( pull_all__collaborators__helpful__name=['Ross']) + def test_pull_from_nested_embedded_using_in_nin(self): + """Ensure that the 'pull' update operation works on embedded documents using 'in' and 'nin' operators. + """ + + class User(EmbeddedDocument): + name = StringField() + + def __unicode__(self): + return '%s' % self.name + + class Collaborator(EmbeddedDocument): + helpful = ListField(EmbeddedDocumentField(User)) + unhelpful = ListField(EmbeddedDocumentField(User)) + + class Site(Document): + name = StringField(max_length=75, unique=True, required=True) + collaborators = EmbeddedDocumentField(Collaborator) + + Site.drop_collection() + + a = User(name='Esteban') + b = User(name='Frank') + x = User(name='Harry') + y = User(name='John') + + s = Site(name="test", collaborators=Collaborator( + helpful=[a, b], unhelpful=[x, y])).save() + + Site.objects(id=s.id).update_one(pull__collaborators__helpful__name__in=['Esteban']) # Pull a + self.assertEqual(Site.objects.first().collaborators['helpful'], [b]) + + Site.objects(id=s.id).update_one(pull__collaborators__unhelpful__name__nin=['John']) # Pull x + self.assertEqual(Site.objects.first().collaborators['unhelpful'], [y]) + def test_pull_from_nested_mapfield(self): class Collaborator(EmbeddedDocument): From 2e01eb87db14e0d6b291f91b3aec7a8c477a4754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Fri, 17 May 2019 23:07:30 +0200 Subject: [PATCH 8/8] Add support for MongoDB 3.6 and Python3.7 in travis --- .install_mongodb_on_travis.sh | 8 +++++- .travis.yml | 2 ++ README.rst | 4 +-- docs/changelog.rst | 1 + mongoengine/mongodb_support.py | 1 + tests/queryset/queryset.py | 46 ++++++++++++++++++---------------- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/.install_mongodb_on_travis.sh b/.install_mongodb_on_travis.sh index 6ac2e364..f1073333 100644 --- a/.install_mongodb_on_travis.sh +++ b/.install_mongodb_on_travis.sh @@ -25,8 +25,14 @@ elif [ "$MONGODB" = "3.4" ]; then sudo apt-get update sudo apt-get install mongodb-org-server=3.4.17 # service should be started automatically +elif [ "$MONGODB" = "3.6" ]; then + sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5 + echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list + sudo apt-get update + sudo apt-get install mongodb-org-server=3.6.12 + # service should be started automatically else - echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2 or 3.4." + echo "Invalid MongoDB version, expected 2.6, 3.0, 3.2, 3.4 or 3.6." exit 1 fi; diff --git a/.travis.yml b/.travis.yml index b943024a..909183c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,8 @@ matrix: env: MONGODB=3.2 PYMONGO=3.x - python: 3.6 env: MONGODB=3.4 PYMONGO=3.x + - python: 3.6 + env: MONGODB=3.6 PYMONGO=3.x before_install: - bash .install_mongodb_on_travis.sh diff --git a/README.rst b/README.rst index 12d9df0e..cb279d2c 100644 --- a/README.rst +++ b/README.rst @@ -26,10 +26,10 @@ an `API reference `_. Supported MongoDB Versions ========================== -MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2 and v3.4. Future +MongoEngine is currently tested against MongoDB v2.6, v3.0, v3.2, v3.4 and v3.6. Future versions 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 with MongoDB v3.4+. +problems with MongoDB v3.6+. Installation ============ diff --git a/docs/changelog.rst b/docs/changelog.rst index b875ffcd..6ac8da93 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Development =========== +- Add support for MongoDB 3.6 and Python3.7 in travis - Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492 - Fix querying on (Generic)EmbeddedDocument subclasses fields #475 - expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all` diff --git a/mongoengine/mongodb_support.py b/mongoengine/mongodb_support.py index 717a3d81..8e414075 100644 --- a/mongoengine/mongodb_support.py +++ b/mongoengine/mongodb_support.py @@ -6,6 +6,7 @@ from mongoengine.connection import get_connection # Constant that can be used to compare the version retrieved with # get_mongodb_version() +MONGODB_36 = (3, 6) MONGODB_34 = (3, 4) MONGODB_32 = (3, 2) MONGODB_3 = (3, 0) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 31b1641e..5005f260 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -6,7 +6,6 @@ import uuid from decimal import Decimal from bson import DBRef, ObjectId -from nose.plugins.skip import SkipTest import pymongo from pymongo.errors import ConfigurationError from pymongo.read_preferences import ReadPreference @@ -18,7 +17,7 @@ from mongoengine import * from mongoengine.connection import get_connection, get_db from mongoengine.context_managers import query_counter, switch_db from mongoengine.errors import InvalidQueryError -from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32 +from mongoengine.mongodb_support import get_mongodb_version, MONGODB_32, MONGODB_36 from mongoengine.pymongo_support import IS_PYMONGO_3 from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned, QuerySet, QuerySetManager, queryset_manager) @@ -33,6 +32,12 @@ class db_ops_tracker(query_counter): return list(self.db.system.profile.find(ignore_query)) +def get_key_compat(mongo_ver): + ORDER_BY_KEY = 'sort' if mongo_ver >= MONGODB_32 else '$orderby' + CMD_QUERY_KEY = 'command' if mongo_ver >= MONGODB_36 else 'query' + return ORDER_BY_KEY, CMD_QUERY_KEY + + class QuerySetTest(unittest.TestCase): def setUp(self): @@ -1323,8 +1328,7 @@ class QuerySetTest(unittest.TestCase): """Ensure that the default ordering can be cleared by calling order_by() w/o any arguments. """ - MONGO_VER = self.mongodb_version - ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby' + ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version) class BlogPost(Document): title = StringField() @@ -1341,7 +1345,7 @@ class QuerySetTest(unittest.TestCase): BlogPost.objects.filter(title='whatever').first() self.assertEqual(len(q.get_ops()), 1) self.assertEqual( - q.get_ops()[0]['query'][ORDER_BY_KEY], + q.get_ops()[0][CMD_QUERY_KEY][ORDER_BY_KEY], {'published_date': -1} ) @@ -1349,14 +1353,14 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.filter(title='whatever').order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY]) # calling an explicit order_by should use a specified sort with db_ops_tracker() as q: BlogPost.objects.filter(title='whatever').order_by('published_date').first() self.assertEqual(len(q.get_ops()), 1) self.assertEqual( - q.get_ops()[0]['query'][ORDER_BY_KEY], + q.get_ops()[0][CMD_QUERY_KEY][ORDER_BY_KEY], {'published_date': 1} ) @@ -1365,13 +1369,12 @@ class QuerySetTest(unittest.TestCase): qs = BlogPost.objects.filter(title='whatever').order_by('published_date') qs.order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY]) def test_no_ordering_for_get(self): """ Ensure that Doc.objects.get doesn't use any ordering. """ - MONGO_VER = self.mongodb_version - ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby' + ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version) class BlogPost(Document): title = StringField() @@ -1387,13 +1390,13 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY]) # Ordering should be ignored for .get even if we set it explicitly with db_ops_tracker() as q: BlogPost.objects.order_by('-title').get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0][CMD_QUERY_KEY]) def test_find_embedded(self): """Ensure that an embedded document is properly returned from @@ -2532,6 +2535,7 @@ class QuerySetTest(unittest.TestCase): def test_comment(self): """Make sure adding a comment to the query gets added to the query""" MONGO_VER = self.mongodb_version + _, CMD_QUERY_KEY = get_key_compat(MONGO_VER) QUERY_KEY = 'filter' if MONGO_VER >= MONGODB_32 else '$query' COMMENT_KEY = 'comment' if MONGO_VER >= MONGODB_32 else '$comment' @@ -2550,8 +2554,8 @@ class QuerySetTest(unittest.TestCase): ops = q.get_ops() self.assertEqual(len(ops), 2) for op in ops: - self.assertEqual(op['query'][QUERY_KEY], {'age': {'$gte': 18}}) - self.assertEqual(op['query'][COMMENT_KEY], 'looking for an adult') + self.assertEqual(op[CMD_QUERY_KEY][QUERY_KEY], {'age': {'$gte': 18}}) + self.assertEqual(op[CMD_QUERY_KEY][COMMENT_KEY], 'looking for an adult') def test_map_reduce(self): """Ensure map/reduce is both mapping and reducing. @@ -5240,8 +5244,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(op['nreturned'], 1) def test_bool_with_ordering(self): - MONGO_VER = self.mongodb_version - ORDER_BY_KEY = 'sort' if MONGO_VER >= MONGODB_32 else '$orderby' + ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version) class Person(Document): name = StringField() @@ -5260,21 +5263,22 @@ class QuerySetTest(unittest.TestCase): op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertNotIn(ORDER_BY_KEY, op['query']) + self.assertNotIn(ORDER_BY_KEY, op[CMD_QUERY_KEY]) # Check that normal query uses orderby qs2 = Person.objects.order_by('name') - with query_counter() as p: + with query_counter() as q: for x in qs2: pass - op = p.db.system.profile.find({"ns": + op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertIn(ORDER_BY_KEY, op['query']) + self.assertIn(ORDER_BY_KEY, op[CMD_QUERY_KEY]) def test_bool_with_ordering_from_meta_dict(self): + ORDER_BY_KEY, CMD_QUERY_KEY = get_key_compat(self.mongodb_version) class Person(Document): name = StringField() @@ -5296,7 +5300,7 @@ class QuerySetTest(unittest.TestCase): op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertNotIn('$orderby', op['query'], + self.assertNotIn('$orderby', op[CMD_QUERY_KEY], 'BaseQuerySet must remove orderby from meta in boolen test') self.assertEqual(Person.objects.first().name, 'A')