32

I am renaming an application to a more suitable name. In doing so, I want to ensure that South properly migrates the database (renames database tables and changes references in django_content_type or south_migrationhistory). I know how to migrate a model to a different app, but when I try rename the app itself, South does not recognize the migration history properly.

Undesirable solution: In renaming old_app to new_app I could leave old_app/migrations intact and add new migrations to this directory to migrate the database to reference new_app.

If possible I would prefer to delete the directory old_app entirely. I have not yet thought of a better solution to this problem.

What is the best way to rename an app with Django South without losing data?

Community
  • 1
  • 1
Trey Hunner
  • 8,794
  • 4
  • 42
  • 82
  • 1
    How about leaving the database completely as it is, but just using `db_table` in the models' inner `Meta` class to refer to the old names? – Daniel Roseman Dec 30 '10 at 23:12
  • That would work for the models, but I would still have the problem that South would not see any of the migrations as being performed for `new_app` and would try to run through all of them all over again. – Trey Hunner Dec 30 '10 at 23:22

6 Answers6

44

I agree with Laksham that you should avoid this situation. But sometimes, we have to. I've faced this situation in the past and I've managed it this way.

If you want to avoid losing data you can dump the old application data into a json file.

python manage.py dumpdata old_app --natural --indent=4 1> old_app.json

Note the --natural option that will force the content types to be exported with their natural keys (app_name, model)

Then you can create a small command to open this json file and to replace all the old_app references with the new_app.

Something like this should work

class Command(BaseCommand):
    help = u"Rename app in json dump"

    def handle(self, *args, **options):
        try:
            old_app = args[0]
            new_app = args[1]
            filename = args[2]
        except IndexError:
            print u'usage :', __name__.split('.')[-1], 'old_app new_app dumpfile.json'
            return

        try:
            dump_file = open(filename, 'r')
        except IOError:
            print filename, u"doesn't exist"
            return

        objects = json.loads(dump_file.read())
        dump_file.close()

        for obj in objects:
            obj["model"] = obj["model"].replace(old_app, new_app, 1)

            if obj["fields"].has_key("content_type") and (old_app == obj["fields"]["content_type"][0]):
                obj["fields"]["content_type"][0] = new_app

        dump_file = open(filename, 'w')
        dump_file.write(json.dumps(objects, indent=4))
        dump_file.close()

Then rename the application, change the name in INSTALLED_APPS.

Then, you should remove all south migrations, regenerate and apply an initial migration for the new app. Then run the SQL command:

update django_content_type set app_label='new_app' where app_label='old_app'

Then launch a south migrate for the new app in order to create the tables and load the json file.

python manage.py loaddata old_app.json

I've done something similar on a project and it seems to work ok.

I hope it helps

luc
  • 37,543
  • 21
  • 117
  • 168
  • 21
    +1 For recipe. Names are important: renaming shouldn't be avoided just because it's difficult. – tawmas Sep 14 '11 at 14:04
  • +1 @luc This is great. I took the liberty to fix 2 bugs in this code. You did `obj["model"].replace(old_app, new_app)` without assigning the result to anything, so this statement did nothing. I replaced it with `obj["model"] = obj["model"].replace(old_app, new_app, 1)` so the model is actually changed, plus I limited the number of replaces to 1, just in case the model name includes the app name. For example if the app is called `greatapp` and the model is called `greatapp.greatapp_blog_post`. ;-) – MiniQuark Mar 11 '13 at 09:42
  • I suggest you also add the imports at the beginning (`import json` and `from django.core.management.base import BaseCommand`), and also explain where to put this file (for newbies). – MiniQuark Mar 11 '13 at 09:43
  • 3
    Note that before Django 1.5, dumpdata loads everything in memory before dumping, so this solution will not work unless your app stores little data (this is fixed in Django 1.5): you may want to include this warning in your answer. Also, I just ran through all steps, it works fine with some minor modifications: please replace "[...]remove all south migrations and regenerate an initial migration for the new app." by "[...]remove all south migrations, regenerate and apply an initial migration for the new app. Then run "update django_content_type set app_label='new_app' where app_label='old_app'" – MiniQuark Mar 11 '13 at 14:01
  • 3
    Not sure why "don't" is the accepted answer here. I have an app to rename and it's not for giggles ... there is a conflict with another app and Django/South doesn't handle name conflicts well. – Rob Osborne May 08 '13 at 14:07
  • 1
    PyCharm has very good support for renaming apps and handles most references automatically. Except for the DB, but you can easily create your own migration to rename the tables. – dom0 Sep 30 '13 at 09:38
  • I feel like dumping the data -- especially if you have a lot of data -- is not nearly as desirable as simply renaming the table names, no? – abhillman Jul 31 '14 at 22:24
24

It is possible to rename an app. As example project, see:

https://github.com/ASKBOT/django-south-app-rename-example

Basically, there are 2 migrations. First the tables are renamed using a db.rename_table(), and next the content types are updated. This can be combined into one migration by checking for if not db.dry_run:. See How do I migrate a model out of one django app and into a new one? for an example of that.

For a initial migrations, you can directly rename the existing tables, if they are there:

if 'old_app_table_name' in connection.introspection.table_names():
    db.rename_table('old_app_table_name', 'new_app_table_name')
else:
    # Create new app tables directly.

For tables with more migrations, you may need to check whether the old migration name was already applied:

from south.models import MigrationHistory
if MigrationHistory.objects.exists(app_name='old_app', migration='0001_initial'):
    return

Lastly, I recommend using an IDE (e.g. a PyCharm trial) to rename the package (right click, refactor -> rename on the package) because it will update all usages across the application for you, including the URLconf, settings and imports.

Community
  • 1
  • 1
vdboor
  • 19,540
  • 11
  • 74
  • 91
8

After some plugging away, I came up with this. Should take care of it. https://gist.github.com/jamesmfriedman/6168003

jamesmfriedman
  • 564
  • 5
  • 6
  • 3
    Also, I believe it has to be renamed as 00xx_rename_table_migration.py and put it in my_app/migrations where xx = number of migrations + 1, so that I can simply use it as: "./manage.py migrate my_app" and "./manage.py migrate my_app (00xx -1)" to roll back. Please correct me if it is meant to be used in a different way. – Humble Learner Sep 09 '13 at 17:28
  • Do I understand this correctly that you _first_ have to rename the app, and _then_ you apply this migration? – Torsten Bronger Sep 12 '14 at 09:02
2

Disclaimer: this isn't probably the ideal way to do it, but this is definitely easier than a lot of other approaches suggested elsewhere/earlier.

If you're using PyCharm you can try the following solution.

  • right click on the app directory in your Django project
  • Refactor > Rename > Enter new app name (new_app)
  • Check both Search for references and Search in comments and strings, Scope Project Files > Refactor
  • This would show you where the files would be renamed > Refactor
  • If you are not happy with your Project name, you can do the same for the Project name as well

Now comes the problem.

python manage.py makemigrations

python manage.py migrate

What would this do?

  • This would basically create a new app and apply all the migrations to this new app
  • However, your data still resides in the old app (old_app)
  • We need to bring that to the new_app tables

In your DB, we need to

  1. insert the data into the new_app tables by copying it from the old_app tables

INSERT INTO new_app_table_name SELECT * FROM old_app_table_name;

You have to do this for all the models/tables in your app

  1. Reset the sequence of the tables to avoid getting the primary key violation error in django

SELECT setval('new_app_table_name_id_seq', (SELECT MAX(id) FROM new_app_table_name));

You have to do this for all the tables from Step 1.

I don't want to be ignorant and claim that it should be as easy as this. My project and model structure were fairly complicated but this worked like charm for me. However, if your project structure is too complicated only parts of this might work for you.

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

1

Disclaimer: This answer is for those who do not care about migration history and have already messed up by renaming the app, completely forgetting about migrations (like I did). Worked for me.

After changing folder name, dealing with all imports in your code and changing corresponding app name in settings.INSTALLED_APPS, just delete all previous migrations folder. Then make an initial one like this

manage.py schemamigration new_app --initial

Then, when applying it, fake it like this

manage.py migrate new_app 0001 --fake

Do not forget to --fake it, otherwise you might end up losing data

All further migrations will work just fine

manage.py migrate new_app 0002

Also you can delete from south_migrationhistory where app_name = "old_app"

-6

I wouldn't mess with the app names. You refer to the app names literally everywhere. URL confs, settings, other apps, templates etc.

The way django is designed, correspondingly south, assumes there is no need to change the app names. - name your projects what you want. You don't refer to it anywhere. Changing app names is cumbersome. Your undesirable solution is the best solution I see, if you really want to rename your app.

For what it is worth, you can always use the python import as to import the app in a different name, if you so desire.

Lakshman Prasad
  • 76,135
  • 46
  • 128
  • 164