143

I've been hunting for an answer to this on South's site, Google, and SO, but couldn't find a simple way to do this.

I want to rename a Django model using South. Say you have the following:

class Foo(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Foo)

and you want to convert Foo to Bar, namely

class Bar(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Bar)

To keep it simple, I'm just trying to change the name from Foo to Bar, but ignore the foo member in FooTwo for now.

What's the easiest way to do this using South?

  1. I could probably do a data migration, but that seems pretty involved.
  2. Write a custom migration, e.g. db.rename_table('city_citystate', 'geo_citystate'), but I'm not sure how to fix the foreign key in this case.
  3. An easier way that you know?
vaughnkoch
  • 1,779
  • 2
  • 13
  • 17
  • 5
    See also http://stackoverflow.com/questions/3235995/django-how-to-rename-a-model-field-using-south for renaming a *model field* rather than a *model*. – Mechanical snail Sep 29 '11 at 04:31
  • Optimized solution for Django >= 1.8 http://stackoverflow.com/questions/25091130/django-migration-strategy-for-renaming-a-model-and-relationship-fields – Chemical Programmer Mar 17 '16 at 01:56

4 Answers4

131

To answer your first question, the simple model/table rename is pretty straightforward. Run the command:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(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 of schemamigration.

Then manually edit the migration file to look like this:

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('yourapp_foo', 'yourapp_bar')


    def backwards(self, orm):
        db.rename_table('yourapp_bar','yourapp_foo')   

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:

The following content types are stale and need to be deleted:

    yourapp | foo

Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.

I answered "no" and everything seemed to be fine.

awidgery
  • 1,896
  • 1
  • 22
  • 36
Leopd
  • 41,333
  • 31
  • 129
  • 167
  • When I do this, I lose all the rows in foo. What am I doing wrong? – jMyles Nov 23 '10 at 08:50
  • @Justin -- hard to say without lots more detail. Write a new SO question and link to it in a comment here? – Leopd Nov 23 '10 at 15:57
  • This seems to break old migrations that depended on `Foo`. – luqui Jul 25 '11 at 06:44
  • 4
    I used this technique on 9/2/2011 without getting any errors. Maybe a newer version of South solved the problem with the errors. – Brian Tol Sep 02 '11 at 18:58
  • 1
    Thanks for keeping this updated! Jian's answer below says it is important to keep the "send_create_signal" calls, do you have any knowledge about that? If you agree, it would be great to update your example migration. – mrooney Oct 12 '12 at 20:52
  • This answer might have saved more than one lives. :) – Bibhas Debnath Jan 21 '13 at 12:56
  • 5
    Beware that this will not rename indexes on that table. If you create a new table in the future with the same name as the old table, you can get errors from index names colliding. We were using this technique, but from now on we are going to explicitly create the new table, migrate the data, then delete the old table. – Jeremy Feb 21 '13 at 18:03
  • 3
    I was able to avoid the error message that Leopd by creating the schema migration using --auto instead of --empty. I then edited the migration file, changing the deletion/creation of the tables into a db.rename_table() call. This seems to have worked very well. – KFB Jun 04 '10 at 05:37
  • 3
    Column names in automatically generated tables, such as M2M tables to the original model, are also not migrated by this method. – spookylukey Nov 22 '13 at 15:17
  • Would be nice if you could add a friendly warning to the top of your answer, that this is a quite hacky way of renaming, as south does not directly support this, and sadly there is no better option? that would clearly set expectations... (I wouldnt be particularly happy, if someone would try this as best practice on a huge production system...). apart from this, solution is nice. thx :) – Vajk Hermecz Apr 15 '14 at 08:23
  • Can someone confirm whether this would or would not destroy all data from the original table? – mgPePe Jul 17 '14 at 21:19
  • 1
    I can confirm that it works (and doesn't destroy data), though AFAICT it doesn't rename FKs, etc. – Matt Luongo Jul 27 '14 at 22:24
  • 1
    How dated is this answer / does it work in Django 1.8? I'm not seeing `schemamigration` in the list of available django manage commands... – TheGrimmScientist Oct 04 '15 at 19:56
  • On what package does `rename_table` belongs? I cannot found it in Django 3.2! – jlandercy Aug 20 '21 at 08:58
67

Make the changes in models.py and then run

./manage.py schemamigration --auto myapp

When you inspect the migration file, you'll see that it deletes a table and creates a new one

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Deleting model 'Foo'                                                                                                                      
        db.delete_table('myapp_foo')

        # Adding model 'Bar'                                                                                                                        
        db.create_table('myapp_bar', (
        ...
        ))
        db.send_create_signal('myapp', ['Bar'])

    def backwards(self, orm):
        ...

This is not quite what you want. Instead, edit the migration so that it looks like:

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Renaming model from 'Foo' to 'Bar'                                                                                                                      
        db.rename_table('myapp_foo', 'myapp_bar')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(
                app_label='myapp', model='foo').update(model='bar')

    def backwards(self, orm):
        # Renaming model from 'Bar' to 'Foo'                                                                                                                      
        db.rename_table('myapp_bar', 'myapp_foo')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(app_label='myapp', model='bar').update(model='foo')

In the absence of the update statement, the db.send_create_signal call will create a new ContentType with the new model name. But it's better to just update the ContentType you already have in case there are database objects pointing to it (e.g., via a GenericForeignKey).

Also, if you've renamed some columns which are foreign keys to the renamed model, don't forget to

db.rename_column(myapp_model, foo_id, bar_id)
Jian
  • 10,320
  • 7
  • 38
  • 43
  • 2
    I get the error, KeyError: "The model 'contenttype' from the app 'contenttypes' is not available in this migration." Also, I have a django_content_type table, but not a contenttypes table. (Django 1.6) – Seth Apr 08 '14 at 19:51
  • 2
    @Seth I worked around this by doing the updating of the ContentType models in a separate data migration and by adding the ``contenttypes.ContentType`` model to the frozen models by using the ``--frozen`` flag to ``./manage.py datamigration``. For example: ``./manage.py datamigration --frozen contenttypes myapp update_contenttypes``. Then edit myapp_migrations/NNNN_update_contenttypes.py with the content type updating code as specified above. – Geoffrey Hing Jul 22 '14 at 18:52
  • @GeoffreyHing I think the parameter is freeze no frozen. http://south.readthedocs.io/en/latest/ormfreezing.html But thank you so much for your help, it was really helpful. – ccsakuweb Aug 09 '16 at 15:39
5

South can't do it itself - how does it know that Bar represents what Foo used to? This is the sort of thing I'd write a custom migration for. You can change your ForeignKey in code as you've done above, and then it's just a case of renaming the appropriate fields and tables, which you can do any way you want.

Finally, do you really need to do this? I've yet to need to rename models - model names are just an implementation detail - particularly given the availability of the verbose_name Meta option.

Dominic Rodger
  • 97,747
  • 36
  • 197
  • 212
-1

I followed Leopd's solution above. But, that did not change the model names. I changed it manually in the code (also in related models where this is referred as FK). And done another south migration, but with --fake option. This makes model names and table names to be same.

Just realized, one could first start with changing model names, then edit the migrations file before applying them. Much cleaner.

gowthaman
  • 3
  • 2