Merge branch 'master' of github.com:MongoEngine/mongoengine into py2py3_improve_compat
This commit is contained in:
@@ -28,7 +28,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
VERSION = (0, 18, 2)
|
||||
VERSION = (0, 19, 1)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
||||
@@ -121,6 +121,9 @@ class BaseList(list):
|
||||
super(BaseList, self).__init__(list_items)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# change index to positive value because MongoDB does not support negative one
|
||||
if isinstance(key, int) and key < 0:
|
||||
key = len(self) + key
|
||||
value = super(BaseList, self).__getitem__(key)
|
||||
|
||||
if isinstance(key, slice):
|
||||
|
||||
@@ -330,7 +330,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
):
|
||||
"""Save the :class:`~mongoengine.Document` to the database. If the
|
||||
document already exists, it will be updated, otherwise it will be
|
||||
created.
|
||||
created. Returns the saved object instance.
|
||||
|
||||
:param force_insert: only try to create a new document, don't allow
|
||||
updates of existing documents.
|
||||
|
||||
@@ -38,6 +38,7 @@ 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
|
||||
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
|
||||
from mongoengine.python_support import StringIO
|
||||
from mongoengine.queryset import DO_NOTHING
|
||||
from mongoengine.queryset.base import BaseQuerySet
|
||||
@@ -1043,6 +1044,15 @@ def key_has_dot_or_dollar(d):
|
||||
return True
|
||||
|
||||
|
||||
def key_starts_with_dollar(d):
|
||||
"""Helper function to recursively determine if any key in a
|
||||
dictionary starts with a dollar
|
||||
"""
|
||||
for k, v in d.items():
|
||||
if (k.startswith("$")) or (isinstance(v, dict) and key_starts_with_dollar(v)):
|
||||
return True
|
||||
|
||||
|
||||
class DictField(ComplexBaseField):
|
||||
"""A dictionary field that wraps a standard Python dictionary. This is
|
||||
similar to an embedded document, but the structure is not defined.
|
||||
@@ -1069,11 +1079,18 @@ class DictField(ComplexBaseField):
|
||||
if key_not_string(value):
|
||||
msg = "Invalid dictionary key - documents must have only string keys"
|
||||
self.error(msg)
|
||||
if key_has_dot_or_dollar(value):
|
||||
|
||||
curr_mongo_ver = get_mongodb_version()
|
||||
|
||||
if curr_mongo_ver < MONGODB_36 and key_has_dot_or_dollar(value):
|
||||
self.error(
|
||||
'Invalid dictionary key name - keys may not contain "."'
|
||||
' or startswith "$" characters'
|
||||
)
|
||||
elif curr_mongo_ver >= MONGODB_36 and key_starts_with_dollar(value):
|
||||
self.error(
|
||||
'Invalid dictionary key name - keys may not startswith "$" characters'
|
||||
)
|
||||
super(DictField, self).validate(value)
|
||||
|
||||
def lookup_member(self, member_name):
|
||||
@@ -2494,6 +2511,13 @@ class LazyReferenceField(BaseField):
|
||||
else:
|
||||
return pk
|
||||
|
||||
def to_python(self, value):
|
||||
"""Convert a MongoDB-compatible type to a Python type."""
|
||||
if not isinstance(value, (DBRef, Document, EmbeddedDocument)):
|
||||
collection = self.document_type._get_collection_name()
|
||||
value = DBRef(collection, self.document_type.id.to_python(value))
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
if isinstance(value, LazyReference):
|
||||
if value.collection != self.document_type._get_collection_name():
|
||||
|
||||
@@ -302,7 +302,7 @@ class BaseQuerySet(object):
|
||||
``insert(..., {w: 2, fsync: True})`` will wait until at least
|
||||
two servers have recorded the write and will force an fsync on
|
||||
each server being written to.
|
||||
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
|
||||
:param signal_kwargs: (optional) kwargs dictionary to be passed to
|
||||
the signal calls.
|
||||
|
||||
By default returns document instances, set ``load_bulk`` to False to
|
||||
@@ -1255,16 +1255,27 @@ class BaseQuerySet(object):
|
||||
for data in son_data
|
||||
]
|
||||
|
||||
def aggregate(self, *pipeline, **kwargs):
|
||||
"""
|
||||
Perform a aggregate function based in your queryset params
|
||||
def aggregate(self, pipeline, *suppl_pipeline, **kwargs):
|
||||
"""Perform a aggregate function based in your queryset params
|
||||
|
||||
:param pipeline: list of aggregation commands,\
|
||||
see: http://docs.mongodb.org/manual/core/aggregation-pipeline/
|
||||
|
||||
:param suppl_pipeline: unpacked list of pipeline (added to support deprecation of the old interface)
|
||||
parameter will be removed shortly
|
||||
:param kwargs: (optional) kwargs dictionary to be passed to pymongo's aggregate call
|
||||
See https://api.mongodb.com/python/current/api/pymongo/collection.html#pymongo.collection.Collection.aggregate
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
initial_pipeline = []
|
||||
using_deprecated_interface = isinstance(pipeline, dict) or bool(suppl_pipeline)
|
||||
user_pipeline = [pipeline] if isinstance(pipeline, dict) else list(pipeline)
|
||||
|
||||
if using_deprecated_interface:
|
||||
msg = "Calling .aggregate() with un unpacked list (*pipeline) is deprecated, it will soon change and will expect a list (similar to pymongo.Collection.aggregate interface), see documentation"
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
|
||||
user_pipeline += suppl_pipeline
|
||||
|
||||
initial_pipeline = []
|
||||
if self._query:
|
||||
initial_pipeline.append({"$match": self._query})
|
||||
|
||||
@@ -1281,14 +1292,14 @@ class BaseQuerySet(object):
|
||||
if self._skip is not None:
|
||||
initial_pipeline.append({"$skip": self._skip})
|
||||
|
||||
pipeline = initial_pipeline + list(pipeline)
|
||||
final_pipeline = initial_pipeline + user_pipeline
|
||||
|
||||
collection = self._collection
|
||||
if self._read_preference is not None:
|
||||
return self._collection.with_options(
|
||||
collection = self._collection.with_options(
|
||||
read_preference=self._read_preference
|
||||
).aggregate(pipeline, cursor={}, **kwargs)
|
||||
|
||||
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
|
||||
)
|
||||
return collection.aggregate(final_pipeline, cursor={}, **kwargs)
|
||||
|
||||
# JS functionality
|
||||
def map_reduce(
|
||||
|
||||
@@ -169,9 +169,9 @@ def query(_doc_cls=None, **kwargs):
|
||||
|
||||
key = ".".join(parts)
|
||||
|
||||
if op is None or key not in mongo_query:
|
||||
if key not in mongo_query:
|
||||
mongo_query[key] = value
|
||||
elif key in mongo_query:
|
||||
else:
|
||||
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
|
||||
mongo_query[key].update(value)
|
||||
# $max/minDistance needs to come last - convert to SON
|
||||
|
||||
Reference in New Issue
Block a user