From 1a4533a9cf9f3a706d79a250db3db6522678c46a Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 23 Jul 2012 14:46:48 +0100 Subject: [PATCH 01/14] Minor perf update --- mongoengine/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 09768c4a..7b1f3206 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -198,7 +198,8 @@ class BaseField(object): """Descriptor for assigning a value to a field in a document. """ instance._data[self.name] = value - instance._mark_as_changed(self.name) + if instance._initialised: + instance._mark_as_changed(self.name) def error(self, message="", errors=None, field_name=None): """Raises a ValidationError. @@ -791,6 +792,8 @@ class BaseDocument(object): # Assign default values to instance for attr_name, field in self._fields.items(): + if self._db_field_map.get(attr_name, attr_name) in values: + continue value = getattr(self, attr_name, None) setattr(self, attr_name, value) @@ -821,6 +824,8 @@ class BaseDocument(object): signals.post_init.send(self.__class__, document=self) def __setattr__(self, name, value): + if not self._initialised: + return super(BaseDocument, self).__setattr__(name, value) # Handle dynamic data only if an initialised dynamic document if self._dynamic and not self._dynamic_lock: From 3628a7653ca6e1e51a712b751db9dcc5e6621b51 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 26 Jul 2012 22:50:39 +0100 Subject: [PATCH 02/14] Squashed commit of the following: commit 48f988acd728f1193b57df8cf6b0154d69c15099 Merge: 6526923 1304f27 Author: Ross Lawley Date: Thu Jul 26 08:17:45 2012 -0700 Merge pull request #44 from faulkner/fix-notes Proper syntax for RST notes (so they actually render). commit 6526923345efd768044c4fba770a8a3ada161a40 Author: Ross Lawley Date: Thu Jul 26 16:00:32 2012 +0100 Fixed recursion loading bug in _get_changed_fields fixes hmarr/mongoengine#548 commit 24fd1acce68341361331e033d1692d61a936f871 Author: Ross Lawley Date: Thu Jul 26 14:14:10 2012 +0100 Version bump commit cbb9235dc5d863ee0bb8a315c976581e71b6a641 Merge: 6459d4c 19ec2c9 Author: Ross Lawley Date: Wed Jul 25 15:12:34 2012 +0100 Merge branch 'master' of github.com:hmarr/mongoengine commit 19ec2c9bc98db454680e681373f3fcd3b0f79a6c Merge: 3070e0b 601f0eb Author: Ross Lawley Date: Wed Jul 25 07:12:07 2012 -0700 Merge pull request #545 from maxcountryman/patch-1 Correcting typo in DynamicField docstring commit 6459d4c0b60229edcd4d562898833f221d00ebf4 Author: Ross Lawley Date: Wed Jul 25 14:55:10 2012 +0100 Fixed issue with custom queryset manager expecting explict variable names If using / expecting kwargs you have to call the queryset manager explicitly. commit 1304f2721f7850b223950edb85ec7c141255176c Author: Chris Faulkner Date: Tue Jul 24 14:06:43 2012 -0700 Proper syntax for RST notes (so they actually render). commit 598ffd3e5c107990dd41995d0bbb21bf13f67fb5 Author: Ross Lawley Date: Mon Jul 23 15:32:02 2012 +0100 Fixed documentation commit 601f0eb1682ebd9c9eb67f1b68b76bdd2cf3d8c7 Author: Max Countryman Date: Fri Jul 20 19:12:43 2012 -0700 Correcting typo in DynamicField docstring --- docs/changelog.rst | 10 +++- docs/guide/defining-documents.rst | 29 ++++++++++ mongoengine/__init__.py | 2 +- mongoengine/base.py | 3 +- mongoengine/document.py | 2 +- mongoengine/fields.py | 14 ++--- mongoengine/queryset.py | 12 ++--- python-mongoengine.spec | 2 +- tests/test_queryset.py | 88 +++++++++++++++++++++++++------ 9 files changed, 130 insertions(+), 32 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 19d21b87..97926f8d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,14 @@ Changelog ========= +Changes in 0.6.18 +================= +- Fixed recursion loading bug in _get_changed_fields + +Changes in 0.6.17 +================= +- Fixed issue with custom queryset manager expecting explict variable names + Changes in 0.6.16 ================= - Fixed issue where db_alias wasn't inherited @@ -27,7 +35,7 @@ Changes in 0.6.14 - Added support for add_to_set and each Changes in 0.6.13 -================ +================= - Fixed EmbeddedDocument db_field validation issue - Fixed StringField unicode issue - Fixes __repr__ modifying the cursor diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 726ce3b7..b4facbda 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -259,6 +259,35 @@ as the constructor's argument:: content = StringField() +.. _one-to-many-with-listfields: + +One to Many with ListFields +''''''''''''''''''''''''''' + +If you are implementing a one to many relationship via a list of references, +then the references are stored as DBRefs and to query you need to pass an +instance of the object to the query:: + + class User(Document): + name = StringField() + + class Page(Document): + content = StringField() + authors = ListField(ReferenceField(User)) + + bob = User(name="Bob Jones").save() + john = User(name="John Smith").save() + + Page(content="Test Page", authors=[bob, john]).save() + Page(content="Another Page", authors=[john]).save() + + # Find all pages Bob authored + Page.objects(authors__in=[bob]) + + # Find all pages that both Bob and John have authored + Page.objects(authors__all=[bob, john]) + + Dealing with deletion of referred documents ''''''''''''''''''''''''''''''''''''''''''' By default, MongoDB doesn't check the integrity of your data, so deleting diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index ad12479d..5f4f7f1d 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -12,7 +12,7 @@ from signals import * __all__ = (document.__all__ + fields.__all__ + connection.__all__ + queryset.__all__ + signals.__all__) -VERSION = (0, 6, 16) +VERSION = (0, 6, 18) def get_version(): diff --git a/mongoengine/base.py b/mongoengine/base.py index 7b1f3206..43243b88 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -1017,9 +1017,10 @@ Invalid data to create a `%s` instance.\n%s""".strip() % (cls._class_name, error field_list.update(self._dynamic_fields) for field_name in field_list: + db_field_name = self._db_field_map.get(field_name, field_name) key = '%s.' % db_field_name - field = getattr(self, field_name, None) + field = self._data.get(field_name, None) if hasattr(field, 'id'): if field.id in inspected: continue diff --git a/mongoengine/document.py b/mongoengine/document.py index 27792780..f8bf769d 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -375,7 +375,7 @@ class DynamicDocument(Document): :class:`~mongoengine.DynamicField` and data can be attributed to that field. - ..note:: + .. note:: There is one caveat on Dynamic Documents: fields cannot start with `_` """ diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 01d50c1c..94f79a12 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -169,7 +169,7 @@ class IntField(BaseField): def prepare_query_value(self, op, value): if value is None: return value - + return int(value) @@ -199,7 +199,7 @@ class FloatField(BaseField): def prepare_query_value(self, op, value): if value is None: return value - + return float(value) @@ -451,7 +451,7 @@ class GenericEmbeddedDocumentField(BaseField): Only valid values are subclasses of :class:`~mongoengine.EmbeddedDocument`. - ..note :: You can use the choices param to limit the acceptable + .. note:: You can use the choices param to limit the acceptable EmbeddedDocument types """ @@ -483,7 +483,7 @@ class GenericEmbeddedDocumentField(BaseField): class DynamicField(BaseField): - """A tryly dynamic field type capable of handling different and varying + """A truly dynamic field type capable of handling different and varying types of data. Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data""" @@ -530,6 +530,8 @@ class ListField(ComplexBaseField): """A list field that wraps a standard field, allowing multiple instances of the field to be used as a list in the database. + If using with ReferenceFields see: :ref:`one-to-many-with-listfields` + .. note:: Required means it cannot be empty - as the default for ListFields is [] """ @@ -766,10 +768,10 @@ class GenericReferenceField(BaseField): """A reference to *any* :class:`~mongoengine.document.Document` subclass that will be automatically dereferenced on access (lazily). - ..note :: Any documents used as a generic reference must be registered in the + .. note:: Any documents used as a generic reference must be registered in the document registry. Importing the model will automatically register it. - ..note :: You can use the choices param to limit the acceptable Document types + .. note:: You can use the choices param to limit the acceptable Document types .. versionadded:: 0.3 """ diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 6499c3e0..0d1d95b7 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -806,9 +806,9 @@ class QuerySet(object): keyword argument called :attr:`defaults`. .. note:: This requires two separate operations and therefore a - race condition exists. Because there are no transactions in mongoDB - other approaches should be investigated, to ensure you don't - accidently duplicate data when using this method. + race condition exists. Because there are no transactions in mongoDB + other approaches should be investigated, to ensure you don't + accidently duplicate data when using this method. :param write_options: optional extra keyword arguments used if we have to create a new document. @@ -816,8 +816,8 @@ class QuerySet(object): :param auto_save: if the object is to be saved automatically if not found. + .. versionchanged:: 0.6 - added `auto_save` .. versionadded:: 0.3 - .. versionupdated:: 0.6 - added `auto_save` """ defaults = query.get('defaults', {}) if 'defaults' in query: @@ -1882,9 +1882,9 @@ class QuerySetManager(object): queryset = queryset_class(owner, owner._get_collection()) if self.get_queryset: var_names = self.get_queryset.func_code.co_varnames - if var_names == ('queryset',): + if len(var_names) == 1: queryset = self.get_queryset(queryset) - elif var_names == ('doc_cls', 'queryset',): + elif len(var_names) == 2: queryset = self.get_queryset(owner, queryset) else: queryset = partial(self.get_queryset, owner, queryset) diff --git a/python-mongoengine.spec b/python-mongoengine.spec index 03093b36..4dfbeccc 100644 --- a/python-mongoengine.spec +++ b/python-mongoengine.spec @@ -5,7 +5,7 @@ %define srcname mongoengine Name: python-%{srcname} -Version: 0.6.16 +Version: 0.6.18 Release: 1%{?dist} Summary: A Python Document-Object Mapper for working with MongoDB diff --git a/tests/test_queryset.py b/tests/test_queryset.py index 1bac6a97..e623790f 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -579,6 +579,64 @@ class QuerySetTest(unittest.TestCase): Blog.objects.insert([blog2, blog3], write_options={'continue_on_error': True}) self.assertEqual(Blog.objects.count(), 3) + def test_get_changed_fields_query_count(self): + + class Person(Document): + name = StringField() + owns = ListField(ReferenceField('Organization')) + projects = ListField(ReferenceField('Project')) + + class Organization(Document): + name = StringField() + owner = ReferenceField('Person') + employees = ListField(ReferenceField('Person')) + + class Project(Document): + name = StringField() + + Person.drop_collection() + Organization.drop_collection() + Project.drop_collection() + + r1 = Project(name="r1").save() + r2 = Project(name="r2").save() + r3 = Project(name="r3").save() + p1 = Person(name="p1", projects=[r1, r2]).save() + p2 = Person(name="p2", projects=[r2]).save() + o1 = Organization(name="o1", employees=[p1]).save() + + with query_counter() as q: + self.assertEqual(q, 0) + + fresh_o1 = Organization.objects.get(id=o1.id) + self.assertEqual(1, q) + fresh_o1._get_changed_fields() + self.assertEqual(1, q) + + with query_counter() as q: + self.assertEqual(q, 0) + + fresh_o1 = Organization.objects.get(id=o1.id) + fresh_o1.save() + + self.assertEquals(q, 2) + + with query_counter() as q: + self.assertEqual(q, 0) + + fresh_o1 = Organization.objects.get(id=o1.id) + fresh_o1.save(cascade=False) + + self.assertEquals(q, 2) + + with query_counter() as q: + self.assertEqual(q, 0) + + fresh_o1 = Organization.objects.get(id=o1.id) + fresh_o1.employees.append(p2) + fresh_o1.save(cascade=False) + + self.assertEquals(q, 3) def test_slave_okay(self): """Ensures that a query can take slave_okay syntax @@ -2228,28 +2286,28 @@ class QuerySetTest(unittest.TestCase): date = DateTimeField(default=datetime.now) @queryset_manager - def objects(doc_cls, queryset): - return queryset(deleted=False) + def objects(cls, qryset): + return qryset(deleted=False) @queryset_manager - def music_posts(doc_cls, queryset): - return queryset(tags='music', deleted=False).order_by('-date') + def music_posts(doc_cls, queryset, deleted=False): + return queryset(tags='music', + deleted=deleted).order_by('date') BlogPost.drop_collection() - post1 = BlogPost(tags=['music', 'film']) - post1.save() - post2 = BlogPost(tags=['music']) - post2.save() - post3 = BlogPost(tags=['film', 'actors']) - post3.save() - post4 = BlogPost(tags=['film', 'actors'], deleted=True) - post4.save() + post1 = BlogPost(tags=['music', 'film']).save() + post2 = BlogPost(tags=['music']).save() + post3 = BlogPost(tags=['film', 'actors']).save() + post4 = BlogPost(tags=['film', 'actors', 'music'], deleted=True).save() - self.assertEqual([p.id for p in BlogPost.objects], + self.assertEqual([p.id for p in BlogPost.objects()], [post1.id, post2.id, post3.id]) - self.assertEqual([p.id for p in BlogPost.music_posts], - [post2.id, post1.id]) + self.assertEqual([p.id for p in BlogPost.music_posts()], + [post1.id, post2.id]) + + self.assertEqual([p.id for p in BlogPost.music_posts(True)], + [post4.id]) BlogPost.drop_collection() From 7ca81d6fb8d71d5f01bf32e9428f7288ed402736 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 30 Jul 2012 13:00:42 +0100 Subject: [PATCH 03/14] Fixes --- mongoengine/base.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 43243b88..ca45080d 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -791,11 +791,11 @@ class BaseDocument(object): self._data = {} # Assign default values to instance - for attr_name, field in self._fields.items(): - if self._db_field_map.get(attr_name, attr_name) in values: + for key, field in self._fields.items(): + if self._db_field_map.get(key, key) in values: continue - value = getattr(self, attr_name, None) - setattr(self, attr_name, value) + value = getattr(self, key, None) + setattr(self, key, value) # Set passed values after initialisation if self._dynamic: @@ -824,8 +824,6 @@ class BaseDocument(object): signals.post_init.send(self.__class__, document=self) def __setattr__(self, name, value): - if not self._initialised: - return super(BaseDocument, self).__setattr__(name, value) # Handle dynamic data only if an initialised dynamic document if self._dynamic and not self._dynamic_lock: From 69d57209f7ace0013fe55cbe2981c35a72b5ff57 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 30 Jul 2012 13:35:45 +0100 Subject: [PATCH 04/14] Minor --- mongoengine/base.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index ca45080d..86cd9b4f 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -111,6 +111,7 @@ class ValidationError(AssertionError): _document_registry = {} +_module_registry = {} def get_document(name): @@ -503,7 +504,6 @@ class DocumentMetaclass(type): simple_class = True for base in bases: - # Include all fields present in superclasses if hasattr(base, '_fields'): doc_fields.update(base._fields) @@ -549,7 +549,7 @@ class DocumentMetaclass(type): # Add the document's fields to the _fields attribute field_names = {} - for attr_name, attr_value in attrs.items(): + for attr_name, attr_value in attrs.iteritems(): if hasattr(attr_value, "__class__") and \ issubclass(attr_value.__class__, BaseField): attr_value.name = attr_name @@ -565,7 +565,15 @@ class DocumentMetaclass(type): attrs['_db_field_map'] = dict([(k, v.db_field) for k, v in doc_fields.items() if k != v.db_field]) attrs['_reverse_db_field_map'] = dict([(v, k) for k, v in attrs['_db_field_map'].items()]) - from mongoengine import Document, EmbeddedDocument, DictField + if 'Document' not in _module_registry: + from mongoengine import Document, EmbeddedDocument, DictField + _module_registry['Document'] = Document + _module_registry['EmbeddedDocument'] = EmbeddedDocument + _module_registry['DictField'] = DictField + else: + Document = _module_registry.get('Document') + EmbeddedDocument = _module_registry.get('EmbeddedDocument') + DictField = _module_registry.get('DictField') new_class = super_new(cls, name, bases, attrs) for field in new_class._fields.values(): From c419f3379ad2ce8ef266ac34786844b76a2cf3bb Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 30 Jul 2012 15:43:53 +0100 Subject: [PATCH 05/14] Style changes --- mongoengine/base.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 86cd9b4f..9d342e8c 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -15,7 +15,6 @@ import operator from functools import partial from bson.dbref import DBRef - class NotRegistered(Exception): pass @@ -474,6 +473,7 @@ class DocumentMetaclass(type): """ def __new__(cls, name, bases, attrs): + def _get_mixin_fields(base): attrs = {} attrs.update(dict([(k, v) for k, v in base.__dict__.items() @@ -502,7 +502,6 @@ class DocumentMetaclass(type): class_name = [name] superclasses = {} simple_class = True - for base in bases: # Include all fields present in superclasses if hasattr(base, '_fields'): @@ -543,20 +542,18 @@ class DocumentMetaclass(type): if not simple_class and not meta['allow_inheritance'] and not meta['abstract']: raise ValueError('Only direct subclasses of Document may set ' '"allow_inheritance" to False') - attrs['_meta'] = meta - attrs['_class_name'] = doc_class_name - attrs['_superclasses'] = superclasses # Add the document's fields to the _fields attribute field_names = {} for attr_name, attr_value in attrs.iteritems(): - if hasattr(attr_value, "__class__") and \ - issubclass(attr_value.__class__, BaseField): - attr_value.name = attr_name - if not attr_value.db_field: - attr_value.db_field = attr_name - doc_fields[attr_name] = attr_value - field_names[attr_value.db_field] = field_names.get(attr_value.db_field, 0) + 1 + if not isinstance(attr_value, BaseField): + continue + attr_value.name = attr_name + if not attr_value.db_field: + attr_value.db_field = attr_name + doc_fields[attr_name] = attr_value + + field_names[attr_value.db_field] = field_names.get(attr_value.db_field, 0) + 1 duplicate_db_fields = [k for k, v in field_names.items() if v > 1] if duplicate_db_fields: @@ -564,6 +561,9 @@ class DocumentMetaclass(type): attrs['_fields'] = doc_fields attrs['_db_field_map'] = dict([(k, v.db_field) for k, v in doc_fields.items() if k != v.db_field]) attrs['_reverse_db_field_map'] = dict([(v, k) for k, v in attrs['_db_field_map'].items()]) + attrs['_meta'] = meta + attrs['_class_name'] = doc_class_name + attrs['_superclasses'] = superclasses if 'Document' not in _module_registry: from mongoengine import Document, EmbeddedDocument, DictField @@ -576,7 +576,8 @@ class DocumentMetaclass(type): DictField = _module_registry.get('DictField') new_class = super_new(cls, name, bases, attrs) - for field in new_class._fields.values(): + + for field in new_class._fields.itervalues(): field.owner_document = new_class delete_rule = getattr(field, 'reverse_delete_rule', DO_NOTHING) @@ -728,7 +729,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): unique_indexes = cls._unique_with_indexes(new_class) new_class._meta['unique_indexes'] = unique_indexes - for field_name, field in new_class._fields.items(): + for field_name, field in new_class._fields.iteritems(): # Check for custom primary key if field.primary_key: current_pk = new_class._meta['id_field'] @@ -799,7 +800,7 @@ class BaseDocument(object): self._data = {} # Assign default values to instance - for key, field in self._fields.items(): + for key, field in self._fields.iteritems(): if self._db_field_map.get(key, key) in values: continue value = getattr(self, key, None) @@ -809,13 +810,13 @@ class BaseDocument(object): if self._dynamic: self._dynamic_fields = {} dynamic_data = {} - for key, value in values.items(): + for key, value in values.iteritems(): if key in self._fields or key == '_id': setattr(self, key, value) elif self._dynamic: dynamic_data[key] = value else: - for key, value in values.items(): + for key, value in values.iteritems(): key = self._reverse_db_field_map.get(key, key) setattr(self, key, value) @@ -824,7 +825,7 @@ class BaseDocument(object): if self._dynamic: self._dynamic_lock = False - for key, value in dynamic_data.items(): + for key, value in dynamic_data.iteritems(): setattr(self, key, value) # Flag initialised From d1add62a0637b839de8038eba0ea57c77c3e80f1 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Wed, 1 Aug 2012 13:11:36 +0100 Subject: [PATCH 06/14] More updates --- mongoengine/base.py | 22 ++++++++++++++-------- mongoengine/queryset.py | 15 ++++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 9d342e8c..f8752f36 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -264,7 +264,7 @@ class ComplexBaseField(BaseField): """ field = None - _dereference = False + __dereference = False def __get__(self, instance, owner): """Descriptor to automatically dereference references. @@ -276,8 +276,6 @@ class ComplexBaseField(BaseField): dereference = self.field is None or isinstance(self.field, (GenericReferenceField, ReferenceField)) if not self._dereference and instance._initialised and dereference: - from dereference import DeReference - self._dereference = DeReference() # Cached instance._data[self.name] = self._dereference( instance._data.get(self.name), max_depth=1, instance=instance, name=self.name @@ -293,14 +291,13 @@ class ComplexBaseField(BaseField): value = BaseDict(value, instance, self.name) instance._data[self.name] = value - if self._dereference and instance._initialised and \ - isinstance(value, (BaseList, BaseDict)) and not value._dereferenced: + if (instance._initialised and isinstance(value, (BaseList, BaseDict)) + and not value._dereferenced): value = self._dereference( value, max_depth=1, instance=instance, name=self.name ) value._dereferenced = True instance._data[self.name] = value - return value def __set__(self, instance, value): @@ -441,6 +438,13 @@ class ComplexBaseField(BaseField): owner_document = property(_get_owner_document, _set_owner_document) + @property + def _dereference(self,): + if not self.__dereference: + from dereference import DeReference + self.__dereference = DeReference() # Cached + return self.__dereference + class ObjectIdField(BaseField): """An field wrapper around MongoDB's ObjectIds. @@ -493,8 +497,9 @@ class DocumentMetaclass(type): attrs.update(_get_mixin_fields(p_base)) return attrs - metaclass = attrs.get('__metaclass__') super_new = super(DocumentMetaclass, cls).__new__ + + metaclass = attrs.get('__metaclass__') if metaclass and issubclass(metaclass, DocumentMetaclass): return super_new(cls, name, bases, attrs) @@ -566,7 +571,8 @@ class DocumentMetaclass(type): attrs['_superclasses'] = superclasses if 'Document' not in _module_registry: - from mongoengine import Document, EmbeddedDocument, DictField + from mongoengine.document import Document, EmbeddedDocument + from mongoengine.fields import DictField _module_registry['Document'] = Document _module_registry['EmbeddedDocument'] = EmbeddedDocument _module_registry['DictField'] = DictField diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 0d1d95b7..0e93fd2b 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -329,6 +329,7 @@ class QuerySet(object): """ __already_indexed = set() + __dereference = False def __init__(self, document, collection): self._document = document @@ -600,7 +601,6 @@ class QuerySet(object): if self._hint != -1: self._cursor_obj.hint(self._hint) - return self._cursor_obj @classmethod @@ -1153,8 +1153,7 @@ class QuerySet(object): .. versionadded:: 0.4 .. versionchanged:: 0.5 - Fixed handling references """ - from dereference import DeReference - return DeReference()(self._cursor.distinct(field), 1) + return self._dereference(self._cursor.distinct(field), 1) def only(self, *fields): """Load only a subset of this document's fields. :: @@ -1854,10 +1853,16 @@ class QuerySet(object): .. versionadded:: 0.5 """ - from dereference import DeReference # Make select related work the same for querysets max_depth += 1 - return DeReference()(self, max_depth=max_depth) + return self._dereference(self, max_depth=max_depth) + + @property + def _dereference(self): + if not self.__dereference: + from dereference import DeReference + self.__dereference = DeReference() # Cached + return self.__dereference class QuerySetManager(object): From 8ac9e6dc195d26a414dd5850790e1e5861a1c9b5 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Thu, 2 Aug 2012 14:11:02 +0100 Subject: [PATCH 07/14] Updated the documents --- docs/guide/querying.rst | 9 +++++++-- mongoengine/queryset.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/guide/querying.rst b/docs/guide/querying.rst index a9567e20..14498017 100644 --- a/docs/guide/querying.rst +++ b/docs/guide/querying.rst @@ -232,7 +232,7 @@ custom manager methods as you like:: BlogPost(title='test1', published=False).save() BlogPost(title='test2', published=True).save() assert len(BlogPost.objects) == 2 - assert len(BlogPost.live_posts) == 1 + assert len(BlogPost.live_posts()) == 1 Custom QuerySets ================ @@ -243,11 +243,16 @@ a document, set ``queryset_class`` to the custom class in a :class:`~mongoengine.Document`\ s ``meta`` dictionary:: class AwesomerQuerySet(QuerySet): - pass + + def get_awesome(self): + return self.filter(awesome=True) class Page(Document): meta = {'queryset_class': AwesomerQuerySet} + # To call: + Page.objects.get_awesome() + .. versionadded:: 0.4 Aggregation diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index b7453c6a..ef0ed04d 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -1861,6 +1861,17 @@ class QuerySet(object): class QuerySetManager(object): + """ + The default QuerySet Manager. + + Custom QuerySet Manager functions can extend this class and users can + add extra queryset functionality. Any custom manager methods must accept a + :class:`~mongoengine.Document` class as its first argument, and a + :class:`~mongoengine.queryset.QuerySet` as its second argument. + + The method function should return a :class:`~mongoengine.queryset.QuerySet` + , probably the same one that was passed in, but modified in some way. + """ get_queryset = None From 3577773af3c91f277a85096c8e23cfecb8679ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Dupanovi=C4=87?= Date: Fri, 3 Aug 2012 14:55:18 +0300 Subject: [PATCH 08/14] Added reference to the official repository --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 548737cd..1305b6e9 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,7 @@ MongoEngine =========== :Info: MongoEngine is an ORM-like layer on top of PyMongo. +:Repository: https://github.com/MongoEngine/mongoengine :Author: Harry Marr (http://github.com/hmarr) :Maintainer: Ross Lawley (http://github.com/rozza) From 2801b38c75641a470b725b1ffe5ebe1c7c37d6eb Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Fri, 3 Aug 2012 14:36:31 +0100 Subject: [PATCH 09/14] Version Bump --- docs/changelog.rst | 4 ++-- mongoengine/__init__.py | 2 +- python-mongoengine.spec | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 08091acb..8f44c810 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,8 +2,8 @@ Changelog ========= -Changes in 0.6.X -================ +Changes in 0.6.19 +================= - Added Binary support to UUID (MongoEngine/mongoengine#47) - Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46) diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index 5f4f7f1d..362b1e97 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -12,7 +12,7 @@ from signals import * __all__ = (document.__all__ + fields.__all__ + connection.__all__ + queryset.__all__ + signals.__all__) -VERSION = (0, 6, 18) +VERSION = (0, 6, 19) def get_version(): diff --git a/python-mongoengine.spec b/python-mongoengine.spec index 4dfbeccc..902f4ce7 100644 --- a/python-mongoengine.spec +++ b/python-mongoengine.spec @@ -5,7 +5,7 @@ %define srcname mongoengine Name: python-%{srcname} -Version: 0.6.18 +Version: 0.6.19 Release: 1%{?dist} Summary: A Python Document-Object Mapper for working with MongoDB From f99b7a811b3ea8b2930dbc72f2a5bfc1f0116278 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 7 Aug 2012 08:53:58 +0100 Subject: [PATCH 10/14] Fixed error in Binary Field --- docs/changelog.rst | 4 ++++ mongoengine/fields.py | 2 +- tests/test_fields.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8f44c810..418c861d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,10 @@ Changelog ========= +Changes in 0.6.X +================ +- Fixed BinaryField lookup re (MongoEngine/mongoengine#48) + Changes in 0.6.19 ================= diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 82689929..8e3cf15a 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1327,7 +1327,7 @@ class UUIDField(BaseField): super(UUIDField, self).__init__(**kwargs) def to_python(self, value): - if not self.binary: + if not self._binary: if not isinstance(value, basestring): value = unicode(value) return uuid.UUID(value) diff --git a/tests/test_fields.py b/tests/test_fields.py index c4013c10..a6eaca43 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -283,6 +283,7 @@ class FieldTest(unittest.TestCase): uu = uuid.uuid4() Person(api_key=uu).save() self.assertEqual(1, Person.objects(api_key=uu).count()) + self.assertEqual(uu, Person.objects.first().api_key) person = Person() valid = (uuid.uuid4(), uuid.uuid1()) @@ -307,6 +308,7 @@ class FieldTest(unittest.TestCase): uu = uuid.uuid4() Person(api_key=uu).save() self.assertEqual(1, Person.objects(api_key=uu).count()) + self.assertEqual(uu, Person.objects.first().api_key) person = Person() valid = (uuid.uuid4(), uuid.uuid1()) From 12c8b5c0b9e4793020be77a533feac5d88852ab9 Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Fri, 27 Jul 2012 23:06:47 -0700 Subject: [PATCH 11/14] Make chained querysets work if constraining the same fields. Refs hmarr/mongoengine#554 --- mongoengine/queryset.py | 18 ++++++++++++++++-- tests/test_queryset.py | 28 +++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index ef0ed04d..703b6e5f 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -765,8 +765,22 @@ class QuerySet(object): key = '.'.join(parts) if op is None or key not in mongo_query: mongo_query[key] = value - elif key in mongo_query and isinstance(mongo_query[key], dict): - mongo_query[key].update(value) + elif key in mongo_query: + if isinstance(mongo_query[key], dict) and isinstance(value, dict): + mongo_query[key].update(value) + elif isinstance(mongo_query[key], list): + mongo_query[key].append(value) + else: + mongo_query[key] = [mongo_query[key], value] + + for k, v in mongo_query.items(): + if isinstance(v, list): + value = [{k:val} for val in v] + if '$and' in mongo_query.keys(): + mongo_query['$and'].append(value) + else: + mongo_query['$and'] = value + del mongo_query[k] return mongo_query diff --git a/tests/test_queryset.py b/tests/test_queryset.py index b4ae805b..02c97e47 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -827,7 +827,11 @@ class QuerySetTest(unittest.TestCase): def test_filter_chaining(self): """Ensure filters can be chained together. """ + class Blog(Document): + id = StringField(unique=True, primary_key=True) + class BlogPost(Document): + blog = ReferenceField(Blog) title = StringField() is_published = BooleanField() published_date = DateTimeField() @@ -836,13 +840,24 @@ class QuerySetTest(unittest.TestCase): def published(doc_cls, queryset): return queryset(is_published=True) - blog_post_1 = BlogPost(title="Blog Post #1", + Blog.drop_collection() + BlogPost.drop_collection() + + blog_1 = Blog(id="1") + blog_2 = Blog(id="2") + blog_3 = Blog(id="3") + + blog_1.save() + blog_2.save() + blog_3.save() + + blog_post_1 = BlogPost(blog=blog_1, title="Blog Post #1", is_published = True, published_date=datetime(2010, 1, 5, 0, 0 ,0)) - blog_post_2 = BlogPost(title="Blog Post #2", + blog_post_2 = BlogPost(blog=blog_2, title="Blog Post #2", is_published = True, published_date=datetime(2010, 1, 6, 0, 0 ,0)) - blog_post_3 = BlogPost(title="Blog Post #3", + blog_post_3 = BlogPost(blog=blog_3, title="Blog Post #3", is_published = True, published_date=datetime(2010, 1, 7, 0, 0 ,0)) @@ -856,7 +871,14 @@ class QuerySetTest(unittest.TestCase): published_date__lt=datetime(2010, 1, 7, 0, 0 ,0)) self.assertEqual(published_posts.count(), 2) + + blog_posts = BlogPost.objects + blog_posts = blog_posts.filter(blog__in=[blog_1, blog_2]) + blog_posts = blog_posts.filter(blog=blog_3) + self.assertEqual(blog_posts.count(), 0) + BlogPost.drop_collection() + Blog.drop_collection() def test_ordering(self): """Ensure default ordering is applied and can be overridden. From 95b1783834810cd1c474851b4882a5f662887091 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 7 Aug 2012 09:31:51 +0100 Subject: [PATCH 12/14] Updated changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 418c861d..b53e008e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Changes in 0.6.X ================ +- Improved support for chained querysets when constraining the same fields (hmarr/mongoengine#554) - Fixed BinaryField lookup re (MongoEngine/mongoengine#48) Changes in 0.6.19 From 475488b9f283808faa270692017229f2605ab9ab Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 7 Aug 2012 10:04:05 +0100 Subject: [PATCH 13/14] Added support for distinct and db_alias (MongoEngine/mongoengine#59) --- AUTHORS | 3 ++- docs/changelog.rst | 5 +++-- mongoengine/dereference.py | 4 +++- mongoengine/queryset.py | 3 ++- tests/test_queryset.py | 24 +++++++++++++++++++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index fe8be767..ab0469e9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -114,4 +114,5 @@ that much better: * Jaime Irurzun * Alexandre González * Thomas Steinacher - * Tommi Komulainen \ No newline at end of file + * Tommi Komulainen + * Peter Landry diff --git a/docs/changelog.rst b/docs/changelog.rst index b53e008e..c05da946 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,8 +2,9 @@ Changelog ========= -Changes in 0.6.X -================ +Changes in 0.6.20 +================= +- Added support for distinct and db_alias (MongoEngine/mongoengine#59) - Improved support for chained querysets when constraining the same fields (hmarr/mongoengine#554) - Fixed BinaryField lookup re (MongoEngine/mongoengine#48) diff --git a/mongoengine/dereference.py b/mongoengine/dereference.py index f74e224c..637380d6 100644 --- a/mongoengine/dereference.py +++ b/mongoengine/dereference.py @@ -34,7 +34,9 @@ class DeReference(object): doc_type = None if instance and instance._fields: - doc_type = instance._fields[name].field + doc_type = instance._fields[name] + if hasattr(doc_type, 'field'): + doc_type = doc_type.field if isinstance(doc_type, ReferenceField): doc_type = doc_type.document_type diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 703b6e5f..5c7b9c88 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -1168,7 +1168,8 @@ class QuerySet(object): .. versionchanged:: 0.5 - Fixed handling references """ from dereference import DeReference - return DeReference()(self._cursor.distinct(field), 1) + return DeReference()(self._cursor.distinct(field), 1, + name=field, instance=self._document) def only(self, *fields): """Load only a subset of this document's fields. :: diff --git a/tests/test_queryset.py b/tests/test_queryset.py index 02c97e47..8f846ea6 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -871,7 +871,7 @@ class QuerySetTest(unittest.TestCase): published_date__lt=datetime(2010, 1, 7, 0, 0 ,0)) self.assertEqual(published_posts.count(), 2) - + blog_posts = BlogPost.objects blog_posts = blog_posts.filter(blog__in=[blog_1, blog_2]) blog_posts = blog_posts.filter(blog=blog_3) @@ -2299,6 +2299,28 @@ class QuerySetTest(unittest.TestCase): self.assertEquals(Foo.objects.distinct("bar"), [bar]) + def test_distinct_handles_references_to_alias(self): + register_connection('testdb', 'mongoenginetest2') + + class Foo(Document): + bar = ReferenceField("Bar") + meta = {'db_alias': 'testdb'} + + class Bar(Document): + text = StringField() + meta = {'db_alias': 'testdb'} + + Bar.drop_collection() + Foo.drop_collection() + + bar = Bar(text="hi") + bar.save() + + foo = Foo(bar=bar) + foo.save() + + self.assertEquals(Foo.objects.distinct("bar"), [bar]) + def test_custom_manager(self): """Ensure that custom QuerySetManager instances work as expected. """ From 9cc61640263697addeeee8c2915bd9b48b24cd52 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 7 Aug 2012 10:05:01 +0100 Subject: [PATCH 14/14] Version bump --- mongoengine/__init__.py | 2 +- python-mongoengine.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index 362b1e97..ea3dd5e6 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -12,7 +12,7 @@ from signals import * __all__ = (document.__all__ + fields.__all__ + connection.__all__ + queryset.__all__ + signals.__all__) -VERSION = (0, 6, 19) +VERSION = (0, 6, 20) def get_version(): diff --git a/python-mongoengine.spec b/python-mongoengine.spec index 902f4ce7..4e803263 100644 --- a/python-mongoengine.spec +++ b/python-mongoengine.spec @@ -5,7 +5,7 @@ %define srcname mongoengine Name: python-%{srcname} -Version: 0.6.19 +Version: 0.6.20 Release: 1%{?dist} Summary: A Python Document-Object Mapper for working with MongoDB