From 12a7fc1af18173d2bdcbd59f84a639dd832f72bd Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Fri, 18 Dec 2009 11:34:32 -0600 Subject: [PATCH 1/4] removed reliance on '_cls' in document; fields only parsed if '__class__' present, allowing inner classes and non-field attributes on a document --- mongoengine/base.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 61deb409..6cb453c1 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -111,7 +111,8 @@ class DocumentMetaclass(type): # Add the document's fields to the _fields attribute for attr_name, attr_value in attrs.items(): - if issubclass(attr_value.__class__, BaseField): + if hasattr(attr_value, "__class__") and \ + issubclass(attr_value.__class__, BaseField): if not attr_value.name: attr_value.name = attr_name doc_fields[attr_name] = attr_value @@ -135,7 +136,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): if attrs.get('__metaclass__') == TopLevelDocumentMetaclass: return super_new(cls, name, bases, attrs) - collection = name.lower() + collection = attrs.get('__collection__', name.lower()) + # Subclassed documents inherit collection from superclass for base in bases: if hasattr(base, '_meta') and 'collection' in base._meta: @@ -233,9 +235,14 @@ class BaseDocument(object): def _from_son(cls, son): """Create an instance of a Document (subclass) from a PyMongo SOM. """ - class_name = son[u'_cls'] + # get the class name from the document, falling back to the given + # class if unavailable + class_name = son.get(u'_cls', cls._class_name) + data = dict((str(key), value) for key, value in son.items()) - del data['_cls'] + + if '_cls' in data: + del data['_cls'] # Return correct subclass for document type if class_name != cls._class_name: From aa9cba38c4504b63573d605f8d21c5dcd37d910a Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Fri, 18 Dec 2009 11:35:26 -0600 Subject: [PATCH 2/4] added 'ensure_index' and 'order_by' methods to queryset. --- mongoengine/queryset.py | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index c5997cf6..dd860524 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -11,8 +11,22 @@ class QuerySet(object): def __init__(self, document, collection): self._document = document self._collection = collection - self._query = {'_types': self._document._class_name} + self._query = {} self._cursor_obj = None + self._ordering = [] + + def ensure_index(self, key_or_list, direction=None): + """Ensure that the given indexes are in place. + """ + if isinstance(key_or_list, basestring): + # single-field indexes needn't specify a direction + if key_or_list.startswith("-"): + key_or_list = key_or_list[1:] + self._collection.ensure_index(key_or_list) + elif isinstance(key_or_list, (list, tuple)): + print key_or_list + self._collection.ensure_index(key_or_list) + return self def __call__(self, **query): """Filter the selected documents by calling the queryset with a query. @@ -91,7 +105,36 @@ class QuerySet(object): """ self._cursor.skip(n) return self + + def order_by(self, *params): + """Apply ordering conditions, Django-style. + + e.g., ``Model.objects.().order_by("-published_date", "ordering")`` + will order first by ``published_date DESC``, and then ``ordering ASC``. + + """ + if not params: + self._ordering = [] + for param in params: + if param.startswith("-"): + param = param[1:] + sort_dir = pymongo.DESCENDING + else: + sort_dir = pymongo.ASCENDING + sort_rule = (param, sort_dir) + if not sort_rule in self._ordering: + self._ordering.append(sort_rule) + self._cursor.sort(self._ordering) + return self + + def explain(self, format=False): + plan = self._cursor.explain() + if format: + import pprint + plan = pprint.pformat(plan) + return plan + def delete(self): """Delete the documents matched by the query. """ From ccb4827ec9ff55aca51e6175faaf995e8bf5f084 Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Tue, 5 Jan 2010 14:28:24 -0600 Subject: [PATCH 3/4] added meta support for indexes ensured at call-time --- mongoengine/base.py | 4 ++++ mongoengine/queryset.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index c00e452c..47db127f 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -147,8 +147,12 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): 'allow_inheritance': True, 'max_documents': None, 'max_size': None, + 'indexes': [] # indexes to be ensured at runtime } + + # Apply document-defined meta options meta.update(attrs.get('meta', {})) + # Only simple classes - direct subclasses of Document - may set # allow_inheritance to False if not simple_class and not meta['allow_inheritance']: diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index e98c11a1..ed634391 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -31,18 +31,32 @@ class QuerySet(object): """ if isinstance(key_or_list, basestring): # single-field indexes needn't specify a direction - if key_or_list.startswith("-"): + if key_or_list.startswith("-") or key_or_list.startswith("+"): key_or_list = key_or_list[1:] self._collection.ensure_index(key_or_list) elif isinstance(key_or_list, (list, tuple)): - print key_or_list - self._collection.ensure_index(key_or_list) + index_list = [] + for key in key_or_list: + if key.startswith("-"): + index_list.append((key[1:], pymongo.DESCENDING)) + else: + if key.startswith("+"): + key = key[1:] + index_list.append((key, pymongo.ASCENDING)) + self._collection.ensure_index(index_list) return self def __call__(self, **query): """Filter the selected documents by calling the :class:`~mongoengine.QuerySet` with a query. """ + + # ensure document-defined indexes are created + if self._document._meta['indexes']: + for key_or_list in self._document._meta['indexes']: + # print "key", key_or_list + self.ensure_index(key_or_list) + query = QuerySet._transform_query(_doc_cls=self._document, **query) self._query.update(query) return self From 9195d96705e1aae717dac9ad82c41f3f9c7952cc Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Thu, 7 Jan 2010 23:08:33 -0600 Subject: [PATCH 4/4] added default ordering to meta options, included docs and tests --- docs/userguide.rst | 35 +++++++++++++++++++++++++++++++++++ mongoengine/base.py | 1 + mongoengine/queryset.py | 6 +++++- tests/queryset.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/docs/userguide.rst b/docs/userguide.rst index 602d51b6..405f7418 100644 --- a/docs/userguide.rst +++ b/docs/userguide.rst @@ -183,6 +183,41 @@ sign. Note that direction only matters on multi-field indexes. :: meta = { 'indexes': ['title', ('title', '-rating')] } + +Ordering +-------- + +A default ordering can be specified for your :class:`~mongoengine.queryset.QuerySet` +using the :attr:`ordering` attributeof :attr:`~Document.meta`. +Ordering will be applied when the ``QuerySet`` is created, and can be +overridden by subsequent calls to :meth:`~mongoengine.QuerySet.order_by`. :: + + from datetime import datetime + + class BlogPost(Document): + title = StringField() + published_date = DateTimeField() + + meta = { + 'ordering': ['-published_date'] + } + + blog_post_1 = BlogPost(title="Blog Post #1", published_date=datetime(2010, 1, 5, 0, 0 ,0)) + blog_post_2 = BlogPost(title="Blog Post #2", published_date=datetime(2010, 1, 6, 0, 0 ,0)) + blog_post_3 = BlogPost(title="Blog Post #3", published_date=datetime(2010, 1, 7, 0, 0 ,0)) + + blog_post_1.save() + blog_post_2.save() + blog_post_3.save() + + # get the "first" BlogPost using default ordering + # from BlogPost.meta.ordering + latest_post = BlogPost.objects.first() + self.assertEqual(latest_post.title, "Blog Post #3") + + # override default ordering, order BlogPosts by "published_date" + first_post = BlogPost.objects.order_by("+published_date").first() + self.assertEqual(first_post.title, "Blog Post #1") Document inheritance -------------------- diff --git a/mongoengine/base.py b/mongoengine/base.py index 8a423a3e..f930f06c 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -155,6 +155,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): 'allow_inheritance': True, 'max_documents': None, 'max_size': None, + 'ordering': [], # default ordering applied at runtime 'indexes': [] # indexes to be ensured at runtime } diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 761a9003..836d847a 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -59,7 +59,6 @@ class QuerySet(object): # ensure document-defined indexes are created if self._document._meta['indexes']: for key_or_list in self._document._meta['indexes']: - # print "key", key_or_list self.ensure_index(key_or_list) query = QuerySet._transform_query(_doc_cls=self._document, **query) @@ -70,6 +69,11 @@ class QuerySet(object): def _cursor(self): if not self._cursor_obj: self._cursor_obj = self._collection.find(self._query) + + # apply default ordering + if self._document._meta['ordering']: + self.order_by(*self._document._meta['ordering']) + return self._cursor_obj @classmethod diff --git a/tests/queryset.py b/tests/queryset.py index e7e79ccc..f1ba9841 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -131,6 +131,36 @@ class QuerySetTest(unittest.TestCase): person = self.Person.objects.with_id(person1.id) self.assertEqual(person.name, "User A") + def test_ordering(self): + """Ensure default ordering is applied and can be overridden. + """ + from datetime import datetime + + class BlogPost(Document): + title = StringField() + published_date = DateTimeField() + + meta = { + 'ordering': ['-published_date'] + } + + blog_post_1 = BlogPost(title="Blog Post #1", published_date=datetime(2010, 1, 5, 0, 0 ,0)) + blog_post_2 = BlogPost(title="Blog Post #2", published_date=datetime(2010, 1, 6, 0, 0 ,0)) + blog_post_3 = BlogPost(title="Blog Post #3", published_date=datetime(2010, 1, 7, 0, 0 ,0)) + + blog_post_1.save() + blog_post_2.save() + blog_post_3.save() + + # get the "first" BlogPost using default ordering + # from BlogPost.meta.ordering + latest_post = BlogPost.objects.first() + self.assertEqual(latest_post.title, "Blog Post #3") + + # override default ordering, order BlogPosts by "published_date" + first_post = BlogPost.objects.order_by("+published_date").first() + self.assertEqual(first_post.title, "Blog Post #1") + def test_find_embedded(self): """Ensure that an embedded document is properly returned from a query. """