Skip to content

Commit ca5d4fa

Browse files
authored
Merge branch 'master' into array_filters
2 parents c6559ed + a49b094 commit ca5d4fa

File tree

6 files changed

+105
-5
lines changed

6 files changed

+105
-5
lines changed

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Development
99
- (Fill this out as you fix issues and develop your features).
1010
- Fix for uuidRepresentation not read when provided in URI #2741
1111
- Add option to user array_filters https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/
12+
- Fix combination of __raw__ and mongoengine syntax #2773
1213
- Add tests against MongoDB 6.0 and MongoDB 7.0 in the pipeline
1314
- Fix validate() not being called when inheritance is used in EmbeddedDocument and validate is overriden #2784
1415
- Add support for readPreferenceTags in connection parameters #2644
@@ -17,6 +18,7 @@ Development
1718
- BREAKING CHANGE: no_dereference context manager no longer returns the class in __enter__ #2788
1819
as it was useless and making it look like it was returning a different class although it was the same.
1920
Thus, it must be called like `with no_dereference(User):` and no longer `with no_dereference(User) as ...:`
21+
- Added __raw__ to :meth:`~mongoengine.Queryset.order_by()` to allow to provide raw pymongo 'sort' argument and get around some of the limitations #2783
2022

2123
Changes in 0.27.0
2224
=================

docs/guide/querying.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ There are several different "modifiers" that you may use with these methods:
605605
* ``add_to_set`` -- add value to a list only if its not in the list already
606606
* ``rename`` -- rename the key name
607607

608+
.. _need to add upsert=True: http://docs.mongodb.org/manual/reference/operator/update/setOnInsert
608609
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
609610

610611
The syntax for atomic updates is similar to the querying syntax, but the

mongoengine/context_managers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"query_counter",
1717
"set_write_concern",
1818
"set_read_write_concern",
19+
"no_dereferencing_active_for_class",
1920
)
2021

2122

mongoengine/queryset/base.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ def all_fields(self):
11191119
)
11201120
return queryset
11211121

1122-
def order_by(self, *keys):
1122+
def order_by(self, *keys, __raw__=None):
11231123
"""Order the :class:`~mongoengine.queryset.QuerySet` by the given keys.
11241124
11251125
The order may be specified by prepending each of the keys by a "+" or
@@ -1129,11 +1129,19 @@ def order_by(self, *keys):
11291129
11301130
:param keys: fields to order the query results by; keys may be
11311131
prefixed with "+" or a "-" to determine the ordering direction.
1132+
:param __raw__: a raw pymongo "sort" argument (provided as a list of (key, direction))
1133+
see 'key_or_list' in `pymongo.cursor.Cursor.sort doc <https://pymongo.readthedocs.io/en/stable/api/pymongo/cursor.html#pymongo.cursor.Cursor.sort>`.
1134+
If both keys and __raw__ are provided, an exception is raised
11321135
"""
1133-
queryset = self.clone()
1136+
if __raw__ and keys:
1137+
raise OperationError("Can not use both keys and __raw__ with order_by() ")
11341138

1139+
queryset = self.clone()
11351140
old_ordering = queryset._ordering
1136-
new_ordering = queryset._get_order_by(keys)
1141+
if __raw__:
1142+
new_ordering = __raw__
1143+
else:
1144+
new_ordering = queryset._get_order_by(keys)
11371145

11381146
if queryset._cursor_obj:
11391147
# If a cursor object has already been created, apply the sort to it

mongoengine/queryset/transform.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,23 @@
6262
)
6363

6464

65+
def handle_raw_query(value, mongo_query):
66+
"""Combine a raw query with an existing one"""
67+
for op, v in value.items():
68+
if op not in mongo_query:
69+
mongo_query[op] = v
70+
elif op in mongo_query and isinstance(mongo_query[op], dict):
71+
mongo_query[op].update(v)
72+
73+
6574
# TODO make this less complex
6675
def query(_doc_cls=None, **kwargs):
6776
"""Transform a query from Django-style format to Mongo format."""
6877
mongo_query = {}
6978
merge_query = defaultdict(list)
7079
for key, value in sorted(kwargs.items()):
7180
if key == "__raw__":
72-
mongo_query.update(value)
81+
handle_raw_query(value, mongo_query)
7382
continue
7483

7584
parts = key.rsplit("__")
@@ -234,7 +243,7 @@ def update(_doc_cls=None, **update):
234243

235244
for key, value in update.items():
236245
if key == "__raw__":
237-
mongo_update.update(value)
246+
handle_raw_query(value, mongo_update)
238247
continue
239248

240249
parts = key.split("__")

tests/queryset/test_queryset.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
MONGODB_36,
1818
get_mongodb_version,
1919
)
20+
from mongoengine.pymongo_support import PYMONGO_VERSION
2021
from mongoengine.queryset import (
2122
DoesNotExist,
2223
MultipleObjectsReturned,
@@ -2328,6 +2329,46 @@ class BlogPost(Document):
23282329
post.reload()
23292330
assert post.slug == "When test test it"
23302331

2332+
def test_combination_of_mongoengine_and__raw__(self):
2333+
"""Ensure that the '__raw__' update/query works in combination with mongoengine syntax correctly."""
2334+
2335+
class BlogPost(Document):
2336+
slug = StringField()
2337+
foo = StringField()
2338+
tags = ListField(StringField())
2339+
2340+
BlogPost.drop_collection()
2341+
2342+
post = BlogPost(slug="test", foo="bar")
2343+
post.save()
2344+
2345+
BlogPost.objects(slug="test").update(
2346+
foo="baz",
2347+
__raw__={"$set": {"slug": "test test"}},
2348+
)
2349+
post.reload()
2350+
assert post.slug == "test test"
2351+
assert post.foo == "baz"
2352+
2353+
assert BlogPost.objects(foo="baz", __raw__={"slug": "test test"}).count() == 1
2354+
assert (
2355+
BlogPost.objects(foo__ne="bar", __raw__={"slug": {"$ne": "test"}}).count()
2356+
== 1
2357+
)
2358+
assert (
2359+
BlogPost.objects(foo="baz", __raw__={"slug": {"$ne": "test test"}}).count()
2360+
== 0
2361+
)
2362+
assert (
2363+
BlogPost.objects(foo__ne="baz", __raw__={"slug": "test test"}).count() == 0
2364+
)
2365+
assert (
2366+
BlogPost.objects(
2367+
foo__ne="baz", __raw__={"slug": {"$ne": "test test"}}
2368+
).count()
2369+
== 0
2370+
)
2371+
23312372
def test_add_to_set_each(self):
23322373
class Item(Document):
23332374
name = StringField(required=True)
@@ -2739,6 +2780,44 @@ def test_order_by_chaining(self):
27392780
ages = [p.age for p in qs]
27402781
assert ages == [40, 30, 20]
27412782

2783+
def test_order_by_using_raw(self):
2784+
person_a = self.Person(name="User A", age=20)
2785+
person_a.save()
2786+
person_b = self.Person(name="User B", age=30)
2787+
person_b.save()
2788+
person_c = self.Person(name="User B", age=25)
2789+
person_c.save()
2790+
person_d = self.Person(name="User C", age=40)
2791+
person_d.save()
2792+
2793+
qs = self.Person.objects.order_by(__raw__=[("name", pymongo.DESCENDING)])
2794+
assert qs._ordering == [("name", pymongo.DESCENDING)]
2795+
names = [p.name for p in qs]
2796+
assert names == ["User C", "User B", "User B", "User A"]
2797+
2798+
names = [
2799+
(p.name, p.age)
2800+
for p in self.Person.objects.order_by(__raw__=[("name", pymongo.ASCENDING)])
2801+
]
2802+
assert names == [("User A", 20), ("User B", 30), ("User B", 25), ("User C", 40)]
2803+
2804+
if PYMONGO_VERSION >= (4, 4):
2805+
# Pymongo >= 4.4 allow to mix single key with tuples inside the list
2806+
qs = self.Person.objects.order_by(
2807+
__raw__=["name", ("age", pymongo.ASCENDING)]
2808+
)
2809+
names = [(p.name, p.age) for p in qs]
2810+
assert names == [
2811+
("User A", 20),
2812+
("User B", 25),
2813+
("User B", 30),
2814+
("User C", 40),
2815+
]
2816+
2817+
def test_order_by_using_raw_and_keys_raises_exception(self):
2818+
with pytest.raises(OperationError):
2819+
self.Person.objects.order_by("-name", __raw__=[("age", pymongo.ASCENDING)])
2820+
27422821
def test_confirm_order_by_reference_wont_work(self):
27432822
"""Ordering by reference is not possible. Use map / reduce.. or
27442823
denormalise"""

0 commit comments

Comments
 (0)