Cleaner ordering code + allow clearing an explicit sort
This commit is contained in:
		| @@ -971,13 +971,31 @@ class BaseQuerySet(object): | |||||||
|     def order_by(self, *keys): |     def order_by(self, *keys): | ||||||
|         """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The |         """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The | ||||||
|         order may be specified by prepending each of the keys by a + or a -. |         order may be specified by prepending each of the keys by a + or a -. | ||||||
|         Ascending order is assumed. |         Ascending order is assumed. If no keys are passed, existing ordering | ||||||
|  |         is cleared instead. | ||||||
|  |  | ||||||
|         :param keys: fields to order the query results by; keys may be |         :param keys: fields to order the query results by; keys may be | ||||||
|             prefixed with **+** or **-** to determine the ordering direction |             prefixed with **+** or **-** to determine the ordering direction | ||||||
|         """ |         """ | ||||||
|         queryset = self.clone() |         queryset = self.clone() | ||||||
|         queryset._ordering = queryset._get_order_by(keys) |  | ||||||
|  |         old_ordering = queryset._ordering | ||||||
|  |         new_ordering = queryset._get_order_by(keys) | ||||||
|  |  | ||||||
|  |         if queryset._cursor_obj: | ||||||
|  |  | ||||||
|  |             # If a cursor object has already been created, apply the sort to it | ||||||
|  |             if new_ordering: | ||||||
|  |                 queryset._cursor_obj.sort(new_ordering) | ||||||
|  |  | ||||||
|  |             # If we're trying to clear a previous explicit ordering, we need | ||||||
|  |             # to clear the cursor entirely (because PyMongo doesn't allow | ||||||
|  |             # clearing an existing sort on a cursor). | ||||||
|  |             elif old_ordering: | ||||||
|  |                 queryset._cursor_obj = None | ||||||
|  |  | ||||||
|  |         queryset._ordering = new_ordering | ||||||
|  |  | ||||||
|         return queryset |         return queryset | ||||||
|  |  | ||||||
|     def comment(self, text): |     def comment(self, text): | ||||||
| @@ -1423,10 +1441,13 @@ class BaseQuerySet(object): | |||||||
|             raise StopIteration |             raise StopIteration | ||||||
|  |  | ||||||
|         raw_doc = self._cursor.next() |         raw_doc = self._cursor.next() | ||||||
|  |  | ||||||
|         if self._as_pymongo: |         if self._as_pymongo: | ||||||
|             return self._get_as_pymongo(raw_doc) |             return self._get_as_pymongo(raw_doc) | ||||||
|         doc = self._document._from_son(raw_doc, |  | ||||||
|                                        _auto_dereference=self._auto_dereference, only_fields=self.only_fields) |         doc = self._document._from_son( | ||||||
|  |             raw_doc, _auto_dereference=self._auto_dereference, | ||||||
|  |             only_fields=self.only_fields) | ||||||
|  |  | ||||||
|         if self._scalar: |         if self._scalar: | ||||||
|             return self._get_scalar(doc) |             return self._get_scalar(doc) | ||||||
| @@ -1436,7 +1457,6 @@ class BaseQuerySet(object): | |||||||
|     def rewind(self): |     def rewind(self): | ||||||
|         """Rewind the cursor to its unevaluated state. |         """Rewind the cursor to its unevaluated state. | ||||||
|  |  | ||||||
|  |  | ||||||
|         .. versionadded:: 0.3 |         .. versionadded:: 0.3 | ||||||
|         """ |         """ | ||||||
|         self._iter = False |         self._iter = False | ||||||
| @@ -1486,11 +1506,16 @@ class BaseQuerySet(object): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def _cursor(self): |     def _cursor(self): | ||||||
|         if self._cursor_obj is None: |         """Return a PyMongo cursor object corresponding to this queryset.""" | ||||||
|  |  | ||||||
|             # In PyMongo 3+, we define the read preference on a collection |         # If _cursor_obj already exists, return it immediately. | ||||||
|             # level, not a cursor level. Thus, we need to get a cloned |         if self._cursor_obj is not None: | ||||||
|             # collection object using `with_options` first. |             return self._cursor_obj | ||||||
|  |  | ||||||
|  |         # Create a new PyMongo cursor. | ||||||
|  |         # XXX In PyMongo 3+, we define the read preference on a collection | ||||||
|  |         # level, not a cursor level. Thus, we need to get a cloned collection | ||||||
|  |         # object using `with_options` first. | ||||||
|         if IS_PYMONGO_3 and self._read_preference is not None: |         if IS_PYMONGO_3 and self._read_preference is not None: | ||||||
|             self._cursor_obj = self._collection\ |             self._cursor_obj = self._collection\ | ||||||
|                 .with_options(read_preference=self._read_preference)\ |                 .with_options(read_preference=self._read_preference)\ | ||||||
| @@ -1498,17 +1523,23 @@ class BaseQuerySet(object): | |||||||
|         else: |         else: | ||||||
|             self._cursor_obj = self._collection.find(self._query, |             self._cursor_obj = self._collection.find(self._query, | ||||||
|                                                      **self._cursor_args) |                                                      **self._cursor_args) | ||||||
|             # Apply where clauses to cursor |         # Apply "where" clauses to cursor | ||||||
|         if self._where_clause: |         if self._where_clause: | ||||||
|             where_clause = self._sub_js_fields(self._where_clause) |             where_clause = self._sub_js_fields(self._where_clause) | ||||||
|             self._cursor_obj.where(where_clause) |             self._cursor_obj.where(where_clause) | ||||||
|  |  | ||||||
|  |         # Apply ordering to the cursor. | ||||||
|  |         # XXX self._ordering can be equal to: | ||||||
|  |         # * None if we didn't explicitly call order_by on this queryset. | ||||||
|  |         # * A list of PyMongo-style sorting tuples. | ||||||
|  |         # * An empty list if we explicitly called order_by() without any | ||||||
|  |         #   arguments. This indicates that we want to clear the default | ||||||
|  |         #   ordering. | ||||||
|         if self._ordering: |         if self._ordering: | ||||||
|                 # Apply query ordering |             # explicit ordering | ||||||
|             self._cursor_obj.sort(self._ordering) |             self._cursor_obj.sort(self._ordering) | ||||||
|         elif self._ordering is None and self._document._meta['ordering']: |         elif self._ordering is None and self._document._meta['ordering']: | ||||||
|                 # Otherwise, apply the ordering from the document model, unless |             # default ordering | ||||||
|                 # it's been explicitly cleared via order_by with no arguments |  | ||||||
|             order = self._get_order_by(self._document._meta['ordering']) |             order = self._get_order_by(self._document._meta['ordering']) | ||||||
|             self._cursor_obj.sort(order) |             self._cursor_obj.sort(order) | ||||||
|  |  | ||||||
| @@ -1696,8 +1727,15 @@ class BaseQuerySet(object): | |||||||
|                     raise err |                     raise err | ||||||
|         return ret |         return ret | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _get_order_by(self, keys): |     def _get_order_by(self, keys): | ||||||
|         """Creates a list of order by fields""" |         """Given a list of MongoEngine-style sort keys, return a list | ||||||
|  |         of sorting tuples that can be applied to a PyMongo cursor. For | ||||||
|  |         example: | ||||||
|  |  | ||||||
|  |         >>> qs._get_order_by(['-last_name', 'first_name']) | ||||||
|  |         [('last_name', -1), ('first_name', 1)] | ||||||
|  |         """ | ||||||
|         key_list = [] |         key_list = [] | ||||||
|         for key in keys: |         for key in keys: | ||||||
|             if not key: |             if not key: | ||||||
| @@ -1710,17 +1748,19 @@ class BaseQuerySet(object): | |||||||
|             direction = pymongo.ASCENDING |             direction = pymongo.ASCENDING | ||||||
|             if key[0] == '-': |             if key[0] == '-': | ||||||
|                 direction = pymongo.DESCENDING |                 direction = pymongo.DESCENDING | ||||||
|  |  | ||||||
|             if key[0] in ('-', '+'): |             if key[0] in ('-', '+'): | ||||||
|                 key = key[1:] |                 key = key[1:] | ||||||
|  |  | ||||||
|             key = key.replace('__', '.') |             key = key.replace('__', '.') | ||||||
|             try: |             try: | ||||||
|                 key = self._document._translate_field_name(key) |                 key = self._document._translate_field_name(key) | ||||||
|             except Exception: |             except Exception: | ||||||
|  |                 # TODO this exception should be more specific | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|             key_list.append((key, direction)) |             key_list.append((key, direction)) | ||||||
|  |  | ||||||
|         if self._cursor_obj and key_list: |  | ||||||
|             self._cursor_obj.sort(key_list) |  | ||||||
|         return key_list |         return key_list | ||||||
|  |  | ||||||
|     def _get_scalar(self, doc): |     def _get_scalar(self, doc): | ||||||
|   | |||||||
| @@ -1242,6 +1242,7 @@ class QuerySetTest(unittest.TestCase): | |||||||
|  |  | ||||||
|         BlogPost.drop_collection() |         BlogPost.drop_collection() | ||||||
|  |  | ||||||
|  |         # default ordering should be used by default | ||||||
|         with db_ops_tracker() as q: |         with db_ops_tracker() as q: | ||||||
|             BlogPost.objects.filter(title='whatever').first() |             BlogPost.objects.filter(title='whatever').first() | ||||||
|             self.assertEqual(len(q.get_ops()), 1) |             self.assertEqual(len(q.get_ops()), 1) | ||||||
| @@ -1250,11 +1251,28 @@ class QuerySetTest(unittest.TestCase): | |||||||
|                 {'published_date': -1} |                 {'published_date': -1} | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |         # calling order_by() should clear the default ordering | ||||||
|         with db_ops_tracker() as q: |         with db_ops_tracker() as q: | ||||||
|             BlogPost.objects.filter(title='whatever').order_by().first() |             BlogPost.objects.filter(title='whatever').order_by().first() | ||||||
|             self.assertEqual(len(q.get_ops()), 1) |             self.assertEqual(len(q.get_ops()), 1) | ||||||
|             self.assertFalse('$orderby' in q.get_ops()[0]['query']) |             self.assertFalse('$orderby' in q.get_ops()[0]['query']) | ||||||
|  |  | ||||||
|  |         # calling an explicit order_by should use a specified sort | ||||||
|  |         with db_ops_tracker() as q: | ||||||
|  |             BlogPost.objects.filter(title='whatever').order_by('published_date').first() | ||||||
|  |             self.assertEqual(len(q.get_ops()), 1) | ||||||
|  |             self.assertEqual( | ||||||
|  |                 q.get_ops()[0]['query']['$orderby'], | ||||||
|  |                 {'published_date': 1} | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # calling order_by() after an explicit sort should clear it | ||||||
|  |         with db_ops_tracker() as q: | ||||||
|  |             qs = BlogPost.objects.filter(title='whatever').order_by('published_date') | ||||||
|  |             qs.order_by().first() | ||||||
|  |             self.assertEqual(len(q.get_ops()), 1) | ||||||
|  |             self.assertFalse('$orderby' in q.get_ops()[0]['query']) | ||||||
|  |  | ||||||
|     def test_no_ordering_for_get(self): |     def test_no_ordering_for_get(self): | ||||||
|         """ Ensure that Doc.objects.get doesn't use any ordering. |         """ Ensure that Doc.objects.get doesn't use any ordering. | ||||||
|         """ |         """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user