Added support for the $ positional operator

closes #205
This commit is contained in:
Ross Lawley 2011-06-21 14:50:11 +01:00
parent 09c32a63ce
commit 14be7ba2e2
4 changed files with 102 additions and 11 deletions

View File

@ -5,6 +5,7 @@ Changelog
Changes in dev
==============
- Added support for the positional operator
- Updated geo index checking to be recursive and check in embedded documents
- Updated default collection naming convention
- Added Document Mixin support

View File

@ -467,3 +467,19 @@ modifier comes before the field, not after it::
>>> post.reload()
>>> post.tags
['database', 'nosql']
The positional operator allows you to update list items without knowing the
index position, therefore making the update a single atomic operation. As we
cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
>>> post = BlogPost(title='Test', page_views=0, tags=['database', 'mongo'])
>>> post.save()
>>> BlogPost.objects(id=post.id, tags='mongo').update(set__tags__S='mongodb')
>>> post.reload()
>>> post.tags
['database', 'mongodb']
.. note ::
Currently only top level lists are handled, future versions of mongodb /
pymongo plan to support nested positional operators. See `The $ positional
operator <http://www.mongodb.org/display/DOCS/Updating#Updating-The%24positionaloperator>`_.

View File

@ -1215,6 +1215,9 @@ class QuerySet(object):
append_field = True
for field in fields:
if isinstance(field, str):
# Convert the S operator to $
if field == 'S':
field = '$'
parts.append(field)
append_field = False
else:
@ -1243,7 +1246,7 @@ class QuerySet(object):
return mongo_update
def update(self, safe_update=True, upsert=False, write_options=None, **update):
def update(self, safe_update=True, upsert=False, multi=True, write_options=None, **update):
"""Perform an atomic update on the fields matched by the query. When
``safe_update`` is used, the number of affected documents is returned.
@ -1261,7 +1264,7 @@ class QuerySet(object):
update = QuerySet._transform_update(self._document, **update)
try:
ret = self._collection.update(self._query, update, multi=True,
ret = self._collection.update(self._query, update, multi=multi,
upsert=upsert, safe=safe_update,
**write_options)
if ret is not None and 'n' in ret:

View File

@ -260,6 +260,77 @@ class QuerySetTest(unittest.TestCase):
Blog.drop_collection()
def test_update_using_positional_operator(self):
"""Ensure that the list fields can be updated using the positional
operator."""
class Comment(EmbeddedDocument):
by = StringField()
votes = IntField()
class BlogPost(Document):
title = StringField()
comments = ListField(EmbeddedDocumentField(Comment))
BlogPost.drop_collection()
c1 = Comment(by="joe", votes=3)
c2 = Comment(by="jane", votes=7)
BlogPost(title="ABC", comments=[c1, c2]).save()
BlogPost.objects(comments__by="joe").update(inc__comments__S__votes=1)
post = BlogPost.objects.first()
self.assertEquals(post.comments[0].by, 'joe')
self.assertEquals(post.comments[0].votes, 4)
# Currently the $ operator only applies to the first matched item in
# the query
class Simple(Document):
x = ListField()
Simple.drop_collection()
Simple(x=[1, 2, 3, 2]).save()
Simple.objects(x=2).update(inc__x__S=1)
simple = Simple.objects.first()
self.assertEquals(simple.x, [1, 3, 3, 2])
Simple.drop_collection()
# You can set multiples
Simple.drop_collection()
Simple(x=[1, 2, 3, 4]).save()
Simple(x=[2, 3, 4, 5]).save()
Simple(x=[3, 4, 5, 6]).save()
Simple(x=[4, 5, 6, 7]).save()
Simple.objects(x=3).update(set__x__S=0)
s = Simple.objects()
self.assertEquals(s[0].x, [1, 2, 0, 4])
self.assertEquals(s[1].x, [2, 0, 4, 5])
self.assertEquals(s[2].x, [0, 4, 5, 6])
self.assertEquals(s[3].x, [4, 5, 6, 7])
# Using "$unset" with an expression like this "array.$" will result in
# the array item becoming None, not being removed.
Simple.drop_collection()
Simple(x=[1, 2, 3, 4, 3, 2, 3, 4]).save()
Simple.objects(x=3).update(unset__x__S=1)
simple = Simple.objects.first()
self.assertEquals(simple.x, [1, 2, None, 4, 3, 2, 3, 4])
# Nested updates arent supported yet..
def update_nested():
Simple.drop_collection()
Simple(x=[{'test': [1, 2, 3, 4]}]).save()
Simple.objects(x__test=2).update(set__x__S__test__S=3)
self.assertEquals(simple.x, [1, 2, 3, 4])
self.assertRaises(OperationError, update_nested)
Simple.drop_collection()
def test_mapfield_update(self):
"""Ensure that the MapField can be updated."""
class Member(EmbeddedDocument):