Skip to content

Commit 72c4444

Browse files
author
erdenezul
authored
Merge branch 'master' into remove-pushall
2 parents 38fdf26 + 2d8d2e7 commit 72c4444

File tree

11 files changed

+103
-37
lines changed

11 files changed

+103
-37
lines changed

AUTHORS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,5 @@ that much better:
244244
* Stanislav Kaledin (https://github.com/sallyruthstruik)
245245
* Dmitry Yantsen (https://github.com/mrTable)
246246
* Renjianxin (https://github.com/Davidrjx)
247-
* Erdenezul Batmunkh (https://github.com/erdenezul)
247+
* Erdenezul Batmunkh (https://github.com/erdenezul)
248+
* Andy Yankovsky (https://github.com/werat)

docs/apireference.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ Fields
8787
.. autoclass:: mongoengine.fields.DictField
8888
.. autoclass:: mongoengine.fields.MapField
8989
.. autoclass:: mongoengine.fields.ReferenceField
90+
.. autoclass:: mongoengine.fields.LazyReferenceField
9091
.. autoclass:: mongoengine.fields.GenericReferenceField
92+
.. autoclass:: mongoengine.fields.GenericLazyReferenceField
9193
.. autoclass:: mongoengine.fields.CachedReferenceField
9294
.. autoclass:: mongoengine.fields.BinaryField
9395
.. autoclass:: mongoengine.fields.FileField

docs/guide/defining-documents.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ objects** as class attributes to the document class::
2222

2323
class Page(Document):
2424
title = StringField(max_length=200, required=True)
25-
date_modified = DateTimeField(default=datetime.datetime.now)
25+
date_modified = DateTimeField(default=datetime.datetime.utcnow)
2626

2727
As BSON (the binary format for storing data in mongodb) is order dependent,
2828
documents are serialized based on their field order.
@@ -80,13 +80,15 @@ are as follows:
8080
* :class:`~mongoengine.fields.FloatField`
8181
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
8282
* :class:`~mongoengine.fields.GenericReferenceField`
83+
* :class:`~mongoengine.fields.GenericLazyReferenceField`
8384
* :class:`~mongoengine.fields.GeoPointField`
8485
* :class:`~mongoengine.fields.ImageField`
8586
* :class:`~mongoengine.fields.IntField`
8687
* :class:`~mongoengine.fields.ListField`
8788
* :class:`~mongoengine.fields.MapField`
8889
* :class:`~mongoengine.fields.ObjectIdField`
8990
* :class:`~mongoengine.fields.ReferenceField`
91+
* :class:`~mongoengine.fields.LazyReferenceField`
9092
* :class:`~mongoengine.fields.SequenceField`
9193
* :class:`~mongoengine.fields.SortedListField`
9294
* :class:`~mongoengine.fields.StringField`
@@ -224,7 +226,7 @@ store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate
224226
user = ReferenceField(User)
225227
answers = DictField()
226228

227-
survey_response = SurveyResponse(date=datetime.now(), user=request.user)
229+
survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
228230
response_form = ResponseForm(request.POST)
229231
survey_response.answers = response_form.cleaned_data()
230232
survey_response.save()
@@ -618,7 +620,7 @@ collection after a given period. See the official
618620
documentation for more information. A common usecase might be session data::
619621

620622
class Session(Document):
621-
created = DateTimeField(default=datetime.now)
623+
created = DateTimeField(default=datetime.utcnow)
622624
meta = {
623625
'indexes': [
624626
{'fields': ['created'], 'expireAfterSeconds': 3600}

docs/tutorial.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ of them stand out as particularly intuitive solutions.
8686
Posts
8787
^^^^^
8888

89-
Happily mongoDB *isn't* a relational database, so we're not going to do it that
89+
Happily MongoDB *isn't* a relational database, so we're not going to do it that
9090
way. As it turns out, we can use MongoDB's schemaless nature to provide us with
9191
a much nicer solution. We will store all of the posts in *one collection* and
9292
each post type will only store the fields it needs. If we later want to add

mongoengine/base/datastructures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ def delete(self):
351351

352352
def update(self, **update):
353353
"""
354-
Updates the embedded documents with the given update values.
354+
Updates the embedded documents with the given replacement values. This
355+
function does not support mongoDB update operators such as ``inc__``.
355356
356357
.. note::
357358
The embedded document changes are not automatically saved

mongoengine/document.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ def modify(self, query=None, **update):
280280
elif query[id_field] != self.pk:
281281
raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
282282

283+
# Need to add shard key to query, or you get an error
284+
query.update(self._object_key)
285+
283286
updated = self._qs(**query).modify(new=True, **update)
284287
if updated is None:
285288
return False

mongoengine/fields.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ class EmbeddedDocumentField(BaseField):
614614
"""
615615

616616
def __init__(self, document_type, **kwargs):
617+
# XXX ValidationError raised outside of the "validate" method.
617618
if not (
618619
isinstance(document_type, six.string_types) or
619620
issubclass(document_type, EmbeddedDocument)
@@ -919,8 +920,11 @@ def __init__(self, basecls=None, field=None, *args, **kwargs):
919920
self.field = field
920921
self._auto_dereference = False
921922
self.basecls = basecls or BaseField
923+
924+
# XXX ValidationError raised outside of the "validate" method.
922925
if not issubclass(self.basecls, BaseField):
923926
self.error('DictField only accepts dict values')
927+
924928
kwargs.setdefault('default', lambda: {})
925929
super(DictField, self).__init__(*args, **kwargs)
926930

@@ -969,6 +973,7 @@ class MapField(DictField):
969973
"""
970974

971975
def __init__(self, field=None, *args, **kwargs):
976+
# XXX ValidationError raised outside of the "validate" method.
972977
if not isinstance(field, BaseField):
973978
self.error('Argument to MapField constructor must be a valid '
974979
'field')
@@ -1028,6 +1033,7 @@ def __init__(self, document_type, dbref=False,
10281033
A reference to an abstract document type is always stored as a
10291034
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
10301035
"""
1036+
# XXX ValidationError raised outside of the "validate" method.
10311037
if (
10321038
not isinstance(document_type, six.string_types) and
10331039
not issubclass(document_type, Document)
@@ -1082,6 +1088,8 @@ def to_mongo(self, document):
10821088
if isinstance(document, Document):
10831089
# We need the id from the saved object to create the DBRef
10841090
id_ = document.pk
1091+
1092+
# XXX ValidationError raised outside of the "validate" method.
10851093
if id_ is None:
10861094
self.error('You can only reference documents once they have'
10871095
' been saved to the database')
@@ -1121,19 +1129,21 @@ def prepare_query_value(self, op, value):
11211129
return self.to_mongo(value)
11221130

11231131
def validate(self, value):
1124-
11251132
if not isinstance(value, (self.document_type, LazyReference, DBRef, ObjectId)):
11261133
self.error('A ReferenceField only accepts DBRef, LazyReference, ObjectId or documents')
11271134

11281135
if isinstance(value, Document) and value.id is None:
11291136
self.error('You can only reference documents once they have been '
11301137
'saved to the database')
11311138

1132-
if self.document_type._meta.get('abstract') and \
1133-
not isinstance(value, self.document_type):
1139+
if (
1140+
self.document_type._meta.get('abstract') and
1141+
not isinstance(value, self.document_type)
1142+
):
11341143
self.error(
11351144
'%s is not an instance of abstract reference type %s' % (
1136-
self.document_type._class_name)
1145+
self.document_type._class_name
1146+
)
11371147
)
11381148

11391149
def lookup_member(self, member_name):
@@ -1156,6 +1166,7 @@ def __init__(self, document_type, fields=None, auto_sync=True, **kwargs):
11561166
if fields is None:
11571167
fields = []
11581168

1169+
# XXX ValidationError raised outside of the "validate" method.
11591170
if (
11601171
not isinstance(document_type, six.string_types) and
11611172
not issubclass(document_type, Document)
@@ -1230,6 +1241,7 @@ def to_mongo(self, document, use_db_field=True, fields=None):
12301241
id_field_name = self.document_type._meta['id_field']
12311242
id_field = self.document_type._fields[id_field_name]
12321243

1244+
# XXX ValidationError raised outside of the "validate" method.
12331245
if isinstance(document, Document):
12341246
# We need the id from the saved object to create the DBRef
12351247
id_ = document.pk
@@ -1238,7 +1250,6 @@ def to_mongo(self, document, use_db_field=True, fields=None):
12381250
' been saved to the database')
12391251
else:
12401252
self.error('Only accept a document object')
1241-
# TODO: should raise here or will fail next statement
12421253

12431254
value = SON((
12441255
('_id', id_field.to_mongo(id_)),
@@ -1256,6 +1267,7 @@ def prepare_query_value(self, op, value):
12561267
if value is None:
12571268
return None
12581269

1270+
# XXX ValidationError raised outside of the "validate" method.
12591271
if isinstance(value, Document):
12601272
if value.pk is None:
12611273
self.error('You can only reference documents once they have'
@@ -1269,7 +1281,6 @@ def prepare_query_value(self, op, value):
12691281
raise NotImplementedError
12701282

12711283
def validate(self, value):
1272-
12731284
if not isinstance(value, self.document_type):
12741285
self.error('A CachedReferenceField only accepts documents')
12751286

@@ -1330,6 +1341,8 @@ def __init__(self, *args, **kwargs):
13301341
elif isinstance(choice, type) and issubclass(choice, Document):
13311342
self.choices.append(choice._class_name)
13321343
else:
1344+
# XXX ValidationError raised outside of the "validate"
1345+
# method.
13331346
self.error('Invalid choices provided: must be a list of'
13341347
'Document subclasses and/or six.string_typess')
13351348

@@ -1393,6 +1406,7 @@ def to_mongo(self, document):
13931406
# We need the id from the saved object to create the DBRef
13941407
id_ = document.id
13951408
if id_ is None:
1409+
# XXX ValidationError raised outside of the "validate" method.
13961410
self.error('You can only reference documents once they have'
13971411
' been saved to the database')
13981412
else:
@@ -2190,8 +2204,11 @@ class MultiPolygonField(GeoJsonBaseField):
21902204

21912205
class LazyReferenceField(BaseField):
21922206
"""A really lazy reference to a document.
2193-
Unlike the :class:`~mongoengine.fields.ReferenceField` it must be manually
2194-
dereferenced using it ``fetch()`` method.
2207+
Unlike the :class:`~mongoengine.fields.ReferenceField` it will
2208+
**not** be automatically (lazily) dereferenced on access.
2209+
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
2210+
instance, allowing access to `pk` or manual dereference by using
2211+
``fetch()`` method.
21952212
21962213
.. versionadded:: 0.15
21972214
"""
@@ -2209,6 +2226,7 @@ def __init__(self, document_type, passthrough=False, dbref=False,
22092226
automatically call `fetch()` and try to retrive the field on the fetched
22102227
document. Note this only work getting field (not setting or deleting).
22112228
"""
2229+
# XXX ValidationError raised outside of the "validate" method.
22122230
if (
22132231
not isinstance(document_type, six.string_types) and
22142232
not issubclass(document_type, Document)
@@ -2316,10 +2334,12 @@ def lookup_member(self, member_name):
23162334

23172335

23182336
class GenericLazyReferenceField(GenericReferenceField):
2319-
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
2320-
that will be automatically dereferenced on access (lazily).
2321-
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it must be
2322-
manually dereferenced using it ``fetch()`` method.
2337+
"""A reference to *any* :class:`~mongoengine.document.Document` subclass.
2338+
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it will
2339+
**not** be automatically (lazily) dereferenced on access.
2340+
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
2341+
instance, allowing access to `pk` or manual dereference by using
2342+
``fetch()`` method.
23232343
23242344
.. note ::
23252345
* Any documents used as a generic reference must be registered in the

mongoengine/queryset/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,8 +486,9 @@ def update(self, upsert=False, multi=True, write_concern=None,
486486
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
487487
wait until at least two servers have recorded the write and
488488
will force an fsync on the primary server.
489-
:param full_result: Return the full result rather than just the number
490-
updated.
489+
:param full_result: Return the full result dictionary rather than just the number
490+
updated, e.g. return
491+
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
491492
:param update: Django-style update keyword arguments
492493
493494
.. versionadded:: 0.2

mongoengine/queryset/transform.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,8 @@ def query(_doc_cls=None, **kwargs):
101101
value = value['_id']
102102

103103
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
104-
# Raise an error if the in/nin/all/near param is not iterable. We need a
105-
# special check for BaseDocument, because - although it's iterable - using
106-
# it as such in the context of this method is most definitely a mistake.
107-
BaseDocument = _import_class('BaseDocument')
108-
if isinstance(value, BaseDocument):
109-
raise TypeError("When using the `in`, `nin`, `all`, or "
110-
"`near`-operators you can\'t use a "
111-
"`Document`, you must wrap your object "
112-
"in a list (object -> [object]).")
113-
elif not hasattr(value, '__iter__'):
114-
raise TypeError("The `in`, `nin`, `all`, or "
115-
"`near`-operators must be applied to an "
116-
"iterable (e.g. a list).")
117-
else:
118-
value = [field.prepare_query_value(op, v) for v in value]
104+
# Raise an error if the in/nin/all/near param is not iterable.
105+
value = _prepare_query_for_iterable(field, op, value)
119106

120107
# If we're querying a GenericReferenceField, we need to alter the
121108
# key depending on the value:
@@ -284,9 +271,15 @@ def update(_doc_cls=None, **update):
284271
if isinstance(field, GeoJsonBaseField):
285272
value = field.to_mongo(value)
286273

287-
if op == 'push' and isinstance(value, (list, tuple, set)):
274+
if op == 'pull':
275+
if field.required or value is not None:
276+
if match == 'in' and not isinstance(value, dict):
277+
value = _prepare_query_for_iterable(field, op, value)
278+
else:
279+
value = field.prepare_query_value(op, value)
280+
elif op == 'push' and isinstance(value, (list, tuple, set)):
288281
value = [field.prepare_query_value(op, v) for v in value]
289-
elif op in (None, 'set', 'push', 'pull'):
282+
elif op in (None, 'set', 'push'):
290283
if field.required or value is not None:
291284
value = field.prepare_query_value(op, value)
292285
elif op in ('pushAll', 'pullAll'):
@@ -443,3 +436,22 @@ def _infer_geometry(value):
443436

444437
raise InvalidQueryError('Invalid $geometry data. Can be either a '
445438
'dictionary or (nested) lists of coordinate(s)')
439+
440+
441+
def _prepare_query_for_iterable(field, op, value):
442+
# We need a special check for BaseDocument, because - although it's iterable - using
443+
# it as such in the context of this method is most definitely a mistake.
444+
BaseDocument = _import_class('BaseDocument')
445+
446+
if isinstance(value, BaseDocument):
447+
raise TypeError("When using the `in`, `nin`, `all`, or "
448+
"`near`-operators you can\'t use a "
449+
"`Document`, you must wrap your object "
450+
"in a list (object -> [object]).")
451+
452+
if not hasattr(value, '__iter__'):
453+
raise TypeError("The `in`, `nin`, `all`, or "
454+
"`near`-operators must be applied to an "
455+
"iterable (e.g. a list).")
456+
457+
return [field.prepare_query_value(op, v) for v in value]

tests/document/instance.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,23 @@ class Site(Document):
13411341
site = Site.objects.first()
13421342
self.assertEqual(site.page.log_message, "Error: Dummy message")
13431343

1344+
def test_update_list_field(self):
1345+
"""Test update on `ListField` with $pull + $in.
1346+
"""
1347+
class Doc(Document):
1348+
foo = ListField(StringField())
1349+
1350+
Doc.drop_collection()
1351+
doc = Doc(foo=['a', 'b', 'c'])
1352+
doc.save()
1353+
1354+
# Update
1355+
doc = Doc.objects.first()
1356+
doc.update(pull__foo__in=['a', 'c'])
1357+
1358+
doc = Doc.objects.first()
1359+
self.assertEqual(doc.foo, ['b'])
1360+
13441361
def test_embedded_update_db_field(self):
13451362
"""Test update on `EmbeddedDocumentField` fields when db_field
13461363
is other than default.

0 commit comments

Comments
 (0)