Skip to content

Commit

Permalink
add contained_by lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
timgraham committed Jan 7, 2025
1 parent 039af42 commit 6cf9766
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 0 deletions.
10 changes: 10 additions & 0 deletions django_mongodb/fields/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ def as_mql(self, compiler, connection):
return {"$and": [{"$ne": [lhs_mql, None]}, {"$setIsSubset": [value, lhs_mql]}]}


@ArrayField.register_lookup
class ArrayContainedBy(ArrayRHSMixin, FieldGetDbPrepValueMixin, Lookup):
lookup_name = "contained_by"

def as_mql(self, compiler, connection):
lhs_mql = process_lhs(self, compiler, connection)
value = process_rhs(self, compiler, connection)
return {"$and": [{"$ne": [lhs_mql, None]}, {"$setIsSubset": [lhs_mql, value]}]}


@ArrayField.register_lookup
class ArrayExact(ArrayRHSMixin, Exact):
pass
Expand Down
19 changes: 19 additions & 0 deletions docs/source/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,25 @@ data. It uses the ``$setIntersection`` operator. For example:
>>> Post.objects.filter(tags__contains=["django", "thoughts"])
<QuerySet [<Post: First post>]>
``contained_by``
~~~~~~~~~~~~~~~~

This is the inverse of the :lookup:`contains <arrayfield.contains>` lookup -
the objects returned will be those where the data is a subset of the values
passed. It uses the ``$setIntersection`` operator. For example:

.. code-block:: pycon
>>> Post.objects.create(name="First post", tags=["thoughts", "django"])
>>> Post.objects.create(name="Second post", tags=["thoughts"])
>>> Post.objects.create(name="Third post", tags=["tutorial", "django"])
>>> Post.objects.filter(tags__contained_by=["thoughts", "django"])
<QuerySet [<Post: First post>, <Post: Second post>]>
>>> Post.objects.filter(tags__contained_by=["thoughts", "django", "tutorial"])
<QuerySet [<Post: First post>, <Post: Second post>, <Post: Third post>]>
.. fieldlookup:: arrayfield.overlap

``overlap``
Expand Down
15 changes: 15 additions & 0 deletions tests/model_fields_/test_arrayfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,18 @@ def test_in_as_F_object(self):
self.objs,
)

def test_contained_by(self):
self.assertSequenceEqual(
NullableIntegerArrayModel.objects.filter(field__contained_by=[1, 2]),
self.objs[:2],
)

def test_contained_by_including_F_object(self):
self.assertSequenceEqual(
NullableIntegerArrayModel.objects.filter(field__contained_by=[models.F("order"), 2]),
self.objs[:3],
)

def test_contains(self):
self.assertSequenceEqual(
NullableIntegerArrayModel.objects.filter(field__contains=[2]),
Expand Down Expand Up @@ -370,6 +382,9 @@ def test_icontains(self):
def test_contains_charfield(self):
self.assertSequenceEqual(CharArrayModel.objects.filter(field__contains=["text"]), [])

def test_contained_by_charfield(self):
self.assertSequenceEqual(CharArrayModel.objects.filter(field__contained_by=["text"]), [])

def test_overlap_charfield(self):
self.assertSequenceEqual(CharArrayModel.objects.filter(field__overlap=["text"]), [])

Expand Down

0 comments on commit 6cf9766

Please sign in to comment.