Standalone Django scripts

In the grand tradition of providing answers to frequently-asked questions from the django-users mailing list and the #django IRC channel, I’d like to tackle something that’s fast becoming the most frequently-asked question: how do you write standalone scripts which make use of Django components?

At first glance, this isn’t a terribly hard thing to do: Django’s just plain Python, and all of its components can — in theory — be imported and used just like any other Python modules. But the thing that trips most people up is the need, in most parts of Django, to supply some settings Django can use so it’ll know things like which database to connect to, which applications are available, where it can find templates, etc.

Depending on exactly what you need to do, there are several ways you can approach this problem, so let’s run through each of them in turn.

Set DJANGO_SETTINGS_MODULE before you run

The simplest method is to simply assign a value to the DJANGO_SETTINGS_MODULE environment variable before you run your script, and that’s not terribly hard to do if you understand a little bit about how environment variables work. On most Unix-based systems (including Linux and Mac OS X), you can typically do this with the export command of the standard shell:

export DJANGO_SETTINGS_MODULE = yoursite.settings

Then you can just run any scripts which rely on Django settings, and they’ll work properly. If you’re using a different shell, or if you’re on Windows, the exact command to type will be slightly different, but the idea is the same.

One extremely useful application of this is in a crontab file; cron lets you set and change environment variables with ease, so you can have things like this in your crontab :

# Cron jobs for foo.com run at 3AM DJANGO_SETTINGS_MODULE = foo.settings 0 3 * * * python /path/to/maintenance/script.py 30 3 * * * python /path/to/other/script.py # Cron jobs for bar.com run at 4AM DJANGO_SETTINGS_MODULE = bar.settings 0 4 * * * python /path/to/maintenance/script.py 30 4 * * * python /path/to/other/script.py

This is pretty much exactly what the crontab files on our servers at World Online look like, and in general this is the cleanest way to handle scripts which use Django components and need to run as cron jobs.

Use setup_environ()

Back in May, Jared Kuolt wrote up this technique, which is exactly how Django’s own manage.py script handles settings: the function setup_environ() in django.core.management will, given a Python module containing Django settings, handle all the business of (appropriately for its name) setting up your environment for you:

from django.core.management import setup_environ from mysite import settings setup_environ ( settings )

Below the setup_environ() line, you can make use of any Django component and rest assured that the proper settings will be available for it.

The only real disadvantage to this is that you lose some flexibility: by tying the script to a particular settings module, you’re also tying it to a particular Django project, and if you later want to re-use it you’ll have to make a copy and change the import to point at another project’s settings file, or find a different way to configurably accept the settings to use (we’ll look at that again in a moment). If all you need is a one-off script for a particular project, though, this is an awfully handy way to set it up.

Use settings.configure()

For cases where you don’t want or need the overhead of a full Django settings file, Django provides a standalone method for configuring only the settings you need, and without needing to use DJANGO_SETTINGS_MODULE : the configure() method of the LazySettings class in django.conf ( django.conf.settings is always an instance of LazySettings , which is used to ensure that settings aren’t accessed until they’re actually needed). There’s official documentation for this, and it’s fairly easy to follow along and use it in your own scripts:

from django.conf import settings settings . configure ( TEMPLATE_DIRS = ( '/path/to/template_dir' ,), DEBUG = False , TEMPLATE_DEBUG = False )

And then below the configure() line you’d be able to make use of Django’s template system as normal (because the appropriate settings for it have been provided). This technique is also handy because for any “missing” settings you didn’t configure it will fill in automatic default values (see Django’s settings documentation for coverage of the default values for each setting), or you can pass a settings module in the default_settings keyword argument to configure() to provide your own custom defaults.

Like setup_environ() , this method does tie you down to a particular combination of settings, but again this isn’t necessarily a problem: it’s fairly common to have project-specific scripts which won’t need to be re-used and rely on some values particular to that project.

Accept settings on the command line

We’ve seen that setup_environ() and settings.configure() both seem to tie you to a particular settings module or combination of manually-provided settings, and while that’s not always a bad thing it presents a major stumbling block to reusable applications. Setting DJANGO_SETTINGS_MODULE (as seen above in the context of a crontab ) is much more flexible, but can be somewhat tedious to do over and over again. So why don’t we come up with a method that lets you specify the settings to use when you call the script?

As it turns out, this is extremely easy to do; I think the technique doesn’t get a lot of attention because most newcomers to Django don’t yet know their way around Python’s standard library and so don’t stumble across the module which makes it all simple: optparse. In a nutshell, optparse provides an easy way to write scripts which take traditional Unix-style command line arguments, and to get those arguments translated into appropriate Python values.

A simple example would look like this:

import os from optparse import OptionParser usage = "usage: %prog -s SETTINGS | --settings=SETTINGS" parser = OptionParser ( usage ) parser . add_option ( '-s' , '--settings' , dest = 'settings' , metavar = 'SETTINGS' , help = "The Django settings module to use" ) ( options , args ) = parser . parse_args () if not options . settings : parser . error ( "You must specify a settings module" ) os . environ [ 'DJANGO_SETTINGS_MODULE' ] = options . settings

There’s a lot going on here in a very small amount of code, so let’s walk through it step-by-step:

We import the standard os module and the OptionParser class from optparse . We set up a usage string; optparse will print this in help and error messages. We create an OptionParser with the usage string. We add an option to the OptionParser : the script will accept an argument, either as -s or as the long option settings , which will be stored in the value attribute “settings” of the parsed options, and we provide it with some explanatory text to show in help and error messages. We parse the arguments from the command line using parser.parse_args() . We check to see that the “settings” argument was supplied, and direct the parser to throw an error if it wasn’t. We use os.environ to set DJANGO_SETTINGS_MODULE .

Not bad for about ten lines of easy-to-write code; once that’s been done, DJANGO_SETTINGS_MODULE will have been set and we can use any Django components we like. Running the script will look like this:

python myscript.py --settings = yoursite.settings

The parser created with optparse will handle the parsing; it’ll also automatically enable a “help” option for the -h or —help flags which will list all of the available options and their help text, and show appropriate error messages when the required “settings” argument isn’t supplied.

Because optparse makes it easy to pack a lot of configurability into a small amount of code, it’s generally my preferred method for writing standalone scripts which need to interact with Django, and I highly recommend spending some time with its official documentation. If you’d like to use one of the other configuration methods — setup_environ() or settings.configure() — it’s relatively easy to write an optparse -based script which does the right thing.

And that’s a wrap