Django extensions

I just started using django-extensions and it is a really simple way to add some really useful features to django.

Installation is really straightforward:

    $ pip install django-extensions

or

    $ easy_install django-extensions

I did also needed to install pygraphviz:

    $ apt-get install python-pygraphviz

Then add django-extensions to the INSTALLED APPS:

INSTALLED_APPS = (
    ...
    'django_extensions',
    ...
)

The first command I wanted to use was the graph_models command which basically creates a graphical relational diagram of the applications in the project. To visualize the whole project with grouping by application:

    $ ./manage.py graph_models -a -g -o my_project.png

and for specific apps:

    $ ./manage.py graph_models my_app | dot -Tpng -o my_app.png

This is a really nice way to let yourself and others visualize the db schema at a glance.

Another insanely useful feature is django shell_plus. Among other things this feature autoloads you models into the shell:

    $ ./manage.py shell_plus

You should see all the models loaded before the prompt.

So no more

    >>> from myapp.models import *

There are plenty of other features described in the documentation, but even in the short time I have played with this package I can tell it will be an indispensable tool in my django toolbox.

Benchmarking queries in Django

So this is not a post specific to Django, but the examples I give are.

The problem I was looking for a solution for was that I wanted to benchmark my Django ORM queries to find ways to improve performance. More specifically, I wanted to find the optimum way to count objects returned as querysets.

I stumbled across MAX() and COUNT() and wanted a raw speed performance comparison between the two. Turns out there is a way without leaving the python shell.

This code is attributed to sleepycal on djangosnippets.org. Original post can be found here.

Here it is:


>>> from django.db.models import Max,Count 
>>> _t = time.time(); x = Article.objects.aggregate(Max('id')); "Took %ss"%(time.time() - _t )
'Took 0.00190091133118s'

and using Count():


>>> _t = time.time(); x = Article.objects.aggregate(Count('id')); "Took %ss"%(time.time() - _t )
'Took 1.34142112732s'

As you can see – quite a significant difference on ~108 000 rows. This is a good way to get a quick look at how a queryset call performs when there is more than one way in which it can be constructed.

Accessing Django Runserver across a network

Here is a simple trick which I have found immensely useful when it comes to browser comparability testing. Basically, I want to run my django development server on one machine (in my case a linux box) and then connect using a different machine (say, running MS Windows with IE).

I have done this before with apache and php projects, but when I tried with my.ip:8000 from another machine I just got my apache welcome page. The trick I found here, was to first stop apache like so:

$ sudo /etc/init.d/apache2 stop

Then start the django runserver on port 80 like so:

$ sudo python manage.py runserver 0.0.0.0:80

And viola! I can connect to the development server using my.ip:80 and test browser compatibility easily – as long as I have the relevant OS/ browser lying around on another machine.

Multiple database implementation in Django

New with Django 1.2 came multiple database support.

It just so happened that I am putting together such a project and had need of this feature. It was one of those tasks which seemed dauntingly complicated at first, though when I got it working, seemed surprisingly easy. However, the documentation, of which the official docs are the best offering, left me scratching my head for some time. So in this post I aim to lay things out a little more clearly for someone who is attempting this for the first time, and of course as a reference for myself.

My use case may or may not be typical but I would bet it is not rare. I had two independent Django projects which had an app each. The time came when these needed to be two apps in a larger project, but they still required discrete databases. So I rolled one into the other and dealt with my need for multi-db support by using the manual method.

This definitely worked but was more of a stop-gap until I worked up courage to tackle the automatic routing method. My primary motivation was that I needed to have access to both apps in the admin, which required the automatic method. Aside from that, the frequency of the using method such as:

item = Item.objects.using('my_db_2').all() 

was getting ugly and daunting to keep track of.

So when it came to it, implementing automatic routing came down to three main steps:

  1. Define database connections in myproject/settings.py:
    DATABASES = {
        'default': {
            'NAME': 	'db1',
            'ENGINE': 	'django.db.backends.mysql',
            'USER': 	'myuser1',
            'PASSWORD':    'mypass1',
        },
        'my_db_2': {
            'NAME': 	'db2',
            'ENGINE': 	'django.db.backends.mysql',
            'USER': 	'myuser2',
            'PASSWORD':    'mypass2'
        }
    }
  2. Define router in myproject/myapp2/routers.py:
    class MyApp2Router(object):
        """
        A router to control all database operations on models in
        the myapp2 application
        """
    
        def db_for_read(self, model, **hints):
            """
            Point all operations on myapp2 models to 'my_db_2'
            """
            if model._meta.app_label == 'myapp2':
                return 'my_db_2'
            return None
    
        def db_for_write(self, model, **hints):
            """
            Point all operations on myapp models to 'other'
            """
            if model._meta.app_label == 'myapp2':
                return 'my_db_2'
            return None
    
        def allow_syncdb(self, db, model):
            """
            Make sure the 'myapp2' app only appears on the 'other' db
            """
            if db == 'my_db_2':
                return model._meta.app_label == 'myapp2'
            elif model._meta.app_label == 'myapp2':
                return False
            return None
    

    This is pretty much a copy and paste job from the official example. The one key point left out was where to define this. It turns out that myapp2/models.py is not the right place and something like myapp2/routers.py is!

  3. Now all that remains is to tell our project about the router. In myproject/settings.py add:
    DATABASE_ROUTERS = ['myapp.routers.MyApp2Router',]
    

You can see that throughout I am only concerned with my second app (myapp2). This is because, for the time being myapp1 would use the default database router which comes out of the box with Django. Of course at some point I may well need another router for that app or an additional one, which is when I could define I new router, hook it up to said app and add the router path to the settings.

I am sure that there are a number of unexplored nuances to working with multiple databases in Django, and I admit I have not delved into the underlying code of this functionality, but for the time being everything seems to works as required. The really impressive part was seeing both apps in the admin without errors.

Thanks Django.