Using Django with cron for batch jobs

26 April 2007

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.

Filed under: Django — Scott @ 5:45 pm

2 Comments »

  1. I made a blog post recently about something very similar to your post. The difference is that I put my code in the application directory instead of the project directory (next to the models.py). I think it is more logical that way.

    Comment by ak — 8 May 2007 @ 7:32 am

  2. Hi ak.

    I suppose it depends what your script is doing whether it should go in the project or application directory. If it only uses models for one application, it would be more logical to put it in the application directory.

    In my case, the script uses models from different applications within the project, so I put it in the project directory (next to settings.py).

    Comment by admin — 8 May 2007 @ 9:19 am

RSS feed for comments on this post.

Leave a comment