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.
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:
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
cron lets you set and change environment variables with ease, so you can have things like this in your
# 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.
Back in May, Jared Kuolt wrote up this technique, which is exactly how Django’s own
manage.py script handles settings: the function
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)
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.
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
configure() method of the
LazySettings class in
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.
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
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
osmodule and the
We set up a usage string;
optparsewill print this in help and error messages.
We create an
OptionParserwith the usage string.
We add an option to the
OptionParser: the script will accept an argument, either as
-sor 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
- We check to see that the “settings” argument was supplied, and direct the parser to throw an error if it wasn’t.
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
—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.
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 —
settings.configure() — it’s relatively easy to write an
optparse-based script which does the right thing.
And that’s a wrap
Each of these methods is appropriate for different types of situations, and depending on exactly what you need to do you may end up using all of them at various times. Personally, I tend to either write scripts which use
optparse and take a command-line argument for settings, or (for maintenance tasks which will run in
cron) to write scripts which just assume
DJANGO_SETTINGS_MODULE is taken care of in advance, but all of these methods can be useful, so keep them all in mind whenever you find yourself needing a standalone script that uses Django.