Filtering Operators
MongoEngine translates Python keyword syntax into MongoDB query operators: field__op=value becomes {"field": {"$op": value}}. The double-underscore form keeps filters readable in code and composable across fields, and the full operator set covers comparison, membership, string matching, and field-existence tests. Combining filters uses Q objects with | (OR) and & (AND), and ~ negates an entire queryset.
The Operator Map
| Kwarg | Mongo operator | Meaning |
|---|---|---|
field=value | - | exact match (equality) |
field__gt / __gte | $gt / $gte | greater than (or equal) |
field__lt / __lte | $lt / $lte | less than (or equal) |
field__ne | $ne | not equal |
field__in / __nin | $in / $nin | membership / non-membership |
field__contains / __icontains | regex | substring, case (in)sensitive |
field__startswith / __istartswith | regex | prefix match |
field__endswith / __iendswith | regex | |
field__exists | $exists | key present (True) or absent (False) |
field__size | $size | list length |
field__elemmatch | $elemMatch | list element matching sub-conditions |
from mongoengine import Document, StringField, IntField, ListField
class User(Document):
username = StringField()
age = IntField()
roles = ListField(StringField())
User.objects(age__gte=18, age__lt=65) # a range, both bounds
User.objects(roles__in=["admin", "editor"]) # has any of these roles
User.objects(roles__size=3) # exactly three roles
User.objects(username__istartswith="jo") # case-insensitive prefix
User.objects(deleted_at__exists=False) # not soft-deleted
Multiple kwargs are ANDed automatically: objects(a=1, b=2) matches documents where both hold. That is 90% of application querying.
OR Logic with Q Objects
from mongoengine import Q
# OR is not expressible as kwargs - use Q
User.objects(Q(age__lt=18) | Q(roles="admin"))
# complex nesting
User.objects(
(Q(active=True) | Q(trial_ends__gt=datetime.now()))
& Q(roles__nin=["banned"])
)
# negation of a whole queryset: "not (active is False)"
User.objects(~User.objects(active=False).filter(roles__in=["x"]))
active_or_trial = ~User.objects(active=False, trial_ends__lt=datetime.now())
The pattern to internalize: Q for OR and nesting, kwargs for AND, and ~ (inverted querysets) for "everything except". Chaining .filter() after a ~-queryset adds more AND conditions on top of the negation.
Filtering Into Lists and Subdocuments
class Post(Document):
tags = ListField(StringField())
author = EmbeddedDocumentField(Author)
Post.objects(tags="python") # any element equals
Post.objects(tags__all=["python", "web"]) # contains all listed
Post.objects(tags__elemmatch={"$gt": 0}) # custom sub-conditions
Post.objects(author__name="ada") # dot-path into embedded docs
Equality against a list field matches any element (MongoDB semantics); __all and __elemmatch tighten it. Dot-path filters (author__name) reach into embedded documents - the embedded-documents chapter builds on this.
Regex Escaping
__contains and friends build a regex from your value, and MongoEngine escapes regex metacharacters in the input - so searching for "c++" matches literally, not as a pattern. When you want real regex power, pass a re.compile(...) object to a plain field= kwarg. The escape behavior is a feature: user-supplied search terms go through __icontains and cannot inject regex syntax.
Summary
Write filters as field__op kwargs (ANDed automatically), reach for Q objects the moment you need OR or nesting, use ~ to invert, and remember list-field equality means "any element". The operator map is short and stable - once __gte/__in/__icontains/__exists are habit, the filtering chapter of MongoEngine is effectively done, and the same conditions feed indexes and aggregation pipelines unchanged.