Blog

Waldemar Kornewald on May 25, 2010

MongoDB backend for Django-nonrel released

Update: It's possible to use simple JOINs via the django-dbindexer now.

Finally, Django-nonrel has a MongoDB backend, which was originally started by George Karpenkov and then finished by Alberto Paro, Flavio Percoco, and I adapted the code to the nonrel backend API.

This means you can use the Django ORM (from django.db import models) directly with MongoDB. Even better, you can write code that can run on both MongoDB and App Engine (via our App Engine backend) and possibly also SQL. For example, this website is running Django-nonrel on App Engine, but the same code also works with the MongoDB backend and even with Django's SQL backends. So, Django-nonrel allows for writing portable Django apps. This portability will often only be possible between non-relational DBs, but for many simple apps SQL should work, too.

You're probably wondering by now: Does the admin interface work? Yes, but it's limited by MongoDB's query capabilities. For example, you can't do JOINs (except for JOINs made possible by django-dbindexer).

Installation

First, install pymongo, for example via "easy_install pymongo". Then, clone the MongoDB backend (or download the source) and install it by running "python setup.py install". Next, clone the Django-nonrel repository (or download the source) and install it via "python setup.py install". Finally, clone the djangotoolbox repository (or download the source) and install it via "python setup.py install". If you want to test everything with this website's code you can also clone the allbuttonspressed repository.

The last step is to specify the MongoDB backend and djangotoolbox in your settings.py:

DATABASES = {
    'default': {
        'ENGINE': 'django_mongodb_engine',
        'NAME': 'test',
        'USER': '',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': 27017,
    }
}

INSTALLED_APPS = (
    ...
    'djangotoolbox',
)

That's it. You can now use MongoDB with Django's ORM.

What can ya do?

The most notable unsupported field type is ManyToManyField. It's no problem if that field is in your model, but you can't access it because it requires JOIN support which obviously doesn't exist on non-relational databases. Actually, any query that spans relations (via "__" like Profile.objects.filter(user__username='someone')) won't work.

This leads to the problem that you can't edit users in the admin interface, but there's a simple workaround which automatically gets applied when you add "djangotoolbox" to your INSTALLED_APPS.

These lookup types are supported:

  • __exact (default)
  • __iexact
  • __startswith
  • __istartswith
  • __contains
  • __icontains
  • __endswith
  • __iendswith
  • __regex
  • __iregex
  • __lt
  • __lte
  • __gt
  • __gte
  • __in
  • __range
  • __year
  • __isnull (except for ForeignKey due to a Django bug)

You can also use QuerySet.exclude() with those lookup types.

Uniqueness constraints on fields and via unique_together are supported, too.

Here's a little summary of the advanced Django features that are not supported:

  • JOINs (except for JOINs made possible by django-dbindexer)
  • many-to-many relations
  • multi-table inheritance
  • aggregates
  • transactions
  • QuerySet.select_reated()

Important: string-based primary keys

Normally, when you create a model the primary key is an integer. For example

class Post(models.Model):
    title = models.CharField(max_length=100)

# ...

post = Post.objects.get(title='MongoDB on Django-nonrel')

Here, post.id is a string on MongoDB (e.g., u'4bd212d9ccdec2510f000000'). In contrast, on SQL and App Engine it's an integer. So, what's the implication for you?

Always write code that works with both string and integer-based primary keys. This also applies to urlpatterns. In particular, when passing an id from your view's URL to a query just leave int/string handling to Django's QuerySet. For example, Post.objects.get(id=object_id) will work correctly on SQL even if object_id is actually a string containing a number (object_id = '32'). This ensures that your code is portable to other backends.

Summary

  • simple queries on a single model work as expected
  • keep MongoDB limitations in mind when formulating queries
  • JOINs (except for JOINs made possible by django-dbindexer), many-to-many relations, and multi-table inheritance don't work
  • write portable code that works with string and integer-based primary keys
  • join the Django-nonrel discussion group if you want to contribute

You should read 4 things to know for NoSQL Django coders if you want to learn more about nonrel development with Django. That post also discusses some parts of this website's code.

Do you already have an idea for a neat project which could be built with Django-nonrel and MongoDB? Would you like to port an existing Django app? Do you have any questions? Please leave a comment.