Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae783d4f45 | ||
|
|
1241a902e3 | ||
|
|
fdba648afb | ||
|
|
b070e7de07 | ||
|
|
d0741946c7 | ||
|
|
3cb6a5cfac | ||
|
|
758971e068 | ||
|
|
8739ab9c66 | ||
|
|
e8e47c39d7 | ||
|
|
446c101018 | ||
|
|
3654591a1b | ||
|
|
7fb1c9dd35 | ||
|
|
0fffaccdf4 | ||
|
|
5902b241f9 | ||
|
|
784386fddc | ||
|
|
d424583cbf | ||
|
|
290b821a3a | ||
|
|
a0dfa8d421 | ||
|
|
ceb00f6748 | ||
|
|
9bd328e147 | ||
|
|
6fb5c312c3 | ||
|
|
3f9ff7254f | ||
|
|
f7a3acfaf4 | ||
|
|
e4451ccaf8 | ||
|
|
2adb640821 | ||
|
|
765038274c | ||
|
|
2cbdced974 | ||
|
|
fc5d9ae100 | ||
|
|
506168ab83 | ||
|
|
088fd6334b | ||
|
|
94cda90a6e | ||
|
|
78601d90c9 | ||
|
|
fa4ac95ecc | ||
|
|
dd4d4e23ad | ||
|
|
acba86993d | ||
|
|
0fc55451c2 | ||
|
|
5c0bd8a810 | ||
|
|
1aebc95145 | ||
|
|
1d3f20b666 | ||
|
|
eb2e106871 | ||
|
|
f9a887c8c6 | ||
|
|
67ab810cb2 | ||
|
|
3e0d84383e | ||
|
|
d245ea3eaa | ||
|
|
843fc03bf4 | ||
|
|
c83c635067 | ||
|
|
f605eb14e8 | ||
|
|
fd02d77c59 | ||
|
|
0da8fb379d | ||
|
|
257a43298b | ||
|
|
a2d3bcd571 | ||
|
|
d4142c2cdd | ||
|
|
e50d66b303 | ||
|
|
08b6433843 | ||
|
|
8cd536aab5 | ||
|
|
2b495c648f | ||
|
|
06048b6d71 | ||
|
|
bb22287336 | ||
|
|
a45942a966 | ||
|
|
85d621846d | ||
|
|
534acf8df2 | ||
|
|
5a6d4387ea | ||
|
|
317e844886 | ||
|
|
b1f62a2735 | ||
|
|
65e4fea4ef | ||
|
|
faca8512c5 | ||
|
|
806a80cef1 | ||
|
|
c6f0d5e478 | ||
|
|
a34fd9ac89 | ||
|
|
7efa67e7e6 | ||
|
|
d69808c204 | ||
|
|
de360c61dd | ||
|
|
6b04ddfad1 | ||
|
|
0d854ce906 | ||
|
|
6835c15d9b | ||
|
|
fa38bfd4e8 | ||
|
|
4d5c6d11ab | ||
|
|
7e8c62104a | ||
|
|
fb213f6e74 | ||
|
|
22e75c1691 | ||
|
|
919f221be9 | ||
|
|
4f5b0634ad | ||
|
|
02733e6e58 | ||
|
|
44732a5dd9 | ||
|
|
9a6aa8f8c6 | ||
|
|
9f02f71c52 | ||
|
|
820b5cbb86 | ||
|
|
e6a30f899c | ||
|
|
0bc6507df3 | ||
|
|
b9e922c658 |
10
.travis.yml
10
.travis.yml
@@ -19,8 +19,6 @@ python:
|
|||||||
- pypy
|
- pypy
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- MONGODB=2.6 PYMONGO=2.7
|
|
||||||
- MONGODB=2.6 PYMONGO=2.8
|
|
||||||
- MONGODB=2.6 PYMONGO=3.x
|
- MONGODB=2.6 PYMONGO=3.x
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
@@ -28,14 +26,10 @@ matrix:
|
|||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- python: 2.7
|
|
||||||
env: MONGODB=2.4 PYMONGO=2.7
|
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: MONGODB=2.4 PYMONGO=3.5
|
env: MONGODB=2.4 PYMONGO=3.5
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: MONGODB=3.0 PYMONGO=3.x
|
env: MONGODB=3.0 PYMONGO=3.x
|
||||||
- python: 3.5
|
|
||||||
env: MONGODB=2.4 PYMONGO=2.7
|
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: MONGODB=2.4 PYMONGO=3.5
|
env: MONGODB=2.4 PYMONGO=3.5
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
@@ -97,11 +91,11 @@ deploy:
|
|||||||
distributions: "sdist bdist_wheel"
|
distributions: "sdist bdist_wheel"
|
||||||
|
|
||||||
# only deploy on tagged commits (aka GitHub releases) and only for the
|
# only deploy on tagged commits (aka GitHub releases) and only for the
|
||||||
# parent repo's builds running Python 2.7 along with PyMongo v3.0 (we run
|
# parent repo's builds running Python 2.7 along with PyMongo v3.x (we run
|
||||||
# Travis against many different Python and PyMongo versions and we don't
|
# Travis against many different Python and PyMongo versions and we don't
|
||||||
# want the deploy to occur multiple times).
|
# want the deploy to occur multiple times).
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: MongoEngine/mongoengine
|
repo: MongoEngine/mongoengine
|
||||||
condition: "$PYMONGO = 3.0"
|
condition: "$PYMONGO = 3.x"
|
||||||
python: 2.7
|
python: 2.7
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
dev
|
Changes in 0.15.3
|
||||||
===
|
=================
|
||||||
- Subfield resolve error in generic_emdedded_document query #1651 #1652
|
- Subfield resolve error in generic_emdedded_document query #1651 #1652
|
||||||
- use each modifier only with $position #1673 #1675
|
- use each modifier only with $position #1673 #1675
|
||||||
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
|
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
|
||||||
- Fix validation error instance in GenericEmbeddedDocumentField #1067
|
- Fix validation error instance in GenericEmbeddedDocumentField #1067
|
||||||
- Update cached fields when fields argument is given #1712
|
- Update cached fields when fields argument is given #1712
|
||||||
- Add a db parameter to register_connection for compatibility with connect
|
- Add a db parameter to register_connection for compatibility with connect
|
||||||
|
- Use insert_one, insert_many in Document.insert #1491
|
||||||
|
- Use new update_one, update_many on document/queryset update #1491
|
||||||
|
- Use insert_one, insert_many in Document.insert #1491
|
||||||
|
- Fix reload(fields) affect changed fields #1371
|
||||||
|
- Fix Read-only access to database fails when trying to create indexes #1338
|
||||||
|
|
||||||
Changes in 0.15.0
|
Changes in 0.15.0
|
||||||
=================
|
=================
|
||||||
|
|||||||
@@ -528,8 +528,9 @@ There are a few top level defaults for all indexes that can be set::
|
|||||||
meta = {
|
meta = {
|
||||||
'index_options': {},
|
'index_options': {},
|
||||||
'index_background': True,
|
'index_background': True,
|
||||||
|
'index_cls': False,
|
||||||
|
'auto_create_index': True,
|
||||||
'index_drop_dups': True,
|
'index_drop_dups': True,
|
||||||
'index_cls': False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -542,6 +543,12 @@ There are a few top level defaults for all indexes that can be set::
|
|||||||
:attr:`index_cls` (Optional)
|
:attr:`index_cls` (Optional)
|
||||||
A way to turn off a specific index for _cls.
|
A way to turn off a specific index for _cls.
|
||||||
|
|
||||||
|
:attr:`auto_create_index` (Optional)
|
||||||
|
When this is True (default), MongoEngine will ensure that the correct
|
||||||
|
indexes exist in MongoDB each time a command is run. This can be disabled
|
||||||
|
in systems where indexes are managed separately. Disabling this will improve
|
||||||
|
performance.
|
||||||
|
|
||||||
:attr:`index_drop_dups` (Optional)
|
:attr:`index_drop_dups` (Optional)
|
||||||
Set the default value for if an index should drop duplicates
|
Set the default value for if an index should drop duplicates
|
||||||
|
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ Ordering by text score
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
objects = News.objects.search('mongo').order_by('$text_score')
|
objects = News.objects.search_text('mongo').order_by('$text_score')
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
|
|||||||
list(signals.__all__) + list(errors.__all__))
|
list(signals.__all__) + list(errors.__all__))
|
||||||
|
|
||||||
|
|
||||||
VERSION = (0, 15, 0)
|
VERSION = (0, 15, 3)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ from mongoengine.errors import NotRegistered
|
|||||||
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
|
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
|
||||||
|
|
||||||
|
|
||||||
UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'pop', 'push',
|
UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'mul',
|
||||||
'push_all', 'pull', 'pull_all', 'add_to_set',
|
'pop', 'push', 'push_all', 'pull',
|
||||||
'set_on_insert', 'min', 'max', 'rename'])
|
'pull_all', 'add_to_set', 'set_on_insert',
|
||||||
|
'min', 'max', 'rename'])
|
||||||
|
|
||||||
|
|
||||||
_document_registry = {}
|
_document_registry = {}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
if not hasattr(self, name) and not name.startswith('_'):
|
if not hasattr(self, name) and not name.startswith('_'):
|
||||||
DynamicField = _import_class('DynamicField')
|
DynamicField = _import_class('DynamicField')
|
||||||
field = DynamicField(db_field=name)
|
field = DynamicField(db_field=name, null=True)
|
||||||
field.name = name
|
field.name = name
|
||||||
self._dynamic_fields[name] = field
|
self._dynamic_fields[name] = field
|
||||||
self._fields_ordered += (name,)
|
self._fields_ordered += (name,)
|
||||||
@@ -337,7 +337,7 @@ class BaseDocument(object):
|
|||||||
value = field.generate()
|
value = field.generate()
|
||||||
self._data[field_name] = value
|
self._data[field_name] = value
|
||||||
|
|
||||||
if value is not None:
|
if (value is not None) or (field.null):
|
||||||
if use_db_field:
|
if use_db_field:
|
||||||
data[field.db_field] = value
|
data[field.db_field] = value
|
||||||
else:
|
else:
|
||||||
@@ -1080,5 +1080,11 @@ class BaseDocument(object):
|
|||||||
"""Return the display value for a choice field"""
|
"""Return the display value for a choice field"""
|
||||||
value = getattr(self, field.name)
|
value = getattr(self, field.name)
|
||||||
if field.choices and isinstance(field.choices[0], (list, tuple)):
|
if field.choices and isinstance(field.choices[0], (list, tuple)):
|
||||||
return dict(field.choices).get(value, value)
|
if value is None:
|
||||||
|
return None
|
||||||
|
sep = getattr(field, 'display_sep', ' ')
|
||||||
|
values = value if field.__class__.__name__ in ('ListField', 'SortedListField') else [value]
|
||||||
|
return sep.join([
|
||||||
|
dict(field.choices).get(val, val)
|
||||||
|
for val in values or []])
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -213,7 +213,9 @@ class BaseField(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Choices which are types other than Documents
|
# Choices which are types other than Documents
|
||||||
elif value not in choice_list:
|
else:
|
||||||
|
values = value if isinstance(value, (list, tuple)) else [value]
|
||||||
|
if len(set(values) - set(choice_list)):
|
||||||
self.error('Value must be one of %s' % six.text_type(choice_list))
|
self.error('Value must be one of %s' % six.text_type(choice_list))
|
||||||
|
|
||||||
def _validate(self, value, **kwargs):
|
def _validate(self, value, **kwargs):
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
from contextlib import contextmanager
|
||||||
|
from pymongo.write_concern import WriteConcern
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
||||||
'no_sub_classes', 'query_counter')
|
'no_sub_classes', 'query_counter', 'set_write_concern')
|
||||||
|
|
||||||
|
|
||||||
class switch_db(object):
|
class switch_db(object):
|
||||||
@@ -215,3 +217,10 @@ class query_counter(object):
|
|||||||
count = self.db.system.profile.find(ignore_query).count() - self.counter
|
count = self.db.system.profile.find(ignore_query).count() - self.counter
|
||||||
self.counter += 1
|
self.counter += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def set_write_concern(collection, write_concerns):
|
||||||
|
combined_concerns = dict(collection.write_concern.document.items())
|
||||||
|
combined_concerns.update(write_concerns)
|
||||||
|
yield collection.with_options(write_concern=WriteConcern(**combined_concerns))
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ class DeReference(object):
|
|||||||
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
||||||
item_name = '%s.%s' % (name, k) if name else name
|
item_name = '%s.%s' % (name, k) if name else name
|
||||||
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
|
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
|
||||||
elif hasattr(v, 'id'):
|
elif isinstance(v, DBRef) and hasattr(v, 'id'):
|
||||||
data[k] = self.object_map.get((v.collection, v.id), v)
|
data[k] = self.object_map.get((v.collection, v.id), v)
|
||||||
|
|
||||||
if instance and name:
|
if instance and name:
|
||||||
|
|||||||
@@ -195,7 +195,10 @@ class Document(BaseDocument):
|
|||||||
|
|
||||||
# Ensure indexes on the collection unless auto_create_index was
|
# Ensure indexes on the collection unless auto_create_index was
|
||||||
# set to False.
|
# set to False.
|
||||||
if cls._meta.get('auto_create_index', True):
|
# Also there is no need to ensure indexes on slave.
|
||||||
|
db = cls._get_db()
|
||||||
|
if cls._meta.get('auto_create_index', True) and\
|
||||||
|
db.client.is_primary:
|
||||||
cls.ensure_indexes()
|
cls.ensure_indexes()
|
||||||
|
|
||||||
return cls._collection
|
return cls._collection
|
||||||
@@ -579,7 +582,7 @@ class Document(BaseDocument):
|
|||||||
"""Delete the :class:`~mongoengine.Document` from the database. This
|
"""Delete the :class:`~mongoengine.Document` from the database. This
|
||||||
will only take effect if the document has been previously saved.
|
will only take effect if the document has been previously saved.
|
||||||
|
|
||||||
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
|
:param signal_kwargs: (optional) kwargs dictionary to be passed to
|
||||||
the signal calls.
|
the signal calls.
|
||||||
:param write_concern: Extra keyword arguments are passed down which
|
:param write_concern: Extra keyword arguments are passed down which
|
||||||
will be used as options for the resultant
|
will be used as options for the resultant
|
||||||
@@ -705,7 +708,6 @@ class Document(BaseDocument):
|
|||||||
obj = obj[0]
|
obj = obj[0]
|
||||||
else:
|
else:
|
||||||
raise self.DoesNotExist('Document does not exist')
|
raise self.DoesNotExist('Document does not exist')
|
||||||
|
|
||||||
for field in obj._data:
|
for field in obj._data:
|
||||||
if not fields or field in fields:
|
if not fields or field in fields:
|
||||||
try:
|
try:
|
||||||
@@ -713,7 +715,7 @@ class Document(BaseDocument):
|
|||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
try:
|
try:
|
||||||
# If field is a special field, e.g. items is stored as _reserved_items,
|
# If field is a special field, e.g. items is stored as _reserved_items,
|
||||||
# an KeyError is thrown. So try to retrieve the field from _data
|
# a KeyError is thrown. So try to retrieve the field from _data
|
||||||
setattr(self, field, self._reload(field, obj._data.get(field)))
|
setattr(self, field, self._reload(field, obj._data.get(field)))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If field is removed from the database while the object
|
# If field is removed from the database while the object
|
||||||
@@ -721,7 +723,9 @@ class Document(BaseDocument):
|
|||||||
# i.e. obj.update(unset__field=1) followed by obj.reload()
|
# i.e. obj.update(unset__field=1) followed by obj.reload()
|
||||||
delattr(self, field)
|
delattr(self, field)
|
||||||
|
|
||||||
self._changed_fields = obj._changed_fields
|
self._changed_fields = list(
|
||||||
|
set(self._changed_fields) - set(fields)
|
||||||
|
) if fields else obj._changed_fields
|
||||||
self._created = False
|
self._created = False
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -967,8 +971,16 @@ class Document(BaseDocument):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
required = cls.list_indexes()
|
required = cls.list_indexes()
|
||||||
existing = [info['key']
|
|
||||||
for info in cls._get_collection().index_information().values()]
|
existing = []
|
||||||
|
for info in cls._get_collection().index_information().values():
|
||||||
|
if '_fts' in info['key'][0]:
|
||||||
|
index_type = info['key'][0][1]
|
||||||
|
text_index_fields = info.get('weights').keys()
|
||||||
|
existing.append(
|
||||||
|
[(key, index_type) for key in text_index_fields])
|
||||||
|
else:
|
||||||
|
existing.append(info['key'])
|
||||||
missing = [index for index in required if index not in existing]
|
missing = [index for index in required if index not in existing]
|
||||||
extra = [index for index in existing if index not in required]
|
extra = [index for index in existing if index not in required]
|
||||||
|
|
||||||
@@ -1013,6 +1025,7 @@ class DynamicDocument(Document):
|
|||||||
field_name = args[0]
|
field_name = args[0]
|
||||||
if field_name in self._dynamic_fields:
|
if field_name in self._dynamic_fields:
|
||||||
setattr(self, field_name, None)
|
setattr(self, field_name, None)
|
||||||
|
self._dynamic_fields[field_name].null = False
|
||||||
else:
|
else:
|
||||||
super(DynamicDocument, self).__delattr__(*args, **kwargs)
|
super(DynamicDocument, self).__delattr__(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from mongoengine import signals
|
|||||||
from mongoengine.base import get_document
|
from mongoengine.base import get_document
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.context_managers import switch_db
|
from mongoengine.context_managers import set_write_concern, switch_db
|
||||||
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
||||||
NotUniqueError, OperationError)
|
NotUniqueError, OperationError)
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.python_support import IS_PYMONGO_3
|
||||||
@@ -350,11 +350,24 @@ class BaseQuerySet(object):
|
|||||||
documents=docs, **signal_kwargs)
|
documents=docs, **signal_kwargs)
|
||||||
|
|
||||||
raw = [doc.to_mongo() for doc in docs]
|
raw = [doc.to_mongo() for doc in docs]
|
||||||
|
|
||||||
|
with set_write_concern(self._collection, write_concern) as collection:
|
||||||
|
insert_func = collection.insert_many
|
||||||
|
if return_one:
|
||||||
|
raw = raw[0]
|
||||||
|
insert_func = collection.insert_one
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ids = self._collection.insert(raw, **write_concern)
|
inserted_result = insert_func(raw)
|
||||||
|
ids = return_one and [inserted_result.inserted_id] or inserted_result.inserted_ids
|
||||||
except pymongo.errors.DuplicateKeyError as err:
|
except pymongo.errors.DuplicateKeyError as err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
raise NotUniqueError(message % six.text_type(err))
|
raise NotUniqueError(message % six.text_type(err))
|
||||||
|
except pymongo.errors.BulkWriteError as err:
|
||||||
|
# inserting documents that already have an _id field will
|
||||||
|
# give huge performance debt or raise
|
||||||
|
message = u'Document must not have _id value before bulk write (%s)'
|
||||||
|
raise NotUniqueError(message % six.text_type(err))
|
||||||
except pymongo.errors.OperationFailure as err:
|
except pymongo.errors.OperationFailure as err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
if re.match('^E1100[01] duplicate key', six.text_type(err)):
|
if re.match('^E1100[01] duplicate key', six.text_type(err)):
|
||||||
@@ -368,7 +381,6 @@ class BaseQuerySet(object):
|
|||||||
signals.post_bulk_insert.send(
|
signals.post_bulk_insert.send(
|
||||||
self._document, documents=docs, loaded=False, **signal_kwargs)
|
self._document, documents=docs, loaded=False, **signal_kwargs)
|
||||||
return return_one and ids[0] or ids
|
return return_one and ids[0] or ids
|
||||||
|
|
||||||
documents = self.in_bulk(ids)
|
documents = self.in_bulk(ids)
|
||||||
results = []
|
results = []
|
||||||
for obj_id in ids:
|
for obj_id in ids:
|
||||||
@@ -511,12 +523,15 @@ class BaseQuerySet(object):
|
|||||||
else:
|
else:
|
||||||
update['$set'] = {'_cls': queryset._document._class_name}
|
update['$set'] = {'_cls': queryset._document._class_name}
|
||||||
try:
|
try:
|
||||||
result = queryset._collection.update(query, update, multi=multi,
|
with set_write_concern(queryset._collection, write_concern) as collection:
|
||||||
upsert=upsert, **write_concern)
|
update_func = collection.update_one
|
||||||
|
if multi:
|
||||||
|
update_func = collection.update_many
|
||||||
|
result = update_func(query, update, upsert=upsert)
|
||||||
if full_result:
|
if full_result:
|
||||||
return result
|
return result
|
||||||
elif result:
|
elif result.raw_result:
|
||||||
return result['n']
|
return result.raw_result['n']
|
||||||
except pymongo.errors.DuplicateKeyError as err:
|
except pymongo.errors.DuplicateKeyError as err:
|
||||||
raise NotUniqueError(u'Update failed (%s)' % six.text_type(err))
|
raise NotUniqueError(u'Update failed (%s)' % six.text_type(err))
|
||||||
except pymongo.errors.OperationFailure as err:
|
except pymongo.errors.OperationFailure as err:
|
||||||
@@ -545,10 +560,10 @@ class BaseQuerySet(object):
|
|||||||
write_concern=write_concern,
|
write_concern=write_concern,
|
||||||
full_result=True, **update)
|
full_result=True, **update)
|
||||||
|
|
||||||
if atomic_update['updatedExisting']:
|
if atomic_update.raw_result['updatedExisting']:
|
||||||
document = self.get()
|
document = self.get()
|
||||||
else:
|
else:
|
||||||
document = self._document.objects.with_id(atomic_update['upserted'])
|
document = self._document.objects.with_id(atomic_update.upserted_id)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def update_one(self, upsert=False, write_concern=None, **update):
|
def update_one(self, upsert=False, write_concern=None, **update):
|
||||||
@@ -1183,6 +1198,10 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
pipeline = initial_pipeline + list(pipeline)
|
pipeline = initial_pipeline + list(pipeline)
|
||||||
|
|
||||||
|
if IS_PYMONGO_3 and self._read_preference is not None:
|
||||||
|
return self._collection.with_options(read_preference=self._read_preference) \
|
||||||
|
.aggregate(pipeline, cursor={}, **kwargs)
|
||||||
|
|
||||||
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
|
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
|
||||||
|
|
||||||
# JS functionality
|
# JS functionality
|
||||||
@@ -1579,6 +1598,9 @@ class BaseQuerySet(object):
|
|||||||
if self._batch_size is not None:
|
if self._batch_size is not None:
|
||||||
self._cursor_obj.batch_size(self._batch_size)
|
self._cursor_obj.batch_size(self._batch_size)
|
||||||
|
|
||||||
|
if self._comment is not None:
|
||||||
|
self._cursor_obj.comment(self._comment)
|
||||||
|
|
||||||
return self._cursor_obj
|
return self._cursor_obj
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class QuerySet(BaseQuerySet):
|
|||||||
# Raise StopIteration if we already established there were no more
|
# Raise StopIteration if we already established there were no more
|
||||||
# docs in the db cursor.
|
# docs in the db cursor.
|
||||||
if not self._has_more:
|
if not self._has_more:
|
||||||
raise StopIteration
|
return
|
||||||
|
|
||||||
# Otherwise, populate more of the cache and repeat.
|
# Otherwise, populate more of the cache and repeat.
|
||||||
if len(self._result_cache) <= pos:
|
if len(self._result_cache) <= pos:
|
||||||
|
|||||||
@@ -314,11 +314,17 @@ def update(_doc_cls=None, **update):
|
|||||||
field_classes = [c.__class__ for c in cleaned_fields]
|
field_classes = [c.__class__ for c in cleaned_fields]
|
||||||
field_classes.reverse()
|
field_classes.reverse()
|
||||||
ListField = _import_class('ListField')
|
ListField = _import_class('ListField')
|
||||||
if ListField in field_classes:
|
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
|
||||||
# Join all fields via dot notation to the last ListField
|
if ListField in field_classes or EmbeddedDocumentListField in field_classes:
|
||||||
|
# Join all fields via dot notation to the last ListField or EmbeddedDocumentListField
|
||||||
# Then process as normal
|
# Then process as normal
|
||||||
|
if ListField in field_classes:
|
||||||
|
_check_field = ListField
|
||||||
|
else:
|
||||||
|
_check_field = EmbeddedDocumentListField
|
||||||
|
|
||||||
last_listField = len(
|
last_listField = len(
|
||||||
cleaned_fields) - field_classes.index(ListField)
|
cleaned_fields) - field_classes.index(_check_field)
|
||||||
key = '.'.join(parts[:last_listField])
|
key = '.'.join(parts[:last_listField])
|
||||||
parts = parts[last_listField:]
|
parts = parts[last_listField:]
|
||||||
parts.insert(0, key)
|
parts.insert(0, key)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from mongoengine import *
|
|||||||
|
|
||||||
from mongoengine.queryset import NULLIFY, PULL
|
from mongoengine.queryset import NULLIFY, PULL
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
|
from tests.utils import needs_mongodb_v26
|
||||||
|
|
||||||
__all__ = ("ClassMethodsTest", )
|
__all__ = ("ClassMethodsTest", )
|
||||||
|
|
||||||
@@ -187,6 +188,26 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
|
||||||
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
|
||||||
|
|
||||||
|
@needs_mongodb_v26
|
||||||
|
def test_compare_indexes_for_text_indexes(self):
|
||||||
|
""" Ensure that compare_indexes behaves correctly for text indexes """
|
||||||
|
|
||||||
|
class Doc(Document):
|
||||||
|
a = StringField()
|
||||||
|
b = StringField()
|
||||||
|
meta = {'indexes': [
|
||||||
|
{'fields': ['$a', "$b"],
|
||||||
|
'default_language': 'english',
|
||||||
|
'weights': {'a': 10, 'b': 2}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
|
||||||
|
Doc.drop_collection()
|
||||||
|
Doc.ensure_indexes()
|
||||||
|
actual = Doc.compare_indexes()
|
||||||
|
expected = {'missing': [], 'extra': []}
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_list_indexes_inheritance(self):
|
def test_list_indexes_inheritance(self):
|
||||||
""" ensure that all of the indexes are listed regardless of the super-
|
""" ensure that all of the indexes are listed regardless of the super-
|
||||||
or sub-class that we call it from
|
or sub-class that we call it from
|
||||||
|
|||||||
@@ -476,6 +476,24 @@ class InstanceTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
|
def test_reload_with_changed_fields(self):
|
||||||
|
"""Ensures reloading will not affect changed fields"""
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
number = IntField()
|
||||||
|
User.drop_collection()
|
||||||
|
|
||||||
|
user = User(name="Bob", number=1).save()
|
||||||
|
user.name = "John"
|
||||||
|
user.number = 2
|
||||||
|
|
||||||
|
self.assertEqual(user._get_changed_fields(), ['name', 'number'])
|
||||||
|
user.reload('number')
|
||||||
|
self.assertEqual(user._get_changed_fields(), ['name'])
|
||||||
|
user.save()
|
||||||
|
user.reload()
|
||||||
|
self.assertEqual(user.name, "John")
|
||||||
|
|
||||||
def test_reload_referencing(self):
|
def test_reload_referencing(self):
|
||||||
"""Ensures reloading updates weakrefs correctly."""
|
"""Ensures reloading updates weakrefs correctly."""
|
||||||
class Embedded(EmbeddedDocument):
|
class Embedded(EmbeddedDocument):
|
||||||
@@ -521,7 +539,7 @@ class InstanceTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
doc.dict_field['extra'] = 1
|
doc.dict_field['extra'] = 1
|
||||||
doc = doc.reload(10, 'list_field')
|
doc = doc.reload(10, 'list_field')
|
||||||
self.assertEqual(doc._get_changed_fields(), [])
|
self.assertEqual(doc._get_changed_fields(), ['dict_field.extra'])
|
||||||
self.assertEqual(len(doc.list_field), 5)
|
self.assertEqual(len(doc.list_field), 5)
|
||||||
self.assertEqual(len(doc.dict_field), 3)
|
self.assertEqual(len(doc.dict_field), 3)
|
||||||
self.assertEqual(len(doc.embedded_field.list_field), 4)
|
self.assertEqual(len(doc.embedded_field.list_field), 4)
|
||||||
|
|||||||
@@ -920,6 +920,12 @@ class FieldTest(MongoDBTestCase):
|
|||||||
|
|
||||||
def test_list_validation(self):
|
def test_list_validation(self):
|
||||||
"""Ensure that a list field only accepts lists with valid elements."""
|
"""Ensure that a list field only accepts lists with valid elements."""
|
||||||
|
AccessLevelChoices = (
|
||||||
|
('a', u'Administration'),
|
||||||
|
('b', u'Manager'),
|
||||||
|
('c', u'Staff'),
|
||||||
|
)
|
||||||
|
|
||||||
class User(Document):
|
class User(Document):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -934,6 +940,7 @@ class FieldTest(MongoDBTestCase):
|
|||||||
authors_as_lazy = ListField(LazyReferenceField(User))
|
authors_as_lazy = ListField(LazyReferenceField(User))
|
||||||
generic = ListField(GenericReferenceField())
|
generic = ListField(GenericReferenceField())
|
||||||
generic_as_lazy = ListField(GenericLazyReferenceField())
|
generic_as_lazy = ListField(GenericLazyReferenceField())
|
||||||
|
access_list = ListField(choices=AccessLevelChoices, display_sep=', ')
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
@@ -951,6 +958,17 @@ class FieldTest(MongoDBTestCase):
|
|||||||
post.tags = ('fun', 'leisure')
|
post.tags = ('fun', 'leisure')
|
||||||
post.validate()
|
post.validate()
|
||||||
|
|
||||||
|
post.access_list = 'a,b'
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.access_list = ['c', 'd']
|
||||||
|
self.assertRaises(ValidationError, post.validate)
|
||||||
|
|
||||||
|
post.access_list = ['a', 'b']
|
||||||
|
post.validate()
|
||||||
|
|
||||||
|
self.assertEqual(post.get_access_list_display(), u'Administration, Manager')
|
||||||
|
|
||||||
post.comments = ['a']
|
post.comments = ['a']
|
||||||
self.assertRaises(ValidationError, post.validate)
|
self.assertRaises(ValidationError, post.validate)
|
||||||
post.comments = 'yay'
|
post.comments = 'yay'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from nose.plugins.skip import SkipTest
|
|||||||
import pymongo
|
import pymongo
|
||||||
from pymongo.errors import ConfigurationError
|
from pymongo.errors import ConfigurationError
|
||||||
from pymongo.read_preferences import ReadPreference
|
from pymongo.read_preferences import ReadPreference
|
||||||
|
from pymongo.results import UpdateResult
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
@@ -589,6 +590,20 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
Scores.objects(id=scores.id).update(max__high_score=500)
|
Scores.objects(id=scores.id).update(max__high_score=500)
|
||||||
self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000)
|
self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000)
|
||||||
|
|
||||||
|
@needs_mongodb_v26
|
||||||
|
def test_update_multiple(self):
|
||||||
|
class Product(Document):
|
||||||
|
item = StringField()
|
||||||
|
price = FloatField()
|
||||||
|
|
||||||
|
product = Product.objects.create(item='ABC', price=10.99)
|
||||||
|
product = Product.objects.create(item='ABC', price=10.99)
|
||||||
|
Product.objects(id=product.id).update(mul__price=1.25)
|
||||||
|
self.assertEqual(Product.objects.get(id=product.id).price, 13.7375)
|
||||||
|
unknown_product = Product.objects.create(item='Unknown')
|
||||||
|
Product.objects(id=unknown_product.id).update(mul__price=100)
|
||||||
|
self.assertEqual(Product.objects.get(id=unknown_product.id).price, 0)
|
||||||
|
|
||||||
def test_updates_can_have_match_operators(self):
|
def test_updates_can_have_match_operators(self):
|
||||||
|
|
||||||
class Comment(EmbeddedDocument):
|
class Comment(EmbeddedDocument):
|
||||||
@@ -656,14 +671,14 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
result = self.Person(name="Bob", age=25).update(
|
result = self.Person(name="Bob", age=25).update(
|
||||||
upsert=True, full_result=True)
|
upsert=True, full_result=True)
|
||||||
self.assertTrue(isinstance(result, dict))
|
self.assertTrue(isinstance(result, UpdateResult))
|
||||||
self.assertTrue("upserted" in result)
|
self.assertTrue("upserted" in result.raw_result)
|
||||||
self.assertFalse(result["updatedExisting"])
|
self.assertFalse(result.raw_result["updatedExisting"])
|
||||||
|
|
||||||
bob = self.Person.objects.first()
|
bob = self.Person.objects.first()
|
||||||
result = bob.update(set__age=30, full_result=True)
|
result = bob.update(set__age=30, full_result=True)
|
||||||
self.assertTrue(isinstance(result, dict))
|
self.assertTrue(isinstance(result, UpdateResult))
|
||||||
self.assertTrue(result["updatedExisting"])
|
self.assertTrue(result.raw_result["updatedExisting"])
|
||||||
|
|
||||||
self.Person(name="Bob", age=20).save()
|
self.Person(name="Bob", age=20).save()
|
||||||
result = self.Person.objects(name="Bob").update(
|
result = self.Person.objects(name="Bob").update(
|
||||||
@@ -830,9 +845,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
blogs.append(Blog(title="post %s" % i, posts=[post1, post2]))
|
blogs.append(Blog(title="post %s" % i, posts=[post1, post2]))
|
||||||
|
|
||||||
Blog.objects.insert(blogs, load_bulk=False)
|
Blog.objects.insert(blogs, load_bulk=False)
|
||||||
if mongodb_version < (2, 6):
|
|
||||||
self.assertEqual(q, 1)
|
|
||||||
else:
|
|
||||||
# profiling logs each doc now in the bulk op
|
# profiling logs each doc now in the bulk op
|
||||||
self.assertEqual(q, 99)
|
self.assertEqual(q, 99)
|
||||||
|
|
||||||
@@ -843,11 +855,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 0)
|
self.assertEqual(q, 0)
|
||||||
|
|
||||||
Blog.objects.insert(blogs)
|
Blog.objects.insert(blogs)
|
||||||
if mongodb_version < (2, 6):
|
self.assertEqual(q, 100) # 99 for insert 1 for fetch
|
||||||
self.assertEqual(q, 2) # 1 for insert, and 1 for in bulk fetch
|
|
||||||
else:
|
|
||||||
# 99 for insert, and 1 for in bulk fetch
|
|
||||||
self.assertEqual(q, 100)
|
|
||||||
|
|
||||||
Blog.drop_collection()
|
Blog.drop_collection()
|
||||||
|
|
||||||
@@ -912,10 +920,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(Blog.objects.count(), 2)
|
self.assertEqual(Blog.objects.count(), 2)
|
||||||
|
|
||||||
Blog.objects.insert([blog2, blog3],
|
|
||||||
write_concern={"w": 0, 'continue_on_error': True})
|
|
||||||
self.assertEqual(Blog.objects.count(), 3)
|
|
||||||
|
|
||||||
def test_get_changed_fields_query_count(self):
|
def test_get_changed_fields_query_count(self):
|
||||||
"""Make sure we don't perform unnecessary db operations when
|
"""Make sure we don't perform unnecessary db operations when
|
||||||
none of document's fields were updated.
|
none of document's fields were updated.
|
||||||
@@ -2383,12 +2387,17 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
age = IntField()
|
age = IntField()
|
||||||
|
|
||||||
with db_ops_tracker() as q:
|
with db_ops_tracker() as q:
|
||||||
adult = (User.objects.filter(age__gte=18)
|
adult1 = (User.objects.filter(age__gte=18)
|
||||||
.comment('looking for an adult')
|
.comment('looking for an adult')
|
||||||
.first())
|
.first())
|
||||||
|
|
||||||
|
adult2 = (User.objects.comment('looking for an adult')
|
||||||
|
.filter(age__gte=18)
|
||||||
|
.first())
|
||||||
|
|
||||||
ops = q.get_ops()
|
ops = q.get_ops()
|
||||||
self.assertEqual(len(ops), 1)
|
self.assertEqual(len(ops), 2)
|
||||||
op = ops[0]
|
for op in ops:
|
||||||
self.assertEqual(op['query']['$query'], {'age': {'$gte': 18}})
|
self.assertEqual(op['query']['$query'], {'age': {'$gte': 18}})
|
||||||
self.assertEqual(op['query']['$comment'], 'looking for an adult')
|
self.assertEqual(op['query']['$comment'], 'looking for an adult')
|
||||||
|
|
||||||
@@ -4429,6 +4438,26 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(bars._cursor._Cursor__read_preference,
|
self.assertEqual(bars._cursor._Cursor__read_preference,
|
||||||
ReadPreference.SECONDARY_PREFERRED)
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
@needs_mongodb_v26
|
||||||
|
def test_read_preference_aggregation_framework(self):
|
||||||
|
class Bar(Document):
|
||||||
|
txt = StringField()
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
'indexes': ['txt']
|
||||||
|
}
|
||||||
|
# Aggregates with read_preference
|
||||||
|
bars = Bar.objects \
|
||||||
|
.read_preference(ReadPreference.SECONDARY_PREFERRED) \
|
||||||
|
.aggregate()
|
||||||
|
if IS_PYMONGO_3:
|
||||||
|
self.assertEqual(bars._CommandCursor__collection.read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
else:
|
||||||
|
self.assertNotEqual(bars._CommandCursor__collection.read_preference,
|
||||||
|
ReadPreference.SECONDARY_PREFERRED)
|
||||||
|
|
||||||
|
|
||||||
def test_json_simple(self):
|
def test_json_simple(self):
|
||||||
|
|
||||||
class Embedded(EmbeddedDocument):
|
class Embedded(EmbeddedDocument):
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from bson.son import SON
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.queryset import Q, transform
|
from mongoengine.queryset import Q, transform
|
||||||
|
|
||||||
@@ -259,6 +261,30 @@ class TransformTest(unittest.TestCase):
|
|||||||
with self.assertRaises(InvalidQueryError):
|
with self.assertRaises(InvalidQueryError):
|
||||||
events.count()
|
events.count()
|
||||||
|
|
||||||
|
def test_update_pull_for_list_fields(self):
|
||||||
|
"""
|
||||||
|
Test added to check pull operation in update for
|
||||||
|
EmbeddedDocumentListField which is inside a EmbeddedDocumentField
|
||||||
|
"""
|
||||||
|
class Word(EmbeddedDocument):
|
||||||
|
word = StringField()
|
||||||
|
index = IntField()
|
||||||
|
|
||||||
|
class SubDoc(EmbeddedDocument):
|
||||||
|
heading = ListField(StringField())
|
||||||
|
text = EmbeddedDocumentListField(Word)
|
||||||
|
|
||||||
|
class MainDoc(Document):
|
||||||
|
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)])}})
|
||||||
|
|
||||||
|
update = transform.update(MainDoc, pull__content__heading='xyz')
|
||||||
|
self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
6
tox.ini
6
tox.ini
@@ -1,14 +1,12 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg35,mg3x}
|
envlist = {py27,py35,pypy,pypy3}-{mg35,mg3x}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
python setup.py nosetests {posargs}
|
python setup.py nosetests {posargs}
|
||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
mg27: PyMongo<2.8
|
|
||||||
mg28: PyMongo>=2.8,<2.9
|
|
||||||
mg35: PyMongo==3.5
|
mg35: PyMongo==3.5
|
||||||
mg3x: PyMongo>=3.0
|
mg3x: PyMongo>=3.0,<3.7
|
||||||
setenv =
|
setenv =
|
||||||
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
||||||
|
|||||||
Reference in New Issue
Block a user