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 4b7c6b33..f930f06c 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -136,6 +136,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): collection = name.lower() simple_class = True + # Subclassed documents inherit collection from superclass for base in bases: if hasattr(base, '_meta') and 'collection' in base._meta: @@ -154,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 760f413c..66ab543c 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -19,6 +19,7 @@ class QuerySet(object): self._document = document self._collection = collection self._query = {} + # If inheritance is allowed, only return instances and instances of # subclasses of the class being used if document._meta.get('allow_inheritance'): @@ -29,25 +30,24 @@ class QuerySet(object): """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:] + key_or_list = [key_or_list] + + index_list = [] + # If _types is being used, prepend it to every specified index + if self._document._meta.get('allow_inheritance'): + index_list.append(('_types', 1)) + + for key in key_or_list: + # Get direction from + or - + direction = pymongo.ASCENDING + if key.startswith("-"): + direction = pymongo.DESCENDING + if key.startswith(("+", "-")): + key = key[1:] # Use real field name - key = QuerySet._translate_field_name(self._document, key_or_list) - self._collection.ensure_index(key) - elif isinstance(key_or_list, (list, tuple)): - index_list = [] - for key in key_or_list: - # Get direction from + or - - direction = pymongo.ASCENDING - if key.startswith("-"): - direction = pymongo.DESCENDING - if key.startswith(("+", "-")): - key = key[1:] - # Use real field name - key = QuerySet._translate_field_name(self._document, key) - index_list.append((key, direction)) - self._collection.ensure_index(index_list) + key = QuerySet._translate_field_name(self._document, key) + index_list.append((key, direction)) + self._collection.ensure_index(index_list) return self def __call__(self, **query): @@ -64,7 +64,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) # If _types is being used (for polymorphism), it needs an index @@ -72,7 +71,11 @@ class QuerySet(object): self._collection.ensure_index('_types') 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 @@ -225,6 +228,7 @@ class QuerySet(object): """Return an explain plan record for the :class:`~mongoengine.queryset.QuerySet`\ 's cursor. """ + plan = self._cursor.explain() if format: import pprint diff --git a/tests/document.py b/tests/document.py index 412c89a9..1ba590e3 100644 --- a/tests/document.py +++ b/tests/document.py @@ -242,9 +242,9 @@ class DocumentTest(unittest.TestCase): # Indexes are lazy so use list() to perform query list(BlogPost.objects) info = BlogPost.objects._collection.index_information() - self.assertTrue([('category', 1), ('addDate', -1)] in info.values()) - # Even though descending order was specified, single-key indexes use 1 - self.assertTrue([('addDate', 1)] in info.values()) + self.assertTrue([('_types', 1), ('category', 1), ('addDate', -1)] + in info.values()) + self.assertTrue([('_types', 1), ('addDate', -1)] in info.values()) BlogPost.drop_collection() diff --git a/tests/queryset.py b/tests/queryset.py index 7ea3da26..6bee363a 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. """ @@ -331,10 +361,17 @@ class QuerySetTest(unittest.TestCase): """Ensure that and index is used when '_types' is being used in a query. """ + class BlogPost(Document): + date = DateTimeField() + meta = {'indexes': ['-date']} + # Indexes are lazy so use list() to perform query - list(self.Person.objects) - info = self.Person.objects._collection.index_information() + list(BlogPost.objects) + info = BlogPost.objects._collection.index_information() self.assertTrue([('_types', 1)] in info.values()) + self.assertTrue([('_types', 1), ('date', -1)] in info.values()) + + BlogPost.drop_collection() class BlogPost(Document): title = StringField()