Merge branch 'master' into remove-pushall

This commit is contained in:
erdenezul 2018-04-17 16:38:08 +08:00 committed by GitHub
commit 72c4444a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 103 additions and 37 deletions

View File

@ -244,4 +244,5 @@ that much better:
* Stanislav Kaledin (https://github.com/sallyruthstruik) * Stanislav Kaledin (https://github.com/sallyruthstruik)
* Dmitry Yantsen (https://github.com/mrTable) * Dmitry Yantsen (https://github.com/mrTable)
* Renjianxin (https://github.com/Davidrjx) * Renjianxin (https://github.com/Davidrjx)
* Erdenezul Batmunkh (https://github.com/erdenezul) * Erdenezul Batmunkh (https://github.com/erdenezul)
* Andy Yankovsky (https://github.com/werat)

View File

@ -87,7 +87,9 @@ Fields
.. autoclass:: mongoengine.fields.DictField .. autoclass:: mongoengine.fields.DictField
.. autoclass:: mongoengine.fields.MapField .. autoclass:: mongoengine.fields.MapField
.. autoclass:: mongoengine.fields.ReferenceField .. autoclass:: mongoengine.fields.ReferenceField
.. autoclass:: mongoengine.fields.LazyReferenceField
.. autoclass:: mongoengine.fields.GenericReferenceField .. autoclass:: mongoengine.fields.GenericReferenceField
.. autoclass:: mongoengine.fields.GenericLazyReferenceField
.. autoclass:: mongoengine.fields.CachedReferenceField .. autoclass:: mongoengine.fields.CachedReferenceField
.. autoclass:: mongoengine.fields.BinaryField .. autoclass:: mongoengine.fields.BinaryField
.. autoclass:: mongoengine.fields.FileField .. autoclass:: mongoengine.fields.FileField

View File

@ -22,7 +22,7 @@ objects** as class attributes to the document class::
class Page(Document): class Page(Document):
title = StringField(max_length=200, required=True) title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.datetime.now) date_modified = DateTimeField(default=datetime.datetime.utcnow)
As BSON (the binary format for storing data in mongodb) is order dependent, As BSON (the binary format for storing data in mongodb) is order dependent,
documents are serialized based on their field order. documents are serialized based on their field order.
@ -80,6 +80,7 @@ are as follows:
* :class:`~mongoengine.fields.FloatField` * :class:`~mongoengine.fields.FloatField`
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField` * :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
* :class:`~mongoengine.fields.GenericReferenceField` * :class:`~mongoengine.fields.GenericReferenceField`
* :class:`~mongoengine.fields.GenericLazyReferenceField`
* :class:`~mongoengine.fields.GeoPointField` * :class:`~mongoengine.fields.GeoPointField`
* :class:`~mongoengine.fields.ImageField` * :class:`~mongoengine.fields.ImageField`
* :class:`~mongoengine.fields.IntField` * :class:`~mongoengine.fields.IntField`
@ -87,6 +88,7 @@ are as follows:
* :class:`~mongoengine.fields.MapField` * :class:`~mongoengine.fields.MapField`
* :class:`~mongoengine.fields.ObjectIdField` * :class:`~mongoengine.fields.ObjectIdField`
* :class:`~mongoengine.fields.ReferenceField` * :class:`~mongoengine.fields.ReferenceField`
* :class:`~mongoengine.fields.LazyReferenceField`
* :class:`~mongoengine.fields.SequenceField` * :class:`~mongoengine.fields.SequenceField`
* :class:`~mongoengine.fields.SortedListField` * :class:`~mongoengine.fields.SortedListField`
* :class:`~mongoengine.fields.StringField` * :class:`~mongoengine.fields.StringField`
@ -224,7 +226,7 @@ store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate
user = ReferenceField(User) user = ReferenceField(User)
answers = DictField() answers = DictField()
survey_response = SurveyResponse(date=datetime.now(), user=request.user) survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
response_form = ResponseForm(request.POST) response_form = ResponseForm(request.POST)
survey_response.answers = response_form.cleaned_data() survey_response.answers = response_form.cleaned_data()
survey_response.save() survey_response.save()
@ -618,7 +620,7 @@ collection after a given period. See the official
documentation for more information. A common usecase might be session data:: documentation for more information. A common usecase might be session data::
class Session(Document): class Session(Document):
created = DateTimeField(default=datetime.now) created = DateTimeField(default=datetime.utcnow)
meta = { meta = {
'indexes': [ 'indexes': [
{'fields': ['created'], 'expireAfterSeconds': 3600} {'fields': ['created'], 'expireAfterSeconds': 3600}

View File

@ -86,7 +86,7 @@ of them stand out as particularly intuitive solutions.
Posts Posts
^^^^^ ^^^^^
Happily mongoDB *isn't* a relational database, so we're not going to do it that Happily MongoDB *isn't* a relational database, so we're not going to do it that
way. As it turns out, we can use MongoDB's schemaless nature to provide us with way. As it turns out, we can use MongoDB's schemaless nature to provide us with
a much nicer solution. We will store all of the posts in *one collection* and a much nicer solution. We will store all of the posts in *one collection* and
each post type will only store the fields it needs. If we later want to add each post type will only store the fields it needs. If we later want to add

View File

@ -351,7 +351,8 @@ class EmbeddedDocumentList(BaseList):
def update(self, **update): def update(self, **update):
""" """
Updates the embedded documents with the given update values. Updates the embedded documents with the given replacement values. This
function does not support mongoDB update operators such as ``inc__``.
.. note:: .. note::
The embedded document changes are not automatically saved The embedded document changes are not automatically saved

View File

@ -280,6 +280,9 @@ class Document(BaseDocument):
elif query[id_field] != self.pk: elif query[id_field] != self.pk:
raise InvalidQueryError('Invalid document modify query: it must modify only this document.') raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
# Need to add shard key to query, or you get an error
query.update(self._object_key)
updated = self._qs(**query).modify(new=True, **update) updated = self._qs(**query).modify(new=True, **update)
if updated is None: if updated is None:
return False return False

View File

@ -614,6 +614,7 @@ class EmbeddedDocumentField(BaseField):
""" """
def __init__(self, document_type, **kwargs): def __init__(self, document_type, **kwargs):
# XXX ValidationError raised outside of the "validate" method.
if not ( if not (
isinstance(document_type, six.string_types) or isinstance(document_type, six.string_types) or
issubclass(document_type, EmbeddedDocument) issubclass(document_type, EmbeddedDocument)
@ -919,8 +920,11 @@ class DictField(ComplexBaseField):
self.field = field self.field = field
self._auto_dereference = False self._auto_dereference = False
self.basecls = basecls or BaseField self.basecls = basecls or BaseField
# XXX ValidationError raised outside of the "validate" method.
if not issubclass(self.basecls, BaseField): if not issubclass(self.basecls, BaseField):
self.error('DictField only accepts dict values') self.error('DictField only accepts dict values')
kwargs.setdefault('default', lambda: {}) kwargs.setdefault('default', lambda: {})
super(DictField, self).__init__(*args, **kwargs) super(DictField, self).__init__(*args, **kwargs)
@ -969,6 +973,7 @@ class MapField(DictField):
""" """
def __init__(self, field=None, *args, **kwargs): def __init__(self, field=None, *args, **kwargs):
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(field, BaseField): if not isinstance(field, BaseField):
self.error('Argument to MapField constructor must be a valid ' self.error('Argument to MapField constructor must be a valid '
'field') 'field')
@ -1028,6 +1033,7 @@ class ReferenceField(BaseField):
A reference to an abstract document type is always stored as a A reference to an abstract document type is always stored as a
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`. :class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
""" """
# XXX ValidationError raised outside of the "validate" method.
if ( if (
not isinstance(document_type, six.string_types) and not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document) not issubclass(document_type, Document)
@ -1082,6 +1088,8 @@ class ReferenceField(BaseField):
if isinstance(document, Document): if isinstance(document, Document):
# We need the id from the saved object to create the DBRef # We need the id from the saved object to create the DBRef
id_ = document.pk id_ = document.pk
# XXX ValidationError raised outside of the "validate" method.
if id_ is None: if id_ is None:
self.error('You can only reference documents once they have' self.error('You can only reference documents once they have'
' been saved to the database') ' been saved to the database')
@ -1121,7 +1129,6 @@ class ReferenceField(BaseField):
return self.to_mongo(value) return self.to_mongo(value)
def validate(self, value): def validate(self, value):
if not isinstance(value, (self.document_type, LazyReference, DBRef, ObjectId)): if not isinstance(value, (self.document_type, LazyReference, DBRef, ObjectId)):
self.error('A ReferenceField only accepts DBRef, LazyReference, ObjectId or documents') self.error('A ReferenceField only accepts DBRef, LazyReference, ObjectId or documents')
@ -1129,11 +1136,14 @@ class ReferenceField(BaseField):
self.error('You can only reference documents once they have been ' self.error('You can only reference documents once they have been '
'saved to the database') 'saved to the database')
if self.document_type._meta.get('abstract') and \ if (
not isinstance(value, self.document_type): self.document_type._meta.get('abstract') and
not isinstance(value, self.document_type)
):
self.error( self.error(
'%s is not an instance of abstract reference type %s' % ( '%s is not an instance of abstract reference type %s' % (
self.document_type._class_name) self.document_type._class_name
)
) )
def lookup_member(self, member_name): def lookup_member(self, member_name):
@ -1156,6 +1166,7 @@ class CachedReferenceField(BaseField):
if fields is None: if fields is None:
fields = [] fields = []
# XXX ValidationError raised outside of the "validate" method.
if ( if (
not isinstance(document_type, six.string_types) and not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document) not issubclass(document_type, Document)
@ -1230,6 +1241,7 @@ class CachedReferenceField(BaseField):
id_field_name = self.document_type._meta['id_field'] id_field_name = self.document_type._meta['id_field']
id_field = self.document_type._fields[id_field_name] id_field = self.document_type._fields[id_field_name]
# XXX ValidationError raised outside of the "validate" method.
if isinstance(document, Document): if isinstance(document, Document):
# We need the id from the saved object to create the DBRef # We need the id from the saved object to create the DBRef
id_ = document.pk id_ = document.pk
@ -1238,7 +1250,6 @@ class CachedReferenceField(BaseField):
' been saved to the database') ' been saved to the database')
else: else:
self.error('Only accept a document object') self.error('Only accept a document object')
# TODO: should raise here or will fail next statement
value = SON(( value = SON((
('_id', id_field.to_mongo(id_)), ('_id', id_field.to_mongo(id_)),
@ -1256,6 +1267,7 @@ class CachedReferenceField(BaseField):
if value is None: if value is None:
return None return None
# XXX ValidationError raised outside of the "validate" method.
if isinstance(value, Document): if isinstance(value, Document):
if value.pk is None: if value.pk is None:
self.error('You can only reference documents once they have' self.error('You can only reference documents once they have'
@ -1269,7 +1281,6 @@ class CachedReferenceField(BaseField):
raise NotImplementedError raise NotImplementedError
def validate(self, value): def validate(self, value):
if not isinstance(value, self.document_type): if not isinstance(value, self.document_type):
self.error('A CachedReferenceField only accepts documents') self.error('A CachedReferenceField only accepts documents')
@ -1330,6 +1341,8 @@ class GenericReferenceField(BaseField):
elif isinstance(choice, type) and issubclass(choice, Document): elif isinstance(choice, type) and issubclass(choice, Document):
self.choices.append(choice._class_name) self.choices.append(choice._class_name)
else: else:
# XXX ValidationError raised outside of the "validate"
# method.
self.error('Invalid choices provided: must be a list of' self.error('Invalid choices provided: must be a list of'
'Document subclasses and/or six.string_typess') 'Document subclasses and/or six.string_typess')
@ -1393,6 +1406,7 @@ class GenericReferenceField(BaseField):
# We need the id from the saved object to create the DBRef # We need the id from the saved object to create the DBRef
id_ = document.id id_ = document.id
if id_ is None: if id_ is None:
# XXX ValidationError raised outside of the "validate" method.
self.error('You can only reference documents once they have' self.error('You can only reference documents once they have'
' been saved to the database') ' been saved to the database')
else: else:
@ -2190,8 +2204,11 @@ class MultiPolygonField(GeoJsonBaseField):
class LazyReferenceField(BaseField): class LazyReferenceField(BaseField):
"""A really lazy reference to a document. """A really lazy reference to a document.
Unlike the :class:`~mongoengine.fields.ReferenceField` it must be manually Unlike the :class:`~mongoengine.fields.ReferenceField` it will
dereferenced using it ``fetch()`` method. **not** be automatically (lazily) dereferenced on access.
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
instance, allowing access to `pk` or manual dereference by using
``fetch()`` method.
.. versionadded:: 0.15 .. versionadded:: 0.15
""" """
@ -2209,6 +2226,7 @@ class LazyReferenceField(BaseField):
automatically call `fetch()` and try to retrive the field on the fetched automatically call `fetch()` and try to retrive the field on the fetched
document. Note this only work getting field (not setting or deleting). document. Note this only work getting field (not setting or deleting).
""" """
# XXX ValidationError raised outside of the "validate" method.
if ( if (
not isinstance(document_type, six.string_types) and not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document) not issubclass(document_type, Document)
@ -2316,10 +2334,12 @@ class LazyReferenceField(BaseField):
class GenericLazyReferenceField(GenericReferenceField): class GenericLazyReferenceField(GenericReferenceField):
"""A reference to *any* :class:`~mongoengine.document.Document` subclass """A reference to *any* :class:`~mongoengine.document.Document` subclass.
that will be automatically dereferenced on access (lazily). Unlike the :class:`~mongoengine.fields.GenericReferenceField` it will
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it must be **not** be automatically (lazily) dereferenced on access.
manually dereferenced using it ``fetch()`` method. Instead, access will return a :class:`~mongoengine.base.LazyReference` class
instance, allowing access to `pk` or manual dereference by using
``fetch()`` method.
.. note :: .. note ::
* Any documents used as a generic reference must be registered in the * Any documents used as a generic reference must be registered in the

View File

@ -486,8 +486,9 @@ class BaseQuerySet(object):
``save(..., write_concern={w: 2, fsync: True}, ...)`` will ``save(..., write_concern={w: 2, fsync: True}, ...)`` will
wait until at least two servers have recorded the write and wait until at least two servers have recorded the write and
will force an fsync on the primary server. will force an fsync on the primary server.
:param full_result: Return the full result rather than just the number :param full_result: Return the full result dictionary rather than just the number
updated. updated, e.g. return
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
:param update: Django-style update keyword arguments :param update: Django-style update keyword arguments
.. versionadded:: 0.2 .. versionadded:: 0.2

View File

@ -101,21 +101,8 @@ def query(_doc_cls=None, **kwargs):
value = value['_id'] value = value['_id']
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict): 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. We need a # Raise an error if the in/nin/all/near param is not iterable.
# special check for BaseDocument, because - although it's iterable - using value = _prepare_query_for_iterable(field, op, value)
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
elif not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
else:
value = [field.prepare_query_value(op, v) for v in value]
# If we're querying a GenericReferenceField, we need to alter the # If we're querying a GenericReferenceField, we need to alter the
# key depending on the value: # key depending on the value:
@ -284,9 +271,15 @@ def update(_doc_cls=None, **update):
if isinstance(field, GeoJsonBaseField): if isinstance(field, GeoJsonBaseField):
value = field.to_mongo(value) value = field.to_mongo(value)
if op == 'push' and isinstance(value, (list, tuple, set)): if op == 'pull':
if field.required or value is not None:
if match == 'in' and not isinstance(value, dict):
value = _prepare_query_for_iterable(field, op, value)
else:
value = field.prepare_query_value(op, value)
elif op == 'push' and isinstance(value, (list, tuple, set)):
value = [field.prepare_query_value(op, v) for v in value] value = [field.prepare_query_value(op, v) for v in value]
elif op in (None, 'set', 'push', 'pull'): elif op in (None, 'set', 'push'):
if field.required or value is not None: if field.required or value is not None:
value = field.prepare_query_value(op, value) value = field.prepare_query_value(op, value)
elif op in ('pushAll', 'pullAll'): elif op in ('pushAll', 'pullAll'):
@ -443,3 +436,22 @@ def _infer_geometry(value):
raise InvalidQueryError('Invalid $geometry data. Can be either a ' raise InvalidQueryError('Invalid $geometry data. Can be either a '
'dictionary or (nested) lists of coordinate(s)') 'dictionary or (nested) lists of coordinate(s)')
def _prepare_query_for_iterable(field, op, value):
# We need a special check for BaseDocument, because - although it's iterable - using
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
if not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
return [field.prepare_query_value(op, v) for v in value]

View File

@ -1341,6 +1341,23 @@ class InstanceTest(unittest.TestCase):
site = Site.objects.first() site = Site.objects.first()
self.assertEqual(site.page.log_message, "Error: Dummy message") self.assertEqual(site.page.log_message, "Error: Dummy message")
def test_update_list_field(self):
"""Test update on `ListField` with $pull + $in.
"""
class Doc(Document):
foo = ListField(StringField())
Doc.drop_collection()
doc = Doc(foo=['a', 'b', 'c'])
doc.save()
# Update
doc = Doc.objects.first()
doc.update(pull__foo__in=['a', 'c'])
doc = Doc.objects.first()
self.assertEqual(doc.foo, ['b'])
def test_embedded_update_db_field(self): def test_embedded_update_db_field(self):
"""Test update on `EmbeddedDocumentField` fields when db_field """Test update on `EmbeddedDocumentField` fields when db_field
is other than default. is other than default.

View File

@ -28,12 +28,16 @@ class TransformTest(unittest.TestCase):
{'name': {'$exists': True}}) {'name': {'$exists': True}})
def test_transform_update(self): def test_transform_update(self):
class LisDoc(Document):
foo = ListField(StringField())
class DicDoc(Document): class DicDoc(Document):
dictField = DictField() dictField = DictField()
class Doc(Document): class Doc(Document):
pass pass
LisDoc.drop_collection()
DicDoc.drop_collection() DicDoc.drop_collection()
Doc.drop_collection() Doc.drop_collection()
@ -50,6 +54,9 @@ class TransformTest(unittest.TestCase):
update = transform.update(DicDoc, pull__dictField__test=doc) update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict)) self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict))
update = transform.update(LisDoc, pull__foo__in=['a'])
self.assertEqual(update, {'$pull': {'foo': {'$in': ['a']}}})
def test_transform_update_push(self): def test_transform_update_push(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'""" """Ensure the differences in behvaior between 'push' and 'push_all'"""