For the past few months, I have been trying out the different schema migration tools for Django. At first, the obvious choice seemed like django-evolution. It was the oldest and the most mature project, and after watching the schema evolution panel from DjangoCon 2008, it was also the one that made most sense in terms of architecture. After working with django-evolution in a few projects though, I found that it did not work well in practice. I have recently been testing South, and it seems like the better choice.
I had a few big issues with django-evolution:
- Inability to go backwards or forwards, or to be able to skip migrations (or mutations as it calls them)
- Inability to use custom fields that require additional parameters
- Lack of integration with syncdb (if someone in the team accidentally does a syncdb, it fails)
I love how clever evolution can be when guessing the modifications to a model, but I don’t think what’s needed from a migration tool is cleverness. I needed a tool that gave me the freedom to do whatever I wanted, while making my job easier by managing all the changes.
I have been playing around with South for the past week, and it seems like it offers pretty much everything I need, plus a bit more. My only grief with it is the lack of documentation. I think they should stop focusing on adding any more features to the project and write a solid documentation with tutorials instead.
One of the best features of South is that it doesn’t need to be on a project from the very beginning, it works on a per-app level, and can be attached to an app at any given time. You simply generate an initial migration by calling the startmigration command. Let’s say we have an app named blog:
./manage.py startmigration blog --initial
This will create a migrations directory inside the application, and the next time you call syncdb, South will prevent Django from synchronizing this application, since it is now marked as an app managed by South. In order to apply the migration, you will have to call migrate. If this is a new app that’s not in the database yet, we can simply do this:
If this is an old app that was already synchronized by using syncdb, then we have to mark this migration as “applied” without actually applying it:
./manage.py migrate blog --fake
Let’s say we added a new model under the blog app called Article, we can once again call startmigration to generate it for us:
./manage.py startmigration blog add_article_model --model Article
What if we forgot to add the slug field to the model we just created? Let’s generate the migration for adding that field:
./manage.py startmigration blog add_article_slug --add-field Article.slug
We could also modify the default migration generated by South to populate that slug field. Here is a quick example:
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
from south.db import db
from blog.models import *
db.add_column('blog_article', 'slug', models.SlugField(_("Slug"), max_length=255, default=""))
articles = Article.objects.all()
for article in articles:
article.slug = slugify(article.title)
There are a few interesting bits here. First of all, we make sure the slugify method is imported so we can generate the slugs, but we also import the lazy translation method, ugettext_lazy because it’s being imported with a custom name.
In the forwards method, we make sure that the slug field has a default value, otherwise PostgreSQL will complain about a NOT NULL field being created without values. After creating the column in the database, we loop through all articles and generate slugs for them.
In the backwards method, there is an additional call to delete_index. This is required to remove the index on the slug field, South does not add this automatically.
Now we can migrate:
./manage.py migrate blog 0003
And that’s it! So far, I really like the freedom and ease of use South is giving me.