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:
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:
./manage.py migrate
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 *
class Migration:
def forwards(self):
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)
article.save()
def backwards(self):
db.delete_index('blog_article', 'slug')
db.delete_column('blog_article', 'slug')
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.
Great, this will save me quite some work. Thank you for the run through. Much appreciated.
Regards
Hi,
Like your comment form! (and the markdown hints)
Thanks for this gentle introduction. I was wondering how 0003 is tied to the Migration class you present above?
Thanks
@pterk Thanks! 0003 is just a way of specifying which migration you want to move forwards to (or backwards). When you generate a new migration, South prepends the file name with its sequence number, so you can easily use that as a reference. In the example line you see there, I could have used:
./manage.py migrate blog
And it would have done the same thing, since I only want to move to the latest migration. But if we were moving backwards, I could use the number convention as such:
./manage.py migrate blog 0002
This would have taken me back to the app’s status right after the second migration.
It is important to note that South has a special case for the initial status of an app. In order to get back to the very beginning, you use the reference “zero”:
./manage.py migrate blog zero
Hope this helps, let me know if anything is not clear.
Yes, there are in fact reasons why I didn’t like django-evolution (and made South) - I’d been using it for at least six months before that, and I just didn’t like it.
As for documentation - I agree, while there’s lots of reference documentation, there’s barely any proper tutorials, and the one that is there is long, rambling, and doesn’t address the new autodetection features - my main focus has been to get to parity with django-evolution before making a big push for switching.
Now we’re pretty much there, so I get to spend more time on the train writing docs!
Thanks for clarifying
© Copyright 2001-2010 Taylan Pince. All rights reserved.
Finally a “tutorial” i could understand. I have tried to use South, but never got around to actualy make it work. After this I think I’m ready to try it again.
Thank you!