From 36034ee15fad413392c009e8f0d367669d355cb5 Mon Sep 17 00:00:00 2001 From: Alistair Roche Date: Mon, 23 May 2011 18:27:01 +0100 Subject: [PATCH 1/3] 'set__comments__0__body="asdf"' syntax works --- mongoengine/base.py | 21 ++++++++++----------- mongoengine/fields.py | 4 ++-- mongoengine/queryset.py | 15 +++++++++++---- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 9d0b8231..495e2418 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -22,7 +22,7 @@ class BaseField(object): may be added to subclasses of `Document` to define a document's schema. """ - # Fields may have _types inserted into indexes by default + # Fields may have _types inserted into indexes by default _index_with_types = True _geo_index = False @@ -32,7 +32,7 @@ class BaseField(object): creation_counter = 0 auto_creation_counter = -1 - def __init__(self, db_field=None, name=None, required=False, default=None, + def __init__(self, db_field=None, name=None, required=False, default=None, unique=False, unique_with=None, primary_key=False, validation=None, choices=None): self.db_field = (db_field or name) if not primary_key else '_id' @@ -57,7 +57,7 @@ class BaseField(object): BaseField.creation_counter += 1 def __get__(self, instance, owner): - """Descriptor for retrieving a value from a field in a document. Do + """Descriptor for retrieving a value from a field in a document. Do any necessary conversion between Python and MongoDB types. """ if instance is None: @@ -167,8 +167,8 @@ class DocumentMetaclass(type): superclasses.update(base._superclasses) if hasattr(base, '_meta'): - # Ensure that the Document class may be subclassed - - # inheritance may be disabled to remove dependency on + # Ensure that the Document class may be subclassed - + # inheritance may be disabled to remove dependency on # additional fields _cls and _types if base._meta.get('allow_inheritance', True) == False: raise ValueError('Document %s may not be subclassed' % @@ -190,7 +190,6 @@ class DocumentMetaclass(type): attrs['_class_name'] = '.'.join(reversed(class_name)) attrs['_superclasses'] = superclasses - # Add the document's fields to the _fields attribute for attr_name, attr_value in attrs.items(): if hasattr(attr_value, "__class__") and \ @@ -211,12 +210,12 @@ class DocumentMetaclass(type): module = attrs.get('__module__') - base_excs = tuple(base.DoesNotExist for base in bases + base_excs = tuple(base.DoesNotExist for base in bases if hasattr(base, 'DoesNotExist')) or (DoesNotExist,) exc = subclass_exception('DoesNotExist', base_excs, module) new_class.add_to_class('DoesNotExist', exc) - base_excs = tuple(base.MultipleObjectsReturned for base in bases + base_excs = tuple(base.MultipleObjectsReturned for base in bases if hasattr(base, 'MultipleObjectsReturned')) base_excs = base_excs or (MultipleObjectsReturned,) exc = subclass_exception('MultipleObjectsReturned', base_excs, module) @@ -238,9 +237,9 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): def __new__(cls, name, bases, attrs): super_new = super(TopLevelDocumentMetaclass, cls).__new__ - # Classes defined in this package are abstract and should not have + # Classes defined in this package are abstract and should not have # their own metadata with DB collection, etc. - # __metaclass__ is only set on the class with the __metaclass__ + # __metaclass__ is only set on the class with the __metaclass__ # attribute (i.e. it is not set on subclasses). This differentiates # 'real' documents from the 'Document' class if attrs.get('__metaclass__') == TopLevelDocumentMetaclass: @@ -366,7 +365,7 @@ class BaseDocument(object): are present. """ # Get a list of tuples of field names and their current values - fields = [(field, getattr(self, name)) + fields = [(field, getattr(self, name)) for name, field in self._fields.items()] # Ensure that each field is matched to a valid value diff --git a/mongoengine/fields.py b/mongoengine/fields.py index c06fdd4d..9fcfcc2b 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -339,7 +339,7 @@ class ListField(BaseField): if isinstance(self.field, ReferenceField): referenced_type = self.field.document_type - # Get value from document instance if available + # Get value from document instance if available value_list = instance._data.get(self.name) if value_list: deref_list = [] @@ -643,7 +643,7 @@ class GridFSProxy(object): if not self.newfile: self.new_file() self.grid_id = self.newfile._id - self.newfile.writelines(lines) + self.newfile.writelines(lines) def read(self): try: diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 6da11fa7..f58328ac 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -523,6 +523,10 @@ class QuerySet(object): fields = [] field = None for field_name in parts: + if field_name.isdigit(): + fields.append(field_name) + field = field.field + continue if field is None: # Look up first field from the document if field_name == 'pk': @@ -620,7 +624,6 @@ class QuerySet(object): mongo_query[key] = value elif key in mongo_query and isinstance(mongo_query[key], dict): mongo_query[key].update(value) - return mongo_query def get(self, *q_objs, **query): @@ -1010,7 +1013,6 @@ class QuerySet(object): """ operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all', 'pull', 'pull_all', 'add_to_set'] - mongo_update = {} for key, value in update.items(): parts = key.split('__') @@ -1033,10 +1035,15 @@ class QuerySet(object): if _doc_cls: # Switch field names to proper names [set in Field(name='foo')] fields = QuerySet._lookup_field(_doc_cls, parts) - parts = [field.db_field for field in fields] - + parts = [] + for field in fields: + if isinstance(field, str): + parts.append(field) + else: + parts.append(field.db_field) # Convert value to proper value field = fields[-1] + if op in (None, 'set', 'push', 'pull', 'addToSet'): value = field.prepare_query_value(op, value) elif op in ('pushAll', 'pullAll'): From 118c0deb7a7bd48170eb49624f79f737419c5342 Mon Sep 17 00:00:00 2001 From: Alistair Roche Date: Tue, 24 May 2011 11:31:44 +0100 Subject: [PATCH 2/3] Fixed list-indexing syntax; created tests. --- mongoengine/queryset.py | 16 +++++++++++++- tests/queryset.py | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 239e146b..e6c93353 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -524,6 +524,15 @@ class QuerySet(object): fields = [] field = None for field_name in parts: + # Handle ListField indexing: + if field_name.isdigit(): + try: + field = field.field + except AttributeError, err: + raise InvalidQueryError( + "Can't use index on unsubscriptable field (%s)" % err) + fields.append(field_name) + continue if field is None: # Look up first field from the document if field_name == 'pk': @@ -1072,7 +1081,12 @@ class QuerySet(object): if _doc_cls: # Switch field names to proper names [set in Field(name='foo')] fields = QuerySet._lookup_field(_doc_cls, parts) - parts = [field.db_field for field in fields] + parts = [] + for field in fields: + if isinstance(field, str): + parts.append(field) + else: + parts.append(field.db_field) # Convert value to proper value field = fields[-1] diff --git a/tests/queryset.py b/tests/queryset.py index 5a2c46cb..b0693f66 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -211,6 +211,55 @@ class QuerySetTest(unittest.TestCase): Blog.drop_collection() + def test_update_array_position(self): + """Ensure that updating by array position works. + + Check update() and update_one() can take syntax like: + set__posts__1__comments__1__name="testc" + Check that it only works for ListFields. + """ + class Comment(EmbeddedDocument): + name = StringField() + + class Post(EmbeddedDocument): + comments = ListField(EmbeddedDocumentField(Comment)) + + class Blog(Document): + tags = ListField(StringField()) + posts = ListField(EmbeddedDocumentField(Post)) + + Blog.drop_collection() + + comment1 = Comment(name='testa') + comment2 = Comment(name='testb') + post1 = Post(comments=[comment1, comment2]) + post2 = Post(comments=[comment2, comment2]) + blog1 = Blog.objects.create(posts=[post1, post2]) + blog2 = Blog.objects.create(posts=[post2, post1]) + + # Update all of the first comments of second posts of all blogs + blog = Blog.objects().update(set__posts__1__comments__0__name="testc") + testc_blogs = Blog.objects(posts__1__comments__0__name="testc") + self.assertEqual(len(testc_blogs), 2) + + Blog.drop_collection() + + blog1 = Blog.objects.create(posts=[post1, post2]) + blog2 = Blog.objects.create(posts=[post2, post1]) + + # Update only the first blog returned by the query + blog = Blog.objects().update_one( + set__posts__1__comments__1__name="testc") + testc_blogs = Blog.objects(posts__1__comments__1__name="testc") + self.assertEqual(len(testc_blogs), 1) + + # Check that using this indexing syntax on a non-list fails + def non_list_indexing(): + Blog.objects().update(set__posts__1__comments__0__name__1="asdf") + self.assertRaises(InvalidQueryError, non_list_indexing) + + Blog.drop_collection() + def test_get_or_create(self): """Ensure that ``get_or_create`` returns one result or creates a new document. From 305fd4b232e480f4aaa85999d87e6a645e4e5c43 Mon Sep 17 00:00:00 2001 From: Alistair Roche Date: Tue, 24 May 2011 11:44:43 +0100 Subject: [PATCH 3/3] Fixed whitespace --- mongoengine/base.py | 1 + mongoengine/queryset.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/mongoengine/base.py b/mongoengine/base.py index 5188c310..77c2d7d1 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -200,6 +200,7 @@ class DocumentMetaclass(type): attrs['_class_name'] = '.'.join(reversed(class_name)) attrs['_superclasses'] = superclasses + # Add the document's fields to the _fields attribute for attr_name, attr_value in attrs.items(): if hasattr(attr_value, "__class__") and \ diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index d7d349ad..087ee487 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -628,6 +628,7 @@ class QuerySet(object): mongo_query[key] = value elif key in mongo_query and isinstance(mongo_query[key], dict): mongo_query[key].update(value) + return mongo_query def get(self, *q_objs, **query): @@ -1055,6 +1056,7 @@ class QuerySet(object): """ operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all', 'pull', 'pull_all', 'add_to_set'] + mongo_update = {} for key, value in update.items(): parts = key.split('__')