Skip to content

Commit dda51bd

Browse files
authored
Merge pull request #2769 from idoshr/array_filters
support for array_filters
2 parents a49b094 + 70636a2 commit dda51bd

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

docs/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Development
88
===========
99
- (Fill this out as you fix issues and develop your features).
1010
- Fix for uuidRepresentation not read when provided in URI #2741
11+
- Add option to user array_filters https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/ #2769
1112
- 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

docs/guide/querying.rst

+17
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,23 @@ and provide the pipeline as a list
252252

253253
.. versionadded:: 0.23.2
254254

255+
Update with Array Operator
256+
--------------------------------
257+
It is possible to update specific value in array by use array_filters (arrayFilters) operator.
258+
This is done by using ``__raw__`` keyword argument to the update method and provide the arrayFilters as a list.
259+
260+
`Update with Array Operator <https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered->`_
261+
::
262+
263+
# assuming an initial 'tags' field == ['test1', 'test2', 'test3']
264+
Page.objects().update(__raw__={'$set': {"tags.$[element]": 'test11111'}},
265+
array_filters=[{"element": {'$eq': 'test2'}}],
266+
267+
# updated 'tags' field == ['test1', 'test11111', 'test3']
268+
269+
)
270+
271+
255272
Sorting/Ordering results
256273
========================
257274
It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`.

mongoengine/queryset/base.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ def update(
531531
write_concern=None,
532532
read_concern=None,
533533
full_result=False,
534+
array_filters=None,
534535
**update,
535536
):
536537
"""Perform an atomic update on the fields matched by the query.
@@ -546,6 +547,7 @@ def update(
546547
:param read_concern: Override the read concern for the operation
547548
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
548549
updated items
550+
:param array_filters: A list of filters specifying which array elements an update should apply.
549551
:param update: Django-style update keyword arguments
550552
551553
:returns the number of updated documents (unless ``full_result`` is True)
@@ -560,7 +562,9 @@ def update(
560562

561563
queryset = self.clone()
562564
query = queryset._query
563-
if "__raw__" in update and isinstance(update["__raw__"], list):
565+
if "__raw__" in update and isinstance(
566+
update["__raw__"], list
567+
): # Case of Update with Aggregation Pipeline
564568
update = [
565569
transform.update(queryset._document, **{"__raw__": u})
566570
for u in update["__raw__"]
@@ -581,7 +585,9 @@ def update(
581585
update_func = collection.update_one
582586
if multi:
583587
update_func = collection.update_many
584-
result = update_func(query, update, upsert=upsert)
588+
result = update_func(
589+
query, update, upsert=upsert, array_filters=array_filters
590+
)
585591
if full_result:
586592
return result
587593
elif result.raw_result:

tests/queryset/test_queryset.py

+59
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,65 @@ class Blog(Document):
592592

593593
Blog.drop_collection()
594594

595+
def test_update_array_filters(self):
596+
"""Ensure that updating by array_filters works."""
597+
598+
class Comment(EmbeddedDocument):
599+
comment_tags = ListField(StringField())
600+
601+
class Blog(Document):
602+
tags = ListField(StringField())
603+
comments = EmbeddedDocumentField(Comment)
604+
605+
Blog.drop_collection()
606+
607+
# update one
608+
Blog.objects.create(tags=["test1", "test2", "test3"])
609+
610+
Blog.objects().update_one(
611+
__raw__={"$set": {"tags.$[element]": "test11111"}},
612+
array_filters=[{"element": {"$eq": "test2"}}],
613+
)
614+
testc_blogs = Blog.objects(tags="test11111")
615+
616+
assert testc_blogs.count() == 1
617+
618+
Blog.drop_collection()
619+
620+
# update one inner list
621+
comments = Comment(comment_tags=["test1", "test2", "test3"])
622+
Blog.objects.create(comments=comments)
623+
624+
Blog.objects().update_one(
625+
__raw__={"$set": {"comments.comment_tags.$[element]": "test11111"}},
626+
array_filters=[{"element": {"$eq": "test2"}}],
627+
)
628+
testc_blogs = Blog.objects(comments__comment_tags="test11111")
629+
630+
assert testc_blogs.count() == 1
631+
632+
# update many
633+
Blog.drop_collection()
634+
635+
Blog.objects.create(tags=["test1", "test2", "test3", "test_all"])
636+
Blog.objects.create(tags=["test4", "test5", "test6", "test_all"])
637+
638+
Blog.objects().update(
639+
__raw__={"$set": {"tags.$[element]": "test11111"}},
640+
array_filters=[{"element": {"$eq": "test2"}}],
641+
)
642+
testc_blogs = Blog.objects(tags="test11111")
643+
644+
assert testc_blogs.count() == 1
645+
646+
Blog.objects().update(
647+
__raw__={"$set": {"tags.$[element]": "test_all1234577"}},
648+
array_filters=[{"element": {"$eq": "test_all"}}],
649+
)
650+
testc_blogs = Blog.objects(tags="test_all1234577")
651+
652+
assert testc_blogs.count() == 2
653+
595654
def test_update_using_positional_operator(self):
596655
"""Ensure that the list fields can be updated using the positional
597656
operator."""

0 commit comments

Comments
 (0)