My first time using a schema evolution tool for a [Django][1] project, and I like it. I chose [South][2] because it had a clear path for devolving a schema and had a database-independent API (I’ve been testing [Postgres][4] with some projects that currently use a [MySQL][3] database since we have a [Trac installation that uses Postgres][5] 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][6] 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!
[1]: http://www.djangoproject.com/
[2]: http://south.aeracode.org/
[3]: http://www.mysql.com/
[4]: http://www.postgresql.org/
[5]: http://trac.edgewall.org/wiki/DatabaseBackend
[6]: http://south.aeracode.org/wiki/ConvertingAnApp