Query MongoDB Documents and Collections

Querying data is done through the Object-Document Mapping (ODM), which allows you to interact with MongoDB using Python code instead of pymongo query documents. At the core of querying are QuerySets, which represent a lazy collection of database objects that can be filtered, ordered, and manipulated. A QuerySet does not hit the database immediately; instead, it is evaluated only when the data is actually needed. This lazy behavior allows QuerySets to be efficiently reused and combined. QuerySets form the foundation for all read operations.

The most common methods to retrieve data are all(), get(), and filter(). Both all() and filter() return a new QuerySet. The filter() method, which returns a new QuerySet containing objects that match given conditions. Filters are non-destructive, meaning they always return a new QuerySet rather than modifying the original one. Djongo supports chaining filters, allowing multiple conditions to be applied sequentially in a clear and readable way. Each chained filter further narrows the result set.

Use the double-underscore (__) to define more advanced queries and field lookups. This syntax separates a field name from a lookup type, such as exact, icontains, gt, or lte. These field lookups determine how comparisons are made, whether for exact matches, partial text searches, or numerical ranges. If no lookup is specified, the query defaults to an exact match. The double-underscore syntax also enables complex queries without writing complex query documents.

QuerySets can be limited using slicing, such as [:10], which translates to aggregation $limit operators. This allows developers to efficiently control how many records are retrieved from the database. Ordering results before limiting them ensures predictable output. Limiting QuerySets is especially useful for performance optimization. Because QuerySets are lazy, slicing does not immediately execute the query until needed.

You can create lookups that span relationships, making it easy to query across a foreign key and many-to-many relationships. By chaining related field names with double underscores, you can filter based on attributes of related models. For example, querying posts by an author’s username requires no joins written by hand. Djongo automatically generates the necessary joins behind the scenes. This feature greatly simplifies working with relational data.

Accessing related objects is another feature where related objects can be accessed using attributes, such as reverse relations. Methods like select_related() and prefetch_related() help optimize database access when working with related data. These tools reduce the number of queries needed and improve application performance.

Query documents

Define the models in models.py to setup the relevant collections in the database.

models.py

from djongo import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    content = models.TextField()

    class Meta:
        abstract = True

class Author(models.Model):
    name = models.CharField(max_length=100)
    verified = models.BooleanField()

    class Meta:
        abstract = True

class Entry(models.Model):
    _id = models.ObjectIdField()
    headline = models.CharField(max_length=255)
    blog = models.EmbeddedField(
        model_container=Blog
    )
    authors = models.ArrayField(model_container=Author)
copy code

Djongo contains several methods for quering the database.

Select all documents in a Collection

To retrieve all documents in a collection use the all() method. It returns a QuerySet which does not hit the database until iterated over.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.all()
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(projection=['_id', 'headline', 'blog', 'authors'])
copy code

More information can be found in the Django reference documentation

Select a unique document from a Collection

To retrieve a single unique document from the collection use get(). The method takes kwargs that can be used to specify equality conditions. The lhs of the kwarg specifies the document field name, while the rhs part specifies the value the field name must match. The method directly hits the database when called and does not need to be iterated over.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.get(headline="The Headline")
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'headline': {'$eq': 'The Headline'}},
                       limit=21,
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

If there are no results that match the query, get() will raise a DoesNotExist exception. If more than one item matches the query, get() will raise a MultipleObjectsReturned exception. More information can be found in the Django reference documentation.

Select a subset of documents from a Collection

A subset of documents is selected by using filter() and specifying the query conditions.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(headline="The Headline")
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'headline': {'$eq': 'The Headline'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify equality condition

To specify equality conditions, pass them as keyword arguments to either the filter() or get() methods. Kwargs are specified with the expression: field=value. Multiple kwargs can be provided at the same time.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(headline="The Headline", blog={'name': 'James'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'$and': [{'blog.name': {'$eq': 'James'}}, {'headline': {'$eq': 'The Headline'}}]},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify AND conditions

By default, all kwargs provided to filter() and get() are considered as AND conditions. No explict syntax is needed for pure AND queries.

main.py

from django.core.management.base import BaseCommand
from models import Entry
from bson import ObjectId

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.get(headline="The Headline",
                                  _id=ObjectId('61d2d6c07d26e98969f8b8c2'))
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'$and': [{'_id': {'$eq': ObjectId('61d2d6c07d26e98969f8b8c2')}},
                                        {'headline': {'$eq': 'The Headline'}}]},
                       limit=21,
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify OR conditions

OR conditions are specified in Djongo by using Q objects

main.py

from django.core.management.base import BaseCommand
from models import Entry
from django.db.models import Q

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(
            Q(headline="The Headline") | Q(blog={'name': 'James'})
        )
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'$or': [{'headline': {'$eq': 'The Headline'}},
                                       {'blog.name': {'$eq': 'James'}}]},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify AND as well as OR conditions

A combination of AND and OR conditions is specified by using Q objects again.

main.py

from django.core.management.base import BaseCommand
from models import Entry
from django.db.models import Q

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(
            (Q(headline="The Headline") | Q(blog={'name': 'James'})) & Q(authors={'0.name': 'James'})
        )
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'$and': [
                                      {'$or': [
                                          {'headline': {'$eq': 'The Headline'}},
                                          {'blog.name': {'$eq': 'James'}}
                                      ]},
                                      {'authors.0.name': {'$eq': 'James'}}
                                ]},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify conditions using query predicates

Djongo supports several MongoDB query predicates through field lookups. Use the double-underscore (__) to specify the field lookup after the field name, <field_name>__<field_lookup>.

The following lookups are supported exact, iexact, contains, icontains, gte, lte, in, and range.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(headline__in=['Breaking News', 'The Headline'])
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'headline': {'$in': ['Breaking News', 'The Headline']}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Evaluating QuerySets

A QuerySet executes when it’s evaluated — when you iterate over it, convert it to a list, access a slice, or call count(), exists(), first(), or last().

The results are cached after the first evaluation, so repeated iterations don’t hit the database again unless you call all() to refresh. This lazy and cached behavior helps minimize database traffic and improve performance.

You can learn more about making queries here.

qs = Entry.objects.filter(published_date__year=2024)
print(list(qs))  # Executes the query
print(list(qs))  # Uses the cached results, no new query
copy code

Specifying Query Options

Djongo lets you specify the configuration of the find command into your QuerySets. Call the configure method on a QuerySet to configure the find query. All options supported by aggregate or find can be included as kwargs. Example of valid arguments:

Arguments

ArgumentTypeDescription
allowDiskUsebooleanEnables writing to temporary files
collationCollationUsed to specify the collation. Takes an instance of Collation

Example

Blog.objects.filter(name='John Lennon').configure(hint=['-tagline'])
copy code

This generates the following pymongo find query:

db.blog.find({'name': 'John Lennon'}, hint=[('tagline', pymongo.DESCENDING)])
copy code

Embedded documents

A Nested field is one that lies inside an embedded document. Querying nested fields is done by specifying the field names as keys of a dict. Next, the value you want the nested field to match, is specified as the value of the dict.

Alternatively, the nested field you want to query can also be specified using the dot notation.

Query nested fields

To query all Blog with the nested field name equal to Beatles, run the following query:

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(blog={'name': 'Beatles'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'blog.name': {'$eq': 'Beatles'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify AND conditions

To query all Blog with the nested field name equal to "Beatles" and content equal to "The Story" run the following query:

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(blog={'name': 'Beatles', 'content': 'The Story'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'blog.name': {'$eq': 'James'}, 'blog.content': {'$eq': 'The Story'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Query nested fields (with Dot Notation)

Deeply nested fields can be queried using the dot notation.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(blog={'tagline.subtitle': 'Beatles reunion tour'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'blog.tagline.subtitle': {'$eq': 'Beatles reunion tour'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Match an embedded/nested document

You can match all fields of an embedded document by simply specifying it.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(blog={'name': 'Beatles', 'content': 'The Story'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'blog.name': {'$eq': 'James'}, 'blog.content': {'$eq': 'The Story'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Arrays

Djongo uses a mixture of Django query syntax and MongoDB query syntax. Consider a query to retrieve all entries made by the author Paul. Using ManyToManyField this requires 2 SQL queries. First selects the id for author Paul from the author table. Next, a JOIN with entry_authors and entry gives the corresponding entries.

With ArrayField the query reduces to a single query with no joins:

Match an Array

To specify equality condition on an array, specify the field name as the lhs of the kwarg. The array value to match against, including the order of the elements, forms the rhs of the kwarg.

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(authors=[
                { "name": "James", "verified": True },
                { "name": "Guest", "verified": False }
              ]
        )
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'authors': {'$eq': [
                            {'name': 'James', 'verified': True}, {'name': 'Guest', 'verified': False}
                        ]}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Query an Array for an element

Djongo lets you get even more specific with your queries. To query all entries where the second author is Guest:

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(authors={'1.name': 'Guest'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'authors.1.name': {'$eq': 'Guest'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

In MongoDB the first element in the array starts at index 0. Djongo follows the same system.

Arrays of Embedded documents

Query for a document nested in an Array

Djongo lets you query for a specific element in an Array. All documents that contain the specific array element are then returned

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(authors={ "name": "James", "verified": True })
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'authors.name': {'$eq': 'James'}, 'authors.verified': {'$eq': True}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Specify a query condition on a field in an Array of documents

Djongo lets you specify query operators on elements of an Array

main.py

from django.core.management.base import BaseCommand
from models import Entry

class Command(BaseCommand):
    help = "Main execution file"

    def handle(self, *args, **options):
        entry = Entry.objects.filter(authors__lt={'name': 'James'})
        print(list(entry))
copy code

The following pymongo commands are generated:

db["myapp_entry"].find(filter={'authors.name': {'$lt': 'James'}},
                       projection=['_id', 'headline', 'blog', 'authors'])
copy code

Query an Array

Djongo uses a mixture of Django query syntax and MongoDB query syntax. Consider a query to retrieve all entries made by the author Paul. Using ManyToManyField this requires 2 SQL queries. First selects the id for author Paul from the author table. Next, a JOIN with entry_authors and entry gives the corresponding entries.

With ArrayField the query reduces to a single simple query:

entries = Entry.objects.filter(authors={'name': 'Paul'})
copy code

Djongo lets you get even more specific with your queries. To query all entries where the third author is Paul:

entries = Entry.objects.filter(authors={'2.name': 'Paul'})
copy code

Note: In MongoDB the first element in the array starts at index 0.

Aggregation

Chaining Filters and Field Lookups

You can chain filters to build complex queries. Each filter call returns a new QuerySet, allowing you to progressively narrow down results.

QuerySets remain lazy until evaluated, so chaining does not hit the database immediately. This pattern allows for clean, readable, composable queries.

Ordering, Slicing, and Limiting

You can order results using order_by() and limit them using Python slicing syntax. Ordering accepts one or more field names, with a - prefix for descending order. Slicing translates to SQL/MongoDB LIMIT and OFFSET.

You can also use first() and last() to get a single record or None if none exist. These tools let you fetch only the portion of data you need efficiently.

latest_five = Entry.objects.order_by('-published_date')[:5]
first_post = Entry.objects.order_by('published_date').first()
copy code

Django ORM (and therefore Djongo) supports aggregations with functions like Count, Sum, Avg, Min, and Max. Use aggregate() to return a single summary value, or annotate() to add computed values to each result row.

You can traverse related fields using double underscores (following ForeignKey, OneToOneField, or ManyToManyField). This lets you perform joins and calculations without writing raw database queries.

from djongo.models import Count

authors = Author.objects.annotate(num_posts=Count('post'))
for a in authors:
    print(a.name, a.num_posts)
copy code

Djongo lets you run MongoDB text search queries on Django CharField and TextField. To run a text search, use the text_search operator that comes built in with Djongo.

from djongo.models.indexes import TextIndex
from djongo import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    class Meta:
        indexes = [
            TextIndex(fields=['name'])
        ]
copy code
Blog.objects.filter(name__text_search='Paul Lennon')
copy code

This will generate the pymongo command:

db.blog.find( { '$text': { '$search': "Paul Lennon" } } )
copy code

Geospatial Queries

Geospatial queries are carried out in Djongo by using a combination of the near lookup operator and the Near search object.

class Near:
    def __init__(self,
                 type=None,
                 coordinates=None,
                 minDistance=None,
                 maxDistance=None):
copy code

Example

from djongo.models.indexes import TwoDSphereIndex
from djongo import models

class Location(models.Model):
    type = models.CharField(max_length=100)
    coordinates = models.ArrayField()

    class Meta:
        abstract = True

class Entry(models.Model):
    loc = models.EmbeddedField(
        model_container=Location,
    )
    class Meta:
        indexes = [
            TwoDSphereIndex(fields=['loc'])
        ]
copy code
from djongo.models import Near

search_region = Near(
    type='point',
    coordinates=[-33.9, 89.81],
    minDistance=100,
    maxDistance=200
)

Entry.objects.filter(loc__near=search_region)
copy code

This generates the following pymongo search query:

db.entry.find({
     'loc': 
        { '$near':
          {
            '$geometry': { 'type': "Point",  'coordinates': [-33.9, 89.81] },
            '$minDistance': 100,
            '$maxDistance': 200
          }
        }
   })
copy code

Tailable Cursors

Tailable cursors are used to retrieve data from capped collections. The querySet first has to be configured using configure to use a tailable cursor in the pymongo find command. Results of the querySet can only be accessed by generating an iterator by calling the QuerySet iterator

Example

iterable = Blog.objects.filter(name='John').configure(cursor_type=CursorType.TAILABLE).iterator()
for blog in iterable:
    blog.name
copy code