Check out response below by Paul for some notes on compatibility with newer versions of Django/South.
This seemed like an interesting problem, and I'm becoming a big fan of South, so I decided to look into this a bit. I built a test project on the abstract of what you've described above, and have successfully used South to perform the migration you are asking about. Here's a couple of notes before we get to the code:
The South documentation recommends doing schema migrations and data migrations separate. I've followed suit in this.
On the backend, Django represents an inherited table by automatically creating a OneToOne field on the inheriting model
Understanding this, our South migration needs to properly handle the OneToOne field manually, however, in experimenting with this it seems that South (or perhaps Django itself) cannot create a OneToOne filed on multiple inherited tables with the same name. Because of this, I renamed each child-table in the movies/tv app to be respective to it's own app (ie. MovieVideoFile/ShowVideoFile).
In playing with the actual data migration code, it seems South prefers to create the OneToOne field first, and then assign data to it. Assigning data to the OneToOne field during creation cause South to choke. (A fair compromise for all the coolness that is South).
So having said all that, I tried to keep a log of the console commands being issued. I'll interject commentary where necessary. The final code is at the bottom.
Command History
django-admin.py startproject southtest
manage.py startapp movies
manage.py startapp tv
manage.py syncdb
manage.py startmigration movies --initial
manage.py startmigration tv --initial
manage.py migrate
manage.py shell # added some fake data...
manage.py startapp media
manage.py startmigration media --initial
manage.py migrate
# edited code, wrote new models, but left old ones intact
manage.py startmigration movies unified-videofile --auto
# create a new (blank) migration to hand-write data migration
manage.py startmigration movies videofile-to-movievideofile-data
manage.py migrate
# edited code, wrote new models, but left old ones intact
manage.py startmigration tv unified-videofile --auto
# create a new (blank) migration to hand-write data migration
manage.py startmigration tv videofile-to-movievideofile-data
manage.py migrate
# removed old VideoFile model from apps
manage.py startmigration movies removed-videofile --auto
manage.py startmigration tv removed-videofile --auto
manage.py migrate
For space sake, and since the models invariably look the same in the end, I'm only going to demonstrate with 'movies' app.
movies/models.py
from django.db import models
from media.models import VideoFile as BaseVideoFile
# This model remains until the last migration, which deletes
# it from the schema. Note the name conflict with media.models
class VideoFile(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
name = models.CharField(max_length=1024, blank=True)
size = models.IntegerField(blank=True, null=True)
ctime = models.DateTimeField(blank=True, null=True)
class MovieVideoFile(BaseVideoFile):
movie = models.ForeignKey(Movie, blank=True, null=True, related_name='shows')
movies/migrations/0002_unified-videofile.py (schema migration)
from south.db import db
from django.db import models
from movies.models import *
class Migration:
def forwards(self, orm):
# Adding model 'MovieVideoFile'
db.create_table('movies_movievideofile', (
('videofile_ptr', orm['movies.movievideofile:videofile_ptr']),
('movie', orm['movies.movievideofile:movie']),
))
db.send_create_signal('movies', ['MovieVideoFile'])
def backwards(self, orm):
# Deleting model 'MovieVideoFile'
db.delete_table('movies_movievideofile')
movies/migration/0003_videofile-to-movievideofile-data.py (data migration)
from south.db import db
from django.db import models
from movies.models import *
class Migration:
def forwards(self, orm):
for movie in orm['movies.videofile'].objects.all():
new_movie = orm.MovieVideoFile.objects.create(movie = movie.movie,)
new_movie.videofile_ptr = orm['media.VideoFile'].objects.create()
# videofile_ptr must be created first before values can be assigned
new_movie.videofile_ptr.name = movie.name
new_movie.videofile_ptr.size = movie.size
new_movie.videofile_ptr.ctime = movie.ctime
new_movie.videofile_ptr.save()
def backwards(self, orm):
print 'No Backwards'
South is awesome!
Ok standard disclaimer: You're dealing with live data. I've given you working code here, but please use the --db-dry-run
to test your schema. Always make a backup before trying anything, and generally be careful.
COMPATIBILITY NOTICE
I'm going to keep my original message intact, but South has since changed the command manage.py startmigration
into manage.py schemamigration
.
You can use the db.rename_column
function.
class Migration:
def forwards(self, orm):
# Rename 'name' field to 'full_name'
db.rename_column('app_foo', 'name', 'full_name')
def backwards(self, orm):
# Rename 'full_name' field to 'name'
db.rename_column('app_foo', 'full_name', 'name')
The first argument of db.rename_column
is the table name, so it's important to remember how Django creates table names:
Django automatically derives the name of the database table from the name of your model class and the app that contains it. A model's database table name is constructed by joining the model's "app label" -- the name you used in manage.py startapp -- to the model's class name, with an underscore between them.
In the case where you have a multi-worded, camel-cased model name, such as ProjectItem, the table name will be app_projectitem
(i.e., an underscore will not be inserted between project
and item
even though they are camel-cased).
Best Answer
To answer your first question, the simple model/table rename is pretty straightforward. Run the command:
(Update 2: try
--auto
instead of--empty
to avoid the warning below. Thanks to @KFB for the tip.)If you're using an older version of south, you'll need
startmigration
instead ofschemamigration
.Then manually edit the migration file to look like this:
You can accomplish this more simply using the
db_table
Meta option in your model class. But every time you do that, you increase the legacy weight of your codebase -- having class names differ from table names makes your code harder to understand and maintain. I fully support doing simple refactorings like this for the sake of clarity.(update) I just tried this in production, and got a strange warning when I went to apply the migration. It said:
I answered "no" and everything seemed to be fine.