Tag Archives: south

First steps with South

My first time using a schema evolution tool for a Django project, and I like it. I chose South because it had a clear path for devolving a schema and had a database-independent API (I’ve been testing Postgres with some projects that currently use a MySQL database since we have a Trac installation that uses Postgres anyway).

I have a simple application to store serial numbers and purchasing information for software. It has a License model, but I wanted to add a comment on each license. Following the instructions for converting an existing app on the South wiki, here’s what I did to convert my software license application.

First I added south to INSTALLED_APPS in settings.py and ran manage.py syncdb to install the South application tables for the first time. This was before using South to actually manage any of the evolutions.

% ./manage.py syncdb
Syncing...
Creating table south_migrationhistory

Synced:
 > django.contrib.auth
 > django.contrib.contenttypes
 > django.contrib.sessions
 > django.contrib.sites
 > django.contrib.admin
 > myproject.software
 > south

Not synced (use migrations):
 - 
(use ./manage.py migrate to migrate these)

Then I created an initial migration for my application. This gives me a migration that matches the existing database schema.

% ./manage.py startmigration software --initial
Creating migrations directory at '/Users/david/myproject/../myproject/software/migrations'...
Creating __init__.py in '/Users/david/myproject/../myproject/software/migrations'...
Created 0001_initial.py.

The next step was to bring this application under South’s control. You have to pretend to apply the initial migration (because it already exists in the database), which is done using South’s --fake switch. I discovered a problem with my use of _ in my 0001_initial.py migration: the easiest fix was to import it explicitly within the migration (this fix was necessary for the second migration too).

from south.db import db
from django.db import models
from myproject.software.models import *
from django.utils.translation import gettext_lazy as _

Then I was ready to bring my application under South’s control.

% ./manage.py migrate software 0001 --fake
 - Soft matched migration 0001 to 0001_initial.
Running migrations for software:
 - Migrating forwards to 0001_initial.
 > software: 0001_initial
   (faked)

Now I was ready to alter my model definitions and get South to do the tedious work of updating the database schema. I added a new Note model in the application’s models.py and added a new field to the existing License model.

class License(models.Model):
    """A license for a software title."""
    ...
    department = models.CharField(_("Department"), max_length=100, blank=True)
    ...


class Note(models.Model):
    """A note for a license."""
    license = models.ForeignKey(License, editable=False)
    author = models.CharField(_("author"), max_length=100, editable=False)
    created = models.DateTimeField(auto_now_add=True)
    note = models.TextField(_("note text"))

    def __unicode__(self):
        return self.note

    class Meta:
        ordering = ['-created']

With those changes I used the startmigration command to generate a migration for the new model and fields.

% ./manage.py startmigration software notes_department --model Note --add-field License.department
Created 0002_notes_department.py.

(Afterwards I edited the migration to import _ as noted above.)

The final step was to use South to apply this migration (and any others) to the database.

% ./manage.py migrate software 
Running migrations for software:
 - Migrating forwards to 0002_notes_department.
 > software: 0002_notes_department
   = ALTER TABLE "software_license" ADD COLUMN "department" varchar(100) NOT NULL; []
   = CREATE TABLE "software_note" ("id" serial NOT NULL PRIMARY KEY, "license_id" integer NOT NULL, "author" varchar(100) NOT NULL, "created" timestamp with time zone NOT NULL, "note" text NOT NULL); []
   = ALTER TABLE "software_note" ADD CONSTRAINT "license_id_refs_id_61c4291d" FOREIGN KEY ("license_id") REFERENCES "software_license" ("id") DEFERRABLE INITIALLY DEFERRED; []
   = CREATE INDEX "software_note_license_id" ON "software_note" ("license_id"); []
 - Sending post_syncdb signal for software: ['Note']
 - Loading initial data for software.

Sweet!