Get Started

Djongo is a MongoDB object document mapper built on top of the Django Web Framework. It maps a MongoDB Collection to a python class and Documents to class instances. It provides query syntax for generating long and complex query documents.

Install

  • Install the latest version of djongo from:
  pip install --extra-index-url https://pypi.djongomapper.com/latest-updates/ djongo
copy code

Prerequisites

  • You have a DjongoCS account.
  • Your access tokens have been successfully setup.
  • Alternatively, you can install an older version directly from pypi:
  pip install djongo
copy code
  • Or, from github:
pip install git+https://github.com/doableware/djongo.git
copy code

Setup

While Djongo is built on top of the Django Web Framework, not all features of the framework are needed for manipulating MongoDB data. Only the object modeling part of the framework is used for modeling MongoDB data.

Creating a project

You will need to auto-generate some code that establishes a project. cd into a directory where you would like to store your code and run the following command to bootstrap a new project:

django-admin startproject mysite myproject
copy code

startproject creates the following files and folders:

myproject/
| -- manage.py
| -- mysite/
|   -- __init__.py
|   -- settings.py
|   -- urls.py
|   -- asgi.py
|   -- wsgi.py
copy code

Delete the files urls.py, asgi.py, and wsgi.py as they are not needed for MongoDB data manipulation. Next, make sure you’re in the same directory as manage.py and execute this command:

python manage.py startapp myapp
copy code

startapp creates the following files and folders:

myapp/
| -- __init__.py
| -- admin.py
| -- apps.py
| -- migrations/
|   -- __init__.py
| -- models.py
| -- tests.py
| -- views.py
copy code

Again, the files views.py, migrations and admin.py are not needed and can be deleted. Finally, edit settings.py file of your project, to look like this:

settings.py

DATABASES = {
  'default': {
    'ENGINE': 'djongo',
    'NAME': 'your-db-name',
  }
}

INSTALLED_APPS = [
  "myapp.apps.MyappAppConfig",
]
copy code

The installation and setup of Djongo is now complete.

Models

A Model is the single, canonical source of information about the data your application stores. It is defined as a Python class that subclasses djongo.models.Model. Each attribute of the class represents a MongoDB document field.

Each model maps to one MongoDB collection and encapsulates the structure and behavior of that collection in one place. Because models are plain Python classes, you can add helper methods and properties to encapsulate domain logic close to the data they operate on.

Defining models in code makes your schema explicit, reviewable, and easy to test. The topic guide covers more information on models.

models.py

from djongo import models

class Entry(models.Model):
    _id = models.ObjectIdField()
    headline = models.CharField(max_length=255)    
copy code

Fields

Fields are the building blocks of models. Djongo supports all of Django field types, for example CharField, TextField, IntegerField, BooleanField, and DateTimeField. Each field accepts options such as max_length, blank, null, default, and choices to control validation and storage behavior.

Models also express relationships at the schema level using ForeignKey, OneToOneField, and ManyToManyField. The inner Meta class lets you provide model-wide configuration such as verbose_name, custom database table names, and ordering preferences.

Djongo adds extra fields for supporting MongoDB features like embedded documents.

EmbeddedField

The EmbeddedField lets you embed a document inside a document. Use it for creating MongoDB embedded documents. Nest a dict inside a model with the EmbeddedField. The model_container is used to describe the structure of the data stored.

models.py

from djongo import models

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

    class Meta:
        abstract = True

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

The topic guide covers more information on embedded fields here.

ArrayField

The ArrayField lets you store a list of embedded documents inside a document.

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

The topic guide covers more information on array fields here.

ArrayReferenceField

The ArrayReferenceField is similar to the ManyToManyField. It is used to let a MongoDB document refer to multiple documents in another collection. The field stores reference ids of other documents inside an array.

models.py

class Author(models.Model):
    _id = models.ObjectIdField()
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    _id = models.ObjectIdField()
    headline = models.CharField(max_length=255)
    blog = models.EmbeddedField(
        model_container=Blog
    )
    authors = models.ArrayReferenceField(
        to=Author,
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.headline
copy code

The topic guide covers more information on ArrayReferenceField.

Execution Environment

Test Environment

Executing code that manipulates MongoDB needs to be done inside an environment. When startapp was run, the test.py environment was automatically generated. Inserting or querying documents in MongoDB can be done from inside test.py.

tests.py

from django.test import TestCase

class TestMain(TestCase):

    def test_main(self):
        pass
copy code

Next run the command below to execute the script

python manage.py test tests
copy code

Command Environment

Django comes with a command environment. Under myapp add the management and commands folders, followed the main.py script.

myapp/
| -- __init__.py
| -- apps.py
| -- models.py
| -- tests.py
| -- management/
|    -- __init__.py
|    -- commands/
|       -- __init__.py
|       -- main.py
copy code

The main.py module has only one requirement – it must define a class Command that extends BaseCommand.

main.py

from django.core.management.base import BaseCommand

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

    def handle(self, *args, **options):
        self.stdout.write(self.style.SUCCESS('Main executed'))
copy code

Script execution is done by running this command:

python manage.py main
copy code

Creating Documents

Documents are creating using the objects.create() method. Open main.py and add

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.objects.create(
            headline='headline',
            blog={ 'name': 'Paul', 'content': 'How to sing' },
            authors=[
                { 'name': 'James', 'verified': True },
                { 'name': 'Guest', 'verified': False }
            ])
        self.stdout.write(self.style.SUCCESS('Main executed'))
copy code

Next, in the command line execute:

python manage.py main
copy code

Alternatively, open tests.py and add:

tests.py

from models import Entry
from django.test import TestCase

class TestInsert(TestCase):

    def test_insert(self):
        Entry.objects.create(
            headline='headline',
            blog={ 'name': 'Paul', 'content': 'How to sing' },
            authors=[
                { 'name': 'James', 'verified': True },
                { 'name': 'Guest', 'verified': False }
            ])
copy code

Next, in the command line execute:

python manage.py test tests.TestInsert
copy code

This generates the pymongo query:

db['myapp_entry'].insert_many(
    [{'headline': 'headline',
      'blog': {'name': 'Paul', 'content': 'How to sing'},
      'authors': [{'name': 'James', 'verified': True},
                  {'name': 'Guest', 'verified': False}]}],
    ordered=False)
copy code

The topic guide covers more information on creating documents.

Query Documents

Querying data is done through the Object-Document Mapping (ODM), which allows developers to interact with MongoDB using Python code instead of 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 way to retrieve data is by using 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 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.

The topic guide covers more information on querying.

Query on Embedded Documents

Querying embedded documents is done by describing the query conditions of the embedded document as a dict.

entries = Entry.objects.filter(blog={'name': 'Beatles'})
copy code

Would create a query document like:

filter = {
    'blog.name': 'Beatles'
}
copy code

The topic guide covers more information on querying embedded fields

Query on Nested Documents

To query based on nested embedded documents, describe the query as a nested dict.

entries = Entry.objects.filter(blog={'tagline': {'subtitle': 'Artist'}})
copy code

or alternatively, use:

entries = Entry.objects.filter(blog={'tagline.subtitle': 'Artist'})
copy code

Which would create a query document like:

filter = {
    'blog.tagline.subtitle': 'Artist'
}
copy code

Query an Array of Embedded Documents

Querying on a particular element of an array of documents is done by specifying the index of the element.

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

Would create a query document like:

filter = {
    'authors.2.name': 'Paul'
}
copy code

The topic guide covers more information on querying array fields

Database Configuration

The settings.py supports (but is not limited to) the following options:

AttributeValueDescription
ENGINEdjongoThe MongoDB connection engine for interfacing with Django.
ENFORCE_SCHEMATrueEnsures that the model schema and database schema are exactly the same. Raises Migration Error in case of discrepancy.
ENFORCE_SCHEMAFalse(Default) Implicitly creates collections. Returns missing fields as None instead of raising an exception.
NAMEyour-db-nameSpecify your database name. This field cannot be left empty.
LOGGINGdictA dictConfig for the type of logging to run on djongo.
CLIENTdictA set of key-value pairs that will be passed directly to MongoClient as kwargs while creating a new client connection.

All options except ENGINE and ENFORCE_SCHEMA are the same those listed in the pymongo documentation.

    DATABASES = {
        'default': {
            'ENGINE': 'djongo',
            'NAME': 'your-db-name',
            'ENFORCE_SCHEMA': False,
            'CLIENT': {
                'host': 'host-name or ip address',
                'port': port_number,
                'username': 'db-username',
                'password': 'password',
                'authSource': 'db-name',
                'authMechanism': 'SCRAM-SHA-1'
            },
            'LOGGING': {
                'version': 1,
                'loggers': {
                    'djongo': {
                        'level': 'DEBUG',
                        'propagate': False,                        
                    }
                },
             },
        }
    }
copy code

Security and Integrity Checks

Djongo allows for checks on data fields before they are saved to the database. Running the correct integrity checks and field value validators before writing data into the database is important.

Enforce schema

MongoDB is schemaless, which means no schema rules are enforced by the database. You can add and exclude fields per entry and MongoDB will not complain. This can make life easier, especially when there are frequent changes to the data model. Take for example the Blog Model (version 1).

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

You can save several entries into the DB and later modify it to version 2:

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

The modified Model can be saved without running any migrations.

This works fine if you know what you are doing. Consider a query that retrieves entries belonging to both the 'older' model (with just 2 fields) and the current model. What will the value of description now be? To handle such scenarios Djongo comes with the ENFORCE_SCHEMA option.

When connecting to Djongo you can set ENFORCE_SCHEMA: True. In this case, a MigrationError will be raised when field values are missing from the retrieved documents. You can then check what went wrong.

ENFORCE_SCHEMA: False works by silently setting the missing fields with the value None. If your app is programmed to expect this (which means it is not a bug) you can get away by not calling any migrations.

Validators

Apply validators to each field before they are saved.

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from djongo import models
from django.core.validators import URLValidator

def script_injection(value):
    if value.find('<script>') != -1:
        raise ValidationError(_('Script injection in %(value)s'),
                              params={'value': value})

class Address(models.Model)
    city = models.CharField(max_length=50)
    homepage = models.URLField(validators=[URLValidator, script_injection])
    class Meta:
        abstract=True

class Entry(models.Model):
    _id = models.ObjectIdField()
    address = models.EmbeddedField(model_container=Address)
copy code

Integrity checks

class Entry(models.Model):
    _id = models.ObjectIdField()
    address = models.EmbeddedField(model_container=Address,
                                   null=False,
                                   blank=False)
copy code

By setting null=False, blank=False in EmbeddedField, missing values are never stored.

Djongo Manager

Djongo Manager extends the functionality of the usual Django Manager. It gives direct access to the pymongo collection API. To use this manager define your manager as DjongoManager in the model.

class Entry(models.Model):
    blog = models.EmbeddedField(
        model_container=Blog,
    )
    headline = models.CharField(max_length=255)    
    objects = models.DjongoManager()
copy code

Use it like the usual Django manager:

post = Entry.objects.get(pk=p_key)
copy code

Will get a model object having primary key p_key.

Using PyMongo Commands

MongoDB has powerful query syntax and DjongoManager lets you exploit it fully. For the above Entry model define a custom query function:

class EntryView(DetailView):

    def get_object(self, queryset=None):
        index = [i for i in Entry.objects.mongo_aggregate([
            {
                '$match': {
                    'headline': self.kwargs['path']
                }
            },
        ])]

        return index
copy code

You can directly access any pymongo command by prefixing mongo_ to the command name.

For example, to perform aggregate on the BlogPage collection (BlogPage is stored as a table in SQL or a collection in MongoDB) the function name becomes mongo_aggregate. To directly insert a document (instead of .save() a model) use mongo_insert_one()

GridFS

To save files using GridFS you must create a file storage instance of GridFSStorage:

grid_fs_storage = GridFSStorage(collection='myfiles')
copy code

In your model define your field as FileField or ImageField as usual:

avatar = models.ImageField(storage=grid_fs_storage, upload_to='')
copy code

Refer to Using GridFSStorage for more details.

DjongoCS

Djongo Cloud Server is the fastest way to deploy to the cloud your djongo powered apps. The DjongoCS package and dependencies come preconfigured and installed on a Google Cloud Platform server.

  1. Start by creating an account. You will be assigned a working webserver instance running Django and MongoDB.
  2. (Optional) Test your instance by entering https://api.djongomapper.com/<username>/ in your browser. The username is what was used while creating the account.
  3. Login to your dashboard and upload your Public SSH key. The command to open a shell to your instance will appear in the dashboard. You can upload your app specific Django scripts to the server.

Generate access token

Once logged into the dashboard click on generate token. This generates and installs a pass token needed for downloading the latest version of djongo for local development. Copy and add the generated token into the ~/.netrc file located in your home directory.

machine pypi.djongomapper.com
login <djongocs username>
password <generated token>
copy code

You can then install the latest version of djongo:

pip install --extra-index-url https://pypi.djongomapper.com/extended-features/ djongo
copy code

Make sure to safely install and store the pass token inside the ~/.netrc file. The pass token is not saved on the DjongoCS server locally.

SSH

On account creation you install your public SSH key at the dashboard. This gives the SSH access to the VM instance for uploading a Django App. Once the key is installed, the dashboard displays the SSH port number over which you can connect to the VM instance.

Establish a secure shell connection using:

ssh <username>@api.djongomapper.com -p <port>
copy code

The username is the same as the username used while creating the DjongoCS account.

When you create an account on DjongoCS you get a unique URL path assigned to you. The Django views that you create for servicing your API can be accessed and extended further starting from the base URL: https://api.djongomapper.com/<username>

Launching the App

Establishing an SSH connection to your server logs you into the /home/$USER directory. The typical home directory structure looks like:

~home
| -- .ssh/
| -- site/
|   -- api/
|     -- settings.py
|     -- urls.py
|   -- apps/
|     -- app1/
|       -- views.py
|       -- models.py
|     -- app2/
|       -- views.py
|       -- models.py
copy code

In your urls.py if you add an entry like path('hello/', app1.views.hello), the URL path becomes https://api.djongomapper.com/<username>/hello

Extended Features

Features under development on DjongoCS are not a part of the standard Djongo package.

DjongoCS supports multiple features of MongoDB including:

  • Indexes: Support for indexes provided by MongoDB, for example 2dSphere Index, Text Index and Compound Indexes.

  • Model Query: Support for GeoSpatial Queries and Tailable Cursors.

  • Model Update: Unordered and Ordered Bulk Writes.

  • Database Transactions: Atomic multi document transactions with commit and rollback support.

  • Schema Validation and Model Creation: Automatic JSON Schema validation document generation and options to add Read and Write Concerns for the Models.

  • Aggregation Operators: Support for various aggregation operators provided by MongoDB.

Contribute

If you think djongo is useful, please share it with the world! Your endorsements and online reviews will help get more support for this project.

You can contribute to the source code or the documentation by creating a simple pull request! You may want to refer to the design documentation to get an idea on how Django MongoDB connector is implemented.