How to move a model between two Django apps (Django 1.7)
Asked Answered
C

13

158

So about a year ago I started a project and like all new developers I didn't really focus too much on the structure, however now I am further along with Django it has started to appear that my project layout mainly my models are horrible in structure.

I have models mainly held in a single app and really most of these models should be in their own individual apps, I did try and resolve this and move them with south however I found it tricky and really difficult due to foreign keys ect.

However due to Django 1.7 and built in support for migrations is there a better way to do this now?

Cavil answered 3/9, 2014 at 15:36 Comment(3)
You might want to consider changing the accepted answer.Frech
For people coming across this in the future: Django 3.x here, and the approach detailed at realpython.com/move-django-model/… worked for me. I had multiple foreign keys between models within the old app, and models in the new app.Squab
If the model you want to move is a custom User model (or any other model referenced insettings.py and having relations to it), the move becomes more complicated. See #69473728 for detailsHesitate
T
33

I am removing the old answer as may result in data loss. As ozan mentioned, we can create 2 migrations one in each app. The comments below this post refer to my old answer.

First migration to remove model from 1st app.

$ python manage.py makemigrations old_app --empty

Edit migration file to include these operations.

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

Second migration which depends on first migration and create the new table in 2nd app. After moving model code to 2nd app

$ python manage.py makemigrations new_app 

and edit migration file to something like this.

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
Transmigrate answered 3/9, 2014 at 19:13 Comment(7)
I do have existing data and a lot of it which I simply cannot lose, it is possibly to do it with this?Cavil
@KevinChristopherHenry Modified the code. This preserves the existing data.Transmigrate
@SamBuckingham Yes, you can try with the modified code to migrate without losing the data.Transmigrate
I think that is going to be the best way really, thank you for all the help guys it has been brilliant.Cavil
IMO this is a wrong solution, basic assumption of migrations is that if you run ./manage.py migrate everything will end in good state. Manually faking migrations is IMO a wrong way.Phil
in new app migration file can have all fields declarations and run migrate with --fake-init flag and there is no problem with foreign keysPhilippe
Each time I run "makemigrations" I get a migration to rename the table to (default) from the new app : Rename table for themodel to (default) Any solution for that?Silicate
F
379

This can be done fairly easily using migrations.SeparateDatabaseAndState. Basically, we use a database operation to rename the table concurrently with two state operations to remove the model from one app's history and create it in another's.

Remove from old app

python manage.py makemigrations old_app --empty

In the migration:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

Add to new app

First, copy the model to the new app's model.py, then:

python manage.py makemigrations new_app

This will generate a migration with a naive CreateModel operation as the sole operation. Wrap that in a SeparateDatabaseAndState operation such that we don't try to recreate the table. Also include the prior migration as a dependency:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
Finespun answered 20/10, 2014 at 18:30 Comment(16)
I did this, but when I run "makemigrations" on the newapp after this, it generates a AlterModelTable migration renaming it to None.Carpentry
Found a way to solve my problem based on these instructions. The problem is more complicated if you have foreign key references that are required fields. I had to add a couple steps to move the references over.Hindquarter
@halfnibble what did you do? EDIT: NVM solution here: #28054437Flutterboard
@Yoshi9143 I had to create a few custom migrations in the following order. First, I made a migration with only "database_operations" to rename tables to match Django's naming convention for the new app. Then, a standard migration to alter FK fields to new app.model fields. Finally, a migration with only "state_operations" to remove references to old app.model fields. In short, a logical nightmare.Hindquarter
Due to multiple requests, I have created a detailed answer on FK model migrations with a GitHub example. #30601607Hindquarter
Besides, the FKs issues pointed out in @otranzer answer, I've run over other 2: a) unique_together index gets renamed, so in the old_app migration, we should remove the index, so it cqn be recreated properly. b) in general, we are keeping the model, so we don't want to delete the associated content_type and recreate a new one. it's enough to add a RunPython operation in the beginning of the old_app migration to rename the app_label from old to new.Hegel
@DiegoPonciano: I had the same problem. In the "options" dict for the new_app migration, the table name shouldn't be qualified with the app name. So it should be "'db_table': 'themodel',".Mayramays
@halfnibble explanation on the last link he posted is perfect, with good examples and everything. Thanks!Compo
This has worked well for me, apart from a ManyToMany table which had it's name updated, but not the column name of the changing table or the constraints which still reference the old app.Decorticate
Generic Foreign Keys will still break! You'll also need to add a migration to rename the app_label in django_contenttypes to reflect the change.Nebulose
Why not simply move model definition class between apps and just keep Meta.db_table = 'oldapp_modelname'? Will this work? I don't see why not. You can also then add RenameTable migration to rename database table if you wish.Sekyere
Some questions: does manage migrate need to be run between the two steps, and when you say "copy the mode" do you mean copy (i.e. it now exists in both models.py files) or do you mean move (i.e. delete it from oldapp/models.py)?Fungiform
Wow, I can't believe this worked. I moved 5 models at a time, with internal foreign key relationships between them. I had almost prematurely given up because I wouldn't believe it worked. But it works. The table structure looks like those were always two apps. Thanks a bunch, this is awesome. :)Lorenzetti
If the primary key of a model is a sequence, which is by default in django. When we move the model to another app, is it fine to keep the sequence table as it is or anything required to be done there?Evanston
this answers is just missing the sequence part. there is a blog post containing this bit: marcela-campo.blogspot.com/2015/01/… ozan maybe you could update your post :)Abbasid
What happens if you migrate a table on which has a related key? By copying the old model to the new app model, you get (fields.E304) Reverse accessor for 'InvitationCode.user' clashes with reverse accessor for 'InvitationCode.user'., how would you proceed?Estreat
T
33

I am removing the old answer as may result in data loss. As ozan mentioned, we can create 2 migrations one in each app. The comments below this post refer to my old answer.

First migration to remove model from 1st app.

$ python manage.py makemigrations old_app --empty

Edit migration file to include these operations.

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

Second migration which depends on first migration and create the new table in 2nd app. After moving model code to 2nd app

$ python manage.py makemigrations new_app 

and edit migration file to something like this.

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
Transmigrate answered 3/9, 2014 at 19:13 Comment(7)
I do have existing data and a lot of it which I simply cannot lose, it is possibly to do it with this?Cavil
@KevinChristopherHenry Modified the code. This preserves the existing data.Transmigrate
@SamBuckingham Yes, you can try with the modified code to migrate without losing the data.Transmigrate
I think that is going to be the best way really, thank you for all the help guys it has been brilliant.Cavil
IMO this is a wrong solution, basic assumption of migrations is that if you run ./manage.py migrate everything will end in good state. Manually faking migrations is IMO a wrong way.Phil
in new app migration file can have all fields declarations and run migrate with --fake-init flag and there is no problem with foreign keysPhilippe
Each time I run "makemigrations" I get a migration to rename the table to (default) from the new app : Rename table for themodel to (default) Any solution for that?Silicate
D
30

I encountered the same problem. Ozan's answer helped me a lot but unfortunately was not enough. Indeed I had several ForeignKey linking to the model I wanted to move. After some headache I found the solution so decided to post it to solve people time.

You need 2 more steps:

  1. Before doing anything, change all your ForeignKey linking to TheModel into Integerfield. Then run python manage.py makemigrations
  2. After doing Ozan's steps, re-convert your foreign keys: put back ForeignKey(TheModel)instead of IntegerField(). Then make the migrations again (python manage.py makemigrations). You can then migrate and it should work (python manage.py migrate)

Hope it helps. Of course test it in local before trying in production to avoid bad suprises :)

Delk answered 14/4, 2015 at 8:29 Comment(3)
what about the ManyToManyField relationships??Malone
@Malone great comment, I would assume by adding a specific through model only for the purpose of migrations. Great deal of work required to leave data intact...Monochasium
Since a many-to-many relationship is usually just a table with two foreign keys, from an SQL point of view you can apply the trick of this answer. But in order to achieve this only via Django, one approach I can think of would be along the lines of @Finespun answer, except the first step would be to duplicate the tables involved in MTM relationship (one version of the dupes in each app), migrate all the Foreign Keys to the new app, and only then delete the dupes in the old app. Disclaimer: I haven't tested :)Jonathonjonati
N
16

I get nervous hand-coding migrations (as is required by Ozan's answer) so the following combines Ozan's and Michael's strategies to minimize the amount of hand-coding required:

  1. Before moving any models, make sure you're working with a clean baseline by running makemigrations.
  2. Move the code for the Model from app1 to app2
  3. As recommended by @Michael, we point the new model to the old database table using the db_table Meta option on the "new" model:

    class Meta:
        db_table = 'app1_yourmodel'
    
  4. Run makemigrations. This will generate CreateModel in app2 and DeleteModel in app1. Technically, these migrations refer to the exact same table and would remove (including all data) and re-create the table.

  5. In reality, we don't want (or need) to do anything to the table. We just need Django to believe that the change has been made. Per @Ozan's answer, the state_operations flag in SeparateDatabaseAndState does this. So we wrap all of the migrations entries IN BOTH MIGRATIONS FILES with SeparateDatabaseAndState(state_operations=[...]). For example,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]
    

    becomes

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. You also need to make sure the new "virtual" CreateModel migration depends on any migration that actually created or altered the original table. For example, if your new migrations are app2.migrations.0004_auto_<date> (for the Create) and app1.migrations.0007_auto_<date> (for the Delete), the simplest thing to do is:

    • Open app1.migrations.0007_auto_<date> and copy its app1 dependency (e.g. ('app1', '0006...'),). This is the "immediately prior" migration in app1 and should include dependencies on all of the actual model building logic.
    • Open app2.migrations.0004_auto_<date> and add the dependency you just copied to its dependencies list.

If you have ForeignKey relationship(s) to the model you're moving, the above may not work. This happens because:

  • Dependencies are not automatically created for the ForeignKey changes
  • We do not want to wrap the ForeignKey changes in state_operations so we need to ensure they are separate from the table operations.

NOTE: Django 2.2 added a warning (models.E028) that breaks this method. You may be able to work around it with managed=False but I have not tested it.

The "minimum" set of operations differ depending on the situation, but the following procedure should work for most/all ForeignKey migrations:

  1. COPY the model from app1 to app2, set db_table, but DON'T change any FK references.
  2. Run makemigrations and wrap all app2 migration in state_operations (see above)
    • As above, add a dependency in the app2 CreateTable to the latest app1 migration
  3. Point all of the FK references to the new model. If you aren't using string references, move the old model to the bottom of models.py (DON'T remove it) so it doesn't compete with the imported class.
  4. Run makemigrations but DON'T wrap anything in state_operations (the FK changes should actually happen). Add a dependency in all the ForeignKey migrations (i.e. AlterField) to the CreateTable migration in app2 (you'll need this list for the next step so keep track of them). For example:

    • Find the migration that includes the CreateModel e.g. app2.migrations.0002_auto_<date> and copy the name of that migration.
    • Find all migrations that have a ForeignKey to that model (e.g. by searching app2.YourModel to find migrations like:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
      
    • Add the CreateModel migration as as a dependency:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
      
  5. Remove the models from app1

  6. Run makemigrations and wrap the app1 migration in state_operations.
    • Add a dependency to all of the ForeignKey migrations (i.e. AlterField) from the previous step (may include migrations in app1 and app2).
    • When I built these migrations, the DeleteTable already depended on the AlterField migrations so I didn't need to manually enforce it (i.e. Alter before Delete).

At this point, Django is good to go. The new model points to the old table and Django's migrations have convinced it that everything has been relocated appropriately. The big caveat (from @Michael's answer) is that a new ContentType is created for the new model. If you link (e.g. by ForeignKey) to content types, you'll need to create a migration to update the ContentType table.

I wanted to cleanup after myself (Meta options and table names) so I used the following procedure (from @Michael):

  1. Remove the db_table Meta entry
  2. Run makemigrations again to generate the database rename
  3. Edit this last migration and make sure it depends on the DeleteTable migration. It doesn't seem like it should be necessary as the Delete should be purely logical, but I've run into errors (e.g. app1_yourmodel doesn't exist) if I don't.
Norther answered 26/2, 2018 at 23:16 Comment(6)
This worked perfectly, thank you! I do not think that editing the last migration matters as this is at the bottom of the dependency tree anyway.Hardship
Good answer! I think you need to add a closing parenthesis to migrations.SeparateDatabaseAndState, right?Tesstessa
This worked for me. I also didn't edit the last migration (step 3, the very last line of the entire answer) like @JamesMeakin and it still worked fineDowel
in the second scenario, the one with FKs, second step failed for me with an error that makes sense: table_name: (models.E028) db_table 'table_name' is used by multiple models: app1.Model, app2.Model.Lilylilyan
I've used the procedure a couple times. If you compare the documentation for 2.2 (docs.djangoproject.com/en/2.2/ref/checks) and 2.1 (docs.djangoproject.com/en/2.1/ref/checks), you can see it was added in 2.2. It may be possible to work around with managed=False but I'm in no place to check.Norther
it worked perfectly. I have tried on Django 2.2. Thanks youCoadjutor
T
15

How I did it (tested on Django==1.8, with postgres, so probably also 1.7)

Situation

app1.YourModel

but you want it to go to: app2.YourModel

  1. Copy YourModel (the code) from app1 to app2.
  2. add this to app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. A new migration (e.g. 0009_auto_something.py) is made in app2 with a migrations.CreateModel() statement, move this statement to the initial migration of app2 (e.g. 0001_initial.py) (it will be just like it always have been there). And now remove the created migration = 0009_auto_something.py

  5. Just as you act, like app2.YourModel always has been there, now remove the existence of app1.YourModel from your migrations. Meaning: comment out the CreateModel statements, and every adjustment or datamigration you used after that.

  6. And of course, every reference to app1.YourModel has to be changed to app2.YourModel through your project. Also, don't forget that all possible foreign keys to app1.YourModel in migrations have to be changed to app2.YourModel

  7. Now if you do $ python manage.py migrate, nothing has changed, also when you do $ python manage.py makemigrations, nothing new has been detected.

  8. Now the finishing touch: remove the Class Meta from app2.YourModel and do $ python manage.py makemigrations app2 && python manage.py migrate app2 (if you look into this migration you'll see something like this:)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    

table=None, means it will take the default table-name, which in this case will be app2_yourmodel.

  1. DONE, with data saved.

P.S during the migration it will see that that content_type app1.yourmodel has been removed and can be deleted. You can say yes to that but only if you don't use it. In case you heavily depend on it to have FKs to that content-type be intact, don't answer yes or no yet, but go into the db that time manually, and remove the contentype app2.yourmodel, and rename the contenttype app1.yourmodel to app2.yourmodel, and then continue by answering no.

Trumpetweed answered 11/6, 2015 at 15:1 Comment(4)
While this solution is definitely "hackier" than @ozan's and it definitely needs more editing, it worked nicely for me (and it's OK to edit migrations - they are supposed to be editable, according to the docs).Fluky
Possibly also use the app_label = 'app1' meta option.Monochasium
Genius! This worked great for me for ForeignKey relationships. I suppose this will also work for ManyToMany fields as well.Frech
I followed your steps but the field in some model belonging to app1 consists of a Foreign Key with a recursive relationship to the model(myModel) to be moved. Like field1 = models.ForeignKey('app1.myModel'). When I migrate, i get a ValueError stating that field1 was declared with a lazy reference to 'app1.myModel' but app 'app1' doesn't provide model 'MyModel'Jangro
M
3

Another hacky alternative if the data is not big or too complicated, but still important to maintain, is to:

  • Get data fixtures using manage.py dumpdata
  • Proceed to model changes and migrations properly, without relating the changes
  • Global replace the fixtures from the old model and app names to the new
  • Load data using manage.py loaddata
Monochasium answered 20/6, 2017 at 6:45 Comment(0)
I
1

You can try the following (untested):

  1. move the model from src_app to dest_app
  2. migrate dest_app; make sure the schema migration depends on the latest src_app migration (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files)
  3. add a data migration to dest_app, that copies all data from src_app
  4. migrate src_app; make sure the schema migration depends on the latest (data) migration of dest_app -- that is: the migration of step 3

Note that you will be copying the whole table, instead of moving it, but that way both apps don't have to touch a table that belongs to the other app, which I think is more important.

Intuitionism answered 13/10, 2014 at 13:18 Comment(0)
H
1

Copied from my answer at https://mcmap.net/q/121781/-move-models-between-django-1-8-apps-with-required-foreignkey-references

In case you need to move the model and you don't have access to the app anymore (or you don't want the access), you can create a new Operation and consider to create a new model only if the migrated model does not exist.

In this example I am passing 'MyModel' from old_app to myapp.

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]
Housewarming answered 20/11, 2017 at 13:23 Comment(1)
Please don't add the same answer to multiple questions. Answer the best one and flag the rest as duplicates. See Is it acceptable to add a duplicate answer to several questions?Mckown
C
1

Introduction

I had to move the model from one app to another.

Tried various methods, such as:

  • adding class Meta: db_table = 'app1_yourmodel',
  • migrations.SeparateDatabaseAndState,
  • renaming the table name by hand,
  • copying the data during the migration process by running raw sql queries with RunSQL,
  • etc

But after each case I would face some kind of error would occur.

I will describe a method that I have used and that has worked for me just fine.

It was very good to practice this in my DEV environment, having copies of sqlite3 DB files and to be able to visually see the content of the DB as I was doing this.

But for those that do not have access to sqlite3 DB file or can not preview their content in the GUI as I could in VScode or in sqlitebrowser, I will write as detailed instructions as I can below. They helped me to execute the same commands in my PROD server afterwards(did not have a gui as well).

note: you can ignore the --settings=settings.development everywhere you see it, you will not need it.

If you see a command like such:

python manage.py makemigrations base_app --settings=settings.development

It means that you have to run your command like that:

python manage.py makemigrations base_app

And change the "base_app" to your app name.

My preferred method

So what I will do is this:

  • Move the models.py file to a new app
  • "makemigrations" for new app only
  • "migrate" the changes for the new app only
  • Prepare for data copying
  • Run some raw sql commands to copy the data to the new app
  • "makemigrations" of the old app to delete the old tables
  • Final check

Move the models.py file to a new app

my old app = base_app my new app = website_fixes_app

Move the models.py file from the old app to the new app. Old app should not have any models.py file left.

"makemigrations" for new app only

Make sure you have a copy of your current db!

makemigrations FOR THE NEW APP only, it will create the migrations file for new model file. You can see that I specify the new app name in the makemigrations command below, so the makemigrations would not happen globally, but only for the chosen app.

python manage.py makemigrations website_fixes_app --settings=settings.development

"migrate" the changes for the new app only

The new tables will be created. Notice I only again specify the new app name.

python manage.py migrate website_fixes_app --settings=settings.development

Now you have two sets of tables. Old tables and new tables. Rows are the same in both tables.

Now the fun part. Copy the data from one to another!

Prepare for data copying

Since I use sqlite3 db I need a "driver" of some sort to connect to the DB and run queries. If you are using a different DB - you might have to use a different driver. SQL commands should be similar as well.

note: or do it in db viewer for sqlite app if you can. It's better to SEE the actual changes and content in the db.

sudo apt install sqlite3
sqlite3 your_db_filename.sqlite3

Confirm that the tables were created by the migration.

# open the db
sqlite3 your_db_filename.sqlite3

Print out the table names, notice that the old tables as well as the new tables exist.

.tables
SELECT * FROM old_table_name;

Run some raw sql commands to copy the data to the new app

Open the db once again if you have exited it before.

sqlite3 your_db_filename.sqlite3

Run these SQL commands. Adjust the fields and table names to your table names.

An example:

INSERT INTO your_new_table_name (id, title)
SELECT id, title
FROM your_old_table_name;

# then to check:
SELECT * FROM your_new_table_name;

In my case I had to run these 3 queries:

INSERT INTO website_fixes_app_websitefix (id, title, description, date_created, status)
SELECT id, title, description, date_created, status
FROM base_app_websitefix;

# check:
SELECT * FROM website_fixes_app_websitefix;

INSERT INTO website_fixes_app_websitefix_tags (id, websitefix_id, websitefixtag_id)
SELECT id, websitefix_id, websitefixtag_id
FROM base_app_websitefix_tags;

check:
SELECT * FROM website_fixes_app_websitefix_tags;

INSERT INTO website_fixes_app_websitefixtag (id, name)
SELECT id, name
FROM base_app_websitefixtag;

check:
SELECT * FROM website_fixes_app_websitefixtag;

exit the sqlite3 with CTRL + D.

"makemigrations" of the old app to delete the old tables

If your new tables contain the data of the old tables, we can remove the old tables (good that you have a copy of your db, so no worries here, we can always go back.).

Make migrations of the old app to remove the old tables from the db.

You can see that I am now not making global migrations again, I am just focusing on one app - my old app (base_app).

python manage.py makemigrations base_app --settings=settings.development
python manage.py migrate base_app --settings=settings.development

Final check

check if the old tables were removed:

sqlite3 your_db_filename.sqlite3
.tables
SELECT * FROM old_table_name;

Start your server and see if the app runs fine. If you have adjusted your app to read from the new tables - then it should work flawlesly.

We can now delete the migrations folder from the old app.

Now whenever you will have to modify the models in the new app - you can do so with no problems. No errors will occur.

Caducous answered 28/7, 2023 at 5:24 Comment(0)
L
0

This is tested roughly, so do not forget to backup your DB!!!

For example, there are two apps: src_app and dst_app, we want to move model MoveMe from src_app to dst_app.

Create empty migrations for both apps:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

Let's assume, that new migrations are XXX1_src_app_new and XXX1_dst_app_new, previuos top migrations are XXX0_src_app_old and XXX0_dst_app_old.

Add an operation that renames table for MoveMe model and renames its app_label in ProjectState to XXX1_dst_app_new. Do not forget to add dependency on XXX0_src_app_old migration. The resulting XXX1_dst_app_new migration is:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

Add dependency on XXX1_dst_app_new to XXX1_src_app_new. XXX1_src_app_new is no-op migration that is needed to make sure that future src_app migrations will be executed after XXX1_dst_app_new.

Move MoveMe from src_app/models.py to dst_app/models.py. Then run:

python manage.py migrate

That's all!

Lanctot answered 12/9, 2014 at 12:53 Comment(1)
Note that this code is probably only useful for django 1.7. Trying this in django 2.0 will not work. This also means that using this mechanism for moving models adds maintenance overhead to upgrading your django version.Filiate
H
0

Lets say you are moving model TheModel from app_a to app_b.

An alternate solution is to alter the existing migrations by hand. The idea is that each time you see an operation altering TheModel in app_a's migrations, you copy that operation to the end of app_b's initial migration. And each time you see a reference 'app_a.TheModel' in app_a's migrations, you change it to 'app_b.TheModel'.

I just did this for an existing project, where I wanted to extract a certain model to an reusable app. The procedure went smoothly. I guess things would be much harder if there were references from app_b to app_a. Also, I had a manually defined Meta.db_table for my model which might have helped.

Notably you will end up with altered migration history. This doesn't matter, even if you have a database with the original migrations applied. If both the original and the rewritten migrations end up with the same database schema, then such rewrite should be OK.

Hetaerism answered 13/10, 2015 at 7:11 Comment(0)
M
0
  1. change the names of old models to ‘model_name_old’
  2. makemigrations
  3. make new models named ‘model_name_new’ with identical relationships on the related models (eg. user model now has user.blog_old and user.blog_new)
  4. makemigrations
  5. write a custom migration that migrates all the data to the new model tables
  6. test the hell out of these migrations by comparing backups with new db copies before and after running the migrations
  7. when all is satisfactory, delete the old models
  8. makemigrations
  9. change the new models to the correct name ‘model_name_new’ -> ‘model_name’
  10. test the whole slew of migrations on a staging server
  11. take your production site down for a few minutes in order to run all migrations without users interfering

Do this individually for each model that needs to be moved. I wouldn’t suggest doing what the other answer says by changing to integers and back to foreign keys There is a chance that new foreign keys will be different and rows may have different IDs after the migrations and I didn’t want to run any risk of mismatching ids when switching back to foreign keys.

Malone answered 4/4, 2017 at 5:25 Comment(0)
U
0

In case you do not have custom migrations, one could just recreate everything. Remove everything, migrate everything freshly. (DO IT ON YOUR OWN RISK)

Preparations:

  1. Ensure all models have:
class Meta:
    db_table = 'just_model_name'  # without app_name prefix
  1. VERY IMPORTANT to make sure that all your previous-current migrations are created and applied to all your servers / test-servers, team mates, etc. Pay time here.
  2. Move everything you want.
  3. Reload django and check that nothing is mixed up and it still stars normally.

Job:

  1. Remove all migration files, so there stands empty migrations/ folders with only __init__.py files.
  2. Remove all migration history. Just clear django_migrations table or:
from django.db.migrations.recorder import MigrationRecorder
MigrationRecorder.Migration.objects.all().delete()
  1. python manage.py makemigrations again.
  2. python manage.py migrate --fake.

Probably you'll get some errors here, like "previous dependency migrations..., could not be applied... because of an app, bla-bla-bla".

And that errors will guide you, so, probably you'll have to migrate by app:

python manage.py migrate myapp1 --fake
python manage.py migrate --fake  # will give another guiding error
python manage.py migrate myapp2 --fake

I do not say it's best or even good solution. But I am happy with it. Surely it preserves data.

Ullund answered 6/11, 2023 at 1:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.