Using Django with cron for batch jobs
The MVC-style separation in Django makes it easy to write Python scripts to handle automated jobs. They can query the database using the Django database API and create and manipulate objects. Essentially everything you can do in a view, except you don’t have a request
object since you aren’t in the context of an http request.
For my current project I have a couple of scripts that are executed by cron. One script emails users telling them what’s new. It gets this information by calling methods on various models. Another script uses the database API to gather some statistics on the state of the system (e.g. how many registered users and who were the last five) and sends them by email.
Here’s some code.
#! /usr/bin/env python import sys import os def setup_environment(): pathname = os.path.dirname(sys.argv[0]) sys.path.append(os.path.abspath(pathname)) sys.path.append(os.path.normpath(os.path.join(os.path.abspath(pathname), '../'))) os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' # Must set up environment before imports. setup_environment() from django.core.mail import send_mail from django.contrib.auth.models import User def main(argv=None): if argv is None: argv = sys.argv # Do stuff here. For example, send an email reporting number of users. user_count = User.objects.count() message = 'There are now %d registered users.' % user_count send_mail('System report', message, 'report@example.com', ['user@example.com']) if __name__ == '__main__': main()
The interesting bit is in setup_environment
which adds paths to the Python path so when you import modules used in your project (e.g. import myapp.models
) Python knows where to find them.
In my case, I put my scripts in to the Django project directory (i.e. next to settings.py). The path to the Django project needs to be in the Python path so that imports work. setup_environment
uses sys.path.append
to add the path of the script that is executing (e.g. /srv/www/mydjangoproject/status_report.py
) and its parent directory. This is sufficient to make imports in the project work.
The other thing that needs to be set is the DJANGO_SETTINGS_MODULE
environment variable. It’s just the name of the settings module, by default “settings”.
There may be a better way to do this setup, but it seems to work ok.
Now you can call the script directly or from cron. If you’re sending emails from your script, you can write straight text templates (rather than html) and use django.template.loader.render_to_string
to render the template with some variables in to a string to send by email.
from django.template.loader import render_to_string email_body = render_to_string('reports/email/score_report.txt', {'user': user, 'top_score': top_score})
Update: Here’s a cleaner way from Jared. It uses the setup_environ
function from django.core.management
.
Update: James Bennett has an excellent write-up on standalone Django scripts.