In my current development house, we have the painfully obvious "different settings for different stages of the development cycle."  Production, obviously, isn't in DEBUG mode so users don't see the underlying technology when things go awry, and each dev has his own database.   The solution we've found is to create separate settings files with only the minor changes made, and then for settings.py to import that when loading, using the "--settings" argument to manage.py.

There is an inherent conflict between South and Fabric when we do this.  We use Fabric for roll-out from the repository to staging and production, and call migrate in the process.  We came to use South late in the development cycle, and many of our apps were converted to South long after they'd been populated with data.  We know how to do the whole "create, migrate data, drop table" migration process, but the real trick was 0001_initial.py; South "fakes" this for tables with existing data, recording that it was run when, in fact, it was skipped over in favor of the version created ages ago by migrate.py syncdb.

When we roll out to another stage, that database has no knowledge of this fakery.  Neither does South.  Fabric would call 'migrate' and the process would crash with warnings about how the table already exists.   So how do we get past that step?

Well, the answer would be to modify 0001_initial.py to test if the table already exists, and to skip if it does.  We need for the engine to fail only when actually accessing the data, and we need it to fail early.  Most models have a core table, the one without which the entire app would be meaningless.  In my example, that table is "stories.story":

    def forwards(self, orm):

        # Probe for this.  If it works, we don't want to proceed,
        # because this isn't a migration, it's a build-out.

        if not db.dry_run:
            try:
                x = orm['stories.Story'].objects.all()
                return
            except Exception, e:
                pass

Now, if the database table doesn't exist, the ORM handler will throw an exception and we'll proceed to build the table. If it does, the table already exists and we shouldn't proceed.