Compare commits

...

26 Commits

Author SHA1 Message Date
erdenezul
18a5fba42b Revert "add tests to increase code coverage" 2017-12-22 20:19:21 +08:00
erdenezul
b5a3b6f86a Merge pull request #1645 from erdenezul/inc_cov
add tests to increase code coverage
2017-12-22 20:14:31 +08:00
Emmanuel Leblond
2f088ce29e Merge pull request #1710 from esmail/patch-1
One-character typo fix ("that" -> "than")
2017-12-22 12:27:47 +01:00
Emmanuel Leblond
ff408c604b Merge pull request #1703 from erdenezul/invalid_instance_genericembedded
fix validation error for invalid embedded document instance #1067
2017-12-22 12:23:31 +01:00
Esmail
7674dc9b34 One-character typo fix ("that" -> "than") 2017-12-05 15:14:15 -05:00
Erdenezul
9e0ca51c2f remove merge conflict after rebase #1067 2017-12-01 08:40:51 +08:00
Erdenezul
961629d156 add changelog into dev #1067 2017-12-01 08:40:00 +08:00
Erdenezul Batmunkh
2cbebf9c99 move changes to development 2017-12-01 08:38:41 +08:00
Erdenezul Batmunkh
08a4deca17 change description in changelog 2017-12-01 08:37:53 +08:00
Erdenezul Batmunkh
ce9ea7baad add changelog 2017-12-01 08:37:53 +08:00
Erdenezul Batmunkh
b35efb9f72 fix validatione error for invalid embedded document instance #1067 2017-12-01 08:37:53 +08:00
Emmanuel Leblond
c45dfacb41 Update changelog 2017-11-29 16:41:42 +01:00
Emmanuel Leblond
91152a7977 Merge pull request #1704 from touilleMan/lazyref-improvements
Improve LazyReferenceField and GenericLazyReferenceField with nested …
2017-11-29 16:38:11 +01:00
Erdenezul Batmunkh
0ce081323f move changes to development 2017-11-22 22:19:50 +08:00
Erdenezul Batmunkh
79486e3393 change description in changelog 2017-11-22 19:27:35 +08:00
Erdenezul Batmunkh
60758dd76b add changelog 2017-11-22 18:56:12 +08:00
Emmanuel Leblond
e74f659015 Improve LazyReferenceField and GenericLazyReferenceField with nested fields 2017-11-22 11:44:49 +01:00
Erdenezul Batmunkh
c1c09fa6b4 fix validatione error for invalid embedded document instance #1067 2017-11-21 21:56:26 +08:00
Emmanuel Leblond
47c7cb9327 Ignore style I202 rule (see https://github.com/PyCQA/flake8-import-order/issues/122) 2017-11-15 20:44:36 +01:00
Emmanuel Leblond
4d6256e1a1 Merge pull request #1675 from erdenezul/push_list_of_list
use each modifier only with $position  #1673
2017-11-15 08:43:29 +01:00
Emmanuel Leblond
13180d92e3 Merge pull request #1652 from erdenezul/generic_embedded_query
Subfield resolve error in generic_emdedded_document query #1651
2017-11-13 15:29:26 +01:00
Erdenezul
9ab856e186 use each modifier only with #1673 2017-10-10 10:34:34 +08:00
Erdenezul
aa4996ef28 fix bug query subfield in generic_embedded_document #1651 2017-09-15 11:18:24 +08:00
Erdenezul Batmunkh
be8f1b9fdd add failing test for generic_emdedded_document query #1651 2017-09-14 22:24:27 +09:00
Erdenezul Batmunkh
ba99190f53 add tests to increase coverage 2017-09-10 13:09:20 +09:00
Erdenezul Batmunkh
70088704e2 add tests to increase code coverage 2017-09-10 01:37:17 +09:00
10 changed files with 241 additions and 27 deletions

View File

@@ -2,6 +2,13 @@
Changelog
=========
dev
===
- Subfield resolve error in generic_emdedded_document query #1651 #1652
- use each modifier only with $position #1673 #1675
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
- Fix validation error instance in GenericEmbeddedDocumentField #1067
Changes in 0.15.0
=================
- Add LazyReferenceField and GenericLazyReferenceField to address #1230

View File

@@ -153,7 +153,7 @@ post. This works, but there is no real reason to be storing the comments
separately from their associated posts, other than to work around the
relational model. Using MongoDB we can store the comments as a list of
*embedded documents* directly on a post document. An embedded document should
be treated no differently that a regular document; it just doesn't have its own
be treated no differently than a regular document; it just doesn't have its own
collection in the database. Using MongoEngine, we can define the structure of
embedded documents, along with utility methods, in exactly the same way we do
with regular documents::

View File

@@ -13,6 +13,7 @@ from mongoengine import signals
from mongoengine.base.common import get_document
from mongoengine.base.datastructures import (BaseDict, BaseList,
EmbeddedDocumentList,
LazyReference,
StrictDict)
from mongoengine.base.fields import ComplexBaseField
from mongoengine.common import _import_class
@@ -488,7 +489,7 @@ class BaseDocument(object):
else:
data = getattr(data, part, None)
if hasattr(data, '_changed_fields'):
if not isinstance(data, LazyReference) and hasattr(data, '_changed_fields'):
if getattr(data, '_is_document', False):
continue

View File

@@ -3,6 +3,7 @@ import six
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
@@ -99,7 +100,10 @@ class DeReference(object):
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in item._fields.iteritems():
v = item._data.get(field_name, None)
if isinstance(v, DBRef):
if isinstance(v, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
continue
elif isinstance(v, DBRef):
reference_map.setdefault(field.document_type, set()).add(v.id)
elif isinstance(v, (dict, SON)) and '_ref' in v:
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
@@ -110,6 +114,9 @@ class DeReference(object):
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
key = field_cls
reference_map.setdefault(key, set()).update(refs)
elif isinstance(item, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
continue
elif isinstance(item, DBRef):
reference_map.setdefault(item.collection, set()).add(item.id)
elif isinstance(item, (dict, SON)) and '_ref' in item:

View File

@@ -28,6 +28,7 @@ except ImportError:
from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField,
GeoJsonBaseField, LazyReference, ObjectIdField,
get_document)
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
@@ -688,16 +689,28 @@ class GenericEmbeddedDocumentField(BaseField):
return value
def validate(self, value, clean=True):
if self.choices and isinstance(value, SON):
for choice in self.choices:
if value['_cls'] == choice._class_name:
return True
if not isinstance(value, EmbeddedDocument):
self.error('Invalid embedded document instance provided to an '
'GenericEmbeddedDocumentField')
value.validate(clean=clean)
def lookup_member(self, member_name):
if self.choices:
for choice in self.choices:
field = choice._fields.get(member_name)
if field:
return field
return None
def to_mongo(self, document, use_db_field=True, fields=None):
if document is None:
return None
data = document.to_mongo(use_db_field, fields)
if '_cls' not in data:
data['_cls'] = document._class_name
@@ -781,6 +794,17 @@ class ListField(ComplexBaseField):
kwargs.setdefault('default', lambda: [])
super(ListField, self).__init__(**kwargs)
def __get__(self, instance, owner):
if instance is None:
# Document class being used rather than a document object
return self
value = instance._data.get(self.name)
LazyReferenceField = _import_class('LazyReferenceField')
GenericLazyReferenceField = _import_class('GenericLazyReferenceField')
if isinstance(self.field, (LazyReferenceField, GenericLazyReferenceField)) and value:
instance._data[self.name] = [self.field.build_lazyref(x) for x in value]
return super(ListField, self).__get__(instance, owner)
def validate(self, value):
"""Make sure that a list of valid fields is being used."""
if (not isinstance(value, (list, tuple, QuerySet)) or
@@ -2203,17 +2227,10 @@ class LazyReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing."""
if instance is None:
# Document class being used rather than a document object
return self
value = instance._data.get(self.name)
def build_lazyref(self, value):
if isinstance(value, LazyReference):
if value.passthrough != self.passthrough:
instance._data[self.name] = LazyReference(
value.document_type, value.pk, passthrough=self.passthrough)
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
elif value is not None:
if isinstance(value, self.document_type):
value = LazyReference(self.document_type, value.pk, passthrough=self.passthrough)
@@ -2222,6 +2239,16 @@ class LazyReferenceField(BaseField):
else:
# value is the primary key of the referenced document
value = LazyReference(self.document_type, value, passthrough=self.passthrough)
return value
def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing."""
if instance is None:
# Document class being used rather than a document object
return self
value = self.build_lazyref(instance._data.get(self.name))
if value:
instance._data[self.name] = value
return super(LazyReferenceField, self).__get__(instance, owner)
@@ -2246,7 +2273,7 @@ class LazyReferenceField(BaseField):
def validate(self, value):
if isinstance(value, LazyReference):
if not issubclass(value.document_type, self.document_type):
if value.collection != self.document_type._get_collection_name():
self.error('Reference must be on a `%s` document.' % self.document_type)
pk = value.pk
elif isinstance(value, self.document_type):
@@ -2306,23 +2333,26 @@ class GenericLazyReferenceField(GenericReferenceField):
def _validate_choices(self, value):
if isinstance(value, LazyReference):
value = value.document_type
value = value.document_type._class_name
super(GenericLazyReferenceField, self)._validate_choices(value)
def __get__(self, instance, owner):
if instance is None:
return self
value = instance._data.get(self.name)
def build_lazyref(self, value):
if isinstance(value, LazyReference):
if value.passthrough != self.passthrough:
instance._data[self.name] = LazyReference(
value.document_type, value.pk, passthrough=self.passthrough)
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
elif value is not None:
if isinstance(value, (dict, SON)):
value = LazyReference(get_document(value['_cls']), value['_ref'].id, passthrough=self.passthrough)
elif isinstance(value, Document):
value = LazyReference(type(value), value.pk, passthrough=self.passthrough)
return value
def __get__(self, instance, owner):
if instance is None:
return self
value = self.build_lazyref(instance._data.get(self.name))
if value:
instance._data[self.name] = value
return super(GenericLazyReferenceField, self).__get__(instance, owner)
@@ -2340,7 +2370,7 @@ class GenericLazyReferenceField(GenericReferenceField):
if isinstance(document, LazyReference):
return SON((
('_cls', document.document_type._class_name),
('_ref', document)
('_ref', DBRef(document.document_type._get_collection_name(), document.pk))
))
else:
return super(GenericLazyReferenceField, self).to_mongo(document)

View File

@@ -344,8 +344,6 @@ def update(_doc_cls=None, **update):
if not isinstance(value, (set, tuple, list)):
value = [value]
value = {key: {'$each': value, '$position': position}}
elif isinstance(value, list):
value = {key: {'$each': value}}
else:
value = {key: value}
else:

View File

@@ -1,11 +1,11 @@
[nosetests]
verbosity=2
detailed-errors=1
tests=tests
#tests=tests
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201
ignore=E501,F401,F403,F405,I201,I202
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests

View File

@@ -3183,6 +3183,17 @@ class InstanceTest(unittest.TestCase):
blog.reload()
self.assertEqual(blog.tags, ['mongodb', 'code', 'python'])
def test_push_nested_list(self):
"""Ensure that push update works in nested list"""
class BlogPost(Document):
slug = StringField()
tags = ListField()
blog = BlogPost(slug="test").save()
blog.update(push__tags=["value1", 123])
blog.reload()
self.assertEqual(blog.tags, [["value1", 123]])
if __name__ == '__main__':
unittest.main()

View File

@@ -4871,6 +4871,48 @@ class LazyReferenceFieldTest(MongoDBTestCase):
self.assertNotEqual(animal, other_animalref)
self.assertNotEqual(other_animalref, animal)
def test_lazy_reference_embedded(self):
class Animal(Document):
name = StringField()
tag = StringField()
class EmbeddedOcurrence(EmbeddedDocument):
in_list = ListField(LazyReferenceField(Animal))
direct = LazyReferenceField(Animal)
class Ocurrence(Document):
in_list = ListField(LazyReferenceField(Animal))
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
direct = LazyReferenceField(Animal)
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
).save()
check_fields_type(occ)
occ.reload()
check_fields_type(occ)
occ.direct = animal1.id
occ.in_list = [animal1.id, animal2.id]
occ.in_embedded.direct = animal1.id
occ.in_embedded.in_list = [animal1.id, animal2.id]
check_fields_type(occ)
class GenericLazyReferenceFieldTest(MongoDBTestCase):
def test_generic_lazy_reference_simple(self):
@@ -5051,6 +5093,50 @@ class GenericLazyReferenceFieldTest(MongoDBTestCase):
p = Ocurrence.objects.get()
self.assertIs(p.animal, None)
def test_generic_lazy_reference_embedded(self):
class Animal(Document):
name = StringField()
tag = StringField()
class EmbeddedOcurrence(EmbeddedDocument):
in_list = ListField(GenericLazyReferenceField())
direct = GenericLazyReferenceField()
class Ocurrence(Document):
in_list = ListField(GenericLazyReferenceField())
in_embedded = EmbeddedDocumentField(EmbeddedOcurrence)
direct = GenericLazyReferenceField()
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal('doggo').save()
animal2 = Animal('cheeta').save()
def check_fields_type(occ):
self.assertIsInstance(occ.direct, LazyReference)
for elem in occ.in_list:
self.assertIsInstance(elem, LazyReference)
self.assertIsInstance(occ.in_embedded.direct, LazyReference)
for elem in occ.in_embedded.in_list:
self.assertIsInstance(elem, LazyReference)
occ = Ocurrence(
in_list=[animal1, animal2],
in_embedded={'in_list': [animal1, animal2], 'direct': animal1},
direct=animal1
).save()
check_fields_type(occ)
occ.reload()
check_fields_type(occ)
animal1_ref = {'_cls': 'Animal', '_ref': DBRef(animal1._get_collection_name(), animal1.pk)}
animal2_ref = {'_cls': 'Animal', '_ref': DBRef(animal2._get_collection_name(), animal2.pk)}
occ.direct = animal1_ref
occ.in_list = [animal1_ref, animal2_ref]
occ.in_embedded.direct = animal1_ref
occ.in_embedded.in_list = [animal1_ref, animal2_ref]
check_fields_type(occ)
if __name__ == '__main__':
unittest.main()

View File

@@ -1929,6 +1929,21 @@ class QuerySetTest(unittest.TestCase):
post.reload()
self.assertEqual(post.tags, ['scala', 'mongodb', 'python', 'java'])
def test_update_push_list_of_list(self):
"""Ensure that the 'push' update operation works in the list of list
"""
class BlogPost(Document):
slug = StringField()
tags = ListField()
BlogPost.drop_collection()
post = BlogPost(slug="test").save()
BlogPost.objects.filter(slug="test").update(push__tags=["value1", 123])
post.reload()
self.assertEqual(post.tags, [["value1", 123]])
def test_update_push_and_pull_add_to_set(self):
"""Ensure that the 'pull' update operation works correctly.
"""
@@ -2071,6 +2086,23 @@ class QuerySetTest(unittest.TestCase):
Site.objects(id=s.id).update_one(
pull_all__collaborators__helpful__user=['Ross'])
def test_pull_in_genericembedded_field(self):
class Foo(EmbeddedDocument):
name = StringField()
class Bar(Document):
foos = ListField(GenericEmbeddedDocumentField(
choices=[Foo, ]))
Bar.drop_collection()
foo = Foo(name="bar")
bar = Bar(foos=[foo]).save()
Bar.objects(id=bar.id).update(pull__foos=foo)
bar.reload()
self.assertEqual(len(bar.foos), 0)
def test_update_one_pop_generic_reference(self):
class BlogTag(Document):
@@ -2164,6 +2196,24 @@ class QuerySetTest(unittest.TestCase):
self.assertEqual(message.authors[1].name, "Ross")
self.assertEqual(message.authors[2].name, "Adam")
def test_set_generic_embedded_documents(self):
class Bar(EmbeddedDocument):
name = StringField()
class User(Document):
username = StringField()
bar = GenericEmbeddedDocumentField(choices=[Bar,])
User.drop_collection()
User(username='abc').save()
User.objects(username='abc').update(
set__bar=Bar(name='test'), upsert=True)
user = User.objects(username='abc').first()
self.assertEqual(user.bar.name, "test")
def test_reload_embedded_docs_instance(self):
class SubDoc(EmbeddedDocument):
@@ -4790,6 +4840,30 @@ class QuerySetTest(unittest.TestCase):
for obj in C.objects.no_sub_classes():
self.assertEqual(obj.__class__, C)
def test_query_generic_embedded_document(self):
"""Ensure that querying sub field on generic_embedded_field works
"""
class A(EmbeddedDocument):
a_name = StringField()
class B(EmbeddedDocument):
b_name = StringField()
class Doc(Document):
document = GenericEmbeddedDocumentField(choices=(A, B))
Doc.drop_collection()
Doc(document=A(a_name='A doc')).save()
Doc(document=B(b_name='B doc')).save()
# Using raw in filter working fine
self.assertEqual(Doc.objects(
__raw__={'document.a_name': 'A doc'}).count(), 1)
self.assertEqual(Doc.objects(
__raw__={'document.b_name': 'B doc'}).count(), 1)
self.assertEqual(Doc.objects(document__a_name='A doc').count(), 1)
self.assertEqual(Doc.objects(document__b_name='B doc').count(), 1)
def test_query_reference_to_custom_pk_doc(self):
class A(Document):