6

I have a Django app named app1 with models and migrations files. I renamed this app to app2 and I fixed all imports, urls etc... I now have a problem with migrations files and data in tables.

How can I write migrations with the correct way to ensure:

  • New installation => create the new tables
  • Update old versions => create new tables, move data, remove old tables

Note 1: there is several tables with many Foreign Keys.

Here is my progress so far and I am not sure if I am on the good way:

  • I removed all older migrations
  • I ran python manage.py makemigrations to generate new migrations files

After these 2 steps, I can install my application but I still have problems with old version.

Question: What is the best way to migrate data?

Note 2: I don't use South.

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
Touhami
  • 759
  • 1
  • 11
  • 26
  • How to rename an app is answered step by step [here](https://stackoverflow.com/questions/8408046/how-to-change-the-name-of-a-django-app) – Corto Nov 29 '22 at 07:53

4 Answers4

12

I found a solution that's works

  1. Fix old migrations with new Foreign Keys and new app dependencies.
  2. Force old migrations to create tables with old app name, so for that in migrations.CreateModel.options, add db_table: 'app1_table_name'
  3. In each migration file add replaces = [('app1', 'migration_file_name')]. This will tell to Django that current migration (app2.migration_file_name) will replace the old file, this will prevenent django to execute migrations twice.
  4. Create a migration file to rename tables with migrations.AlterModelTable
GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
Touhami
  • 759
  • 1
  • 11
  • 26
  • 4
    To add to this, I found that django doesn't update the content type when the app is renamed using this method, and will instead create new content types (and permissions) for each app model when the migration is run. This will cause trouble if you're using generic foreignkeys etc. I fixed this by adding a `RunPython` operation before all of the `AlterModelTable`s. My `RunPython` function runs `ContentType = apps.get_model('contenttypes', 'ContentType')` `ContentType.objects.filter(app_label='old_app').update(app_label='new_app')` – DBrowne Feb 07 '18 at 04:22
  • 1
    This is awesome! It would have taken loads of time to figure out the elaborate path through all these things we rarely use. – rschwieb Sep 06 '18 at 17:56
  • This is really useful, I would not have even known to look for these things in the docs. – Shaun Taylor Feb 25 '21 at 14:17
2

This is eloquently answered in this blog post.

The bullet points are:

  1. Rename the folder of the application you want to update.
  2. Update any and import statements to the folder you updated.
  3. Update entries for the django_content_type table to refer to the application's app_label.
  4. Update the table names of any models you haven't explicitly set the table name of. These table names inferred by the application name and need to be updated.
  5. Update entries for the django_migrations table and update the reference for each migration by setting the app field your new app label.
  6. Update any namespaced folder names that are within your /static or /templates folder. For example, you might have ./foo_app/templates/foo_app/index.html and it should be updated to ./bar_app/templates/bar_app/index.html.
smac89
  • 39,374
  • 15
  • 132
  • 179
Derek Adair
  • 21,846
  • 31
  • 97
  • 134
1

Renaming an app is always a tricky issue.

If you do the migration like a simple table renaming migration, at any moment the apps.get_model() for the old app cannot work because the app simply doesn't exist.

I found this answer. I know you are not using south, but I think it might work the same way, just skip the south steps.

Basically, you have to:

  1. Dump the data, before rename, into a json file

  2. Run the script in the answer to rename references in the json file from the app1 to app2

  3. Rename app1 to app2 (all import references, settings.py, etc)

  4. Run the migrations to create the tables for app2

  5. Load the data from json file to the database

  6. Drop the app1 tables

I hope this help.

GabLeRoux
  • 16,715
  • 16
  • 63
  • 81
Thiago Rossener
  • 934
  • 1
  • 6
  • 17
0

Another solution maybe a bit easier and that can be deployed by itself during the migration.

Let's consider the old app old_app and the renamed one new_app.

  1. The first step is to retrieve the SQL commands to create the models in case the new_app is deployed on a fresh database (you might need to merge the migration of the old app to get a single migration file):
python3 ./manage.py sqlmigrate old_app 0001_initial
--
-- Create model MailingList
--
CREATE TABLE `old_mailinglist` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `email` varchar(254) NOT NULL);
  1. The second step is to create a new migration file in the new app
python3 ./manage.py makemigrations new_app --empty --name rename_app
  1. In this new migration file, copy and adapt this code:
# -*- coding: utf-8 -*-
from django.db import connections, migrations

def create_or_update_mailing_list(apps, schema_editor):
    db_alias = schema_editor.connection.alias

    try:
        _ = apps.get_model("old_app", "MailingList")

        print("Rename table MailingList app")
        with connections[db_alias].cursor() as cursor:
            cursor.execute("UPDATE django_content_type SET app_label=new_app WHERE app_label=old_app;")
            cursor.execute("ALTER TABLE old_app_mailinglist RENAME TO new_app_mailinglist;")
    except LookupError:
        print("Create table MailingList")
        with connections[db_alias].cursor() as cursor:
            cursor.execute("CREATE TABLE `new_app_mailinglist` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `email` varchar(254) NOT NULL);")

class Migration(migrations.Migration):
    dependencies = [
    ]

    operations = [
        migrations.RunPython(create_or_update_mailing_list),
    ]
OlivierM
  • 2,820
  • 24
  • 41