Django management commands, remotely

I love Django management commands.

For I’ve written several management commands for things that I don’t need a web UI for, like:

  • expire - delete expired pastes (invoked by cron)
  • expunge - manage TOS violations like spam
  • stats - useful metrics on the current corpus of pastes, and on user activity

In running the site I also use management commands from my django-blocklist app, for tasks like deleting expired entries (via cron) or generating reports.

Attitudes toward management commands

Management commands have been used very differently across the big Django projects I’ve worked on.

Some places used the stock ones (e.g. shell, dbshell, migrate) but didn’t create custom ones — in those shops there was typically a collection of standalone Python scripts that could have been written as management commands, but weren’t.

On one project one of those standalone Python scripts had huge chunks of raw SQL in it, from an engineer who was convinced the ORM couldn’t do what they wanted.

In contrast, some other engineering orgs embrace management commands heartily, creating custom ones as the basis for staff-facing tooling.

Why management commands?

Running a Django-based site, like any web app, invariably involves some maintenance/ops tasks that aren’t handled by the main application code, but which depend on the main application code or data.

The first time a task of this sort comes up, you might just use the admin, or a Python shell, or a DB shell, or a one-off script.

But if it comes up a second or third time, you’ll start hearing a little voice crying “I want to be a management command!”

So, yeah, I love management commands.

The downside

For one-off use — a quick run of a report for example — there’s overhead if you’re not already logged into the server:

  1. SSH to server
  2. Activate venv
  3. Run management command
  4. Save any important output
  5. Disconnect

My solution: dpmanage

I’ve come up with a solution that I want to share. It’s not terribly complicated or clever, and I’m sure it’s been “invented” independently many times. This post is for sharing it with everybody else.

I created a shell alias called dpmanage that lets me run management commands on my production host, directly from my laptop. It even has tab completion.

I declare it like this: alias dpmanage='ssh -q $HOST "$VENV_DIR/bin/python $PROJECT_DIR/"' (you’d replace $HOST and $VENV_DIR and $PROJECT_DIR with your own values of course).

With passwordless login, it Just Works.

Wins from this approach:

  • No login required
  • No manual venv activation required
  • No twitchy typing from high-latency SSH connections
  • Ability to pipe text in or out

Example of piping in action:

$ echo "SELECT COUNT(*) FROM django_blocklist_blockedip" | dpmanage dbshell

Let’s add tab completion!

Tab Completion

It’s pretty easy to add tab completion for the subcommands. Here’s the code I added to .zshrc for this:

autoload -Uz compinit && compinit
setopt completealiases

_dpmanage() {
    local -a subcommands
    subcommands=('cat' 'expunge' 'report_blocklist' 'search_blocklist' 'stats')
    _describe 'command' subcommands
compdef _dpmanage dpmanage

I love having tab-completion. My actual _dpmanage definition also includes the built-in management commands. I haven’t yet added completion for options (e.g. migrate --check).

Let me know if you try this, and if you come up with any interesting twists or improvements!