How to move a model between two Django apps (Django 1.7)
Solution 1
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)
]
Solution 2
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)
]
Solution 3
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:
- Before doing anything, change all your
ForeignKey
linking toTheModel
intoIntegerfield
. Then runpython manage.py makemigrations
- After doing Ozan's steps, re-convert your foreign keys: put back
ForeignKey(TheModel)
instead ofIntegerField()
. 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 :)
Solution 4
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
- Copy YourModel (the code) from app1 to app2.
-
add this to app2.YourModel:
Class Meta: db_table = 'app1_yourmodel'
$ python manage.py makemigrations app2
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
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.
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
Now if you do $ python manage.py migrate, nothing has changed, also when you do $ python manage.py makemigrations, nothing new has been detected.
-
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.
- 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.
Solution 5
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:
- Before moving any models, make sure you're working with a clean baseline by running
makemigrations
. - Move the code for the Model from
app1
toapp2
-
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'
Run
makemigrations
. This will generateCreateModel
inapp2
andDeleteModel
inapp1
. Technically, these migrations refer to the exact same table and would remove (including all data) and re-create the table.-
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 inSeparateDatabaseAndState
does this. So we wrap all of themigrations
entries IN BOTH MIGRATIONS FILES withSeparateDatabaseAndState(state_operations=[...])
. For example,operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
becomes
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
-
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 areapp2.migrations.0004_auto_<date>
(for theCreate
) andapp1.migrations.0007_auto_<date>
(for theDelete
), the simplest thing to do is:- Open
app1.migrations.0007_auto_<date>
and copy itsapp1
dependency (e.g.('app1', '0006...'),
). This is the "immediately prior" migration inapp1
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 itsdependencies
list.
- Open
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 instate_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:
-
COPY the model from
app1
toapp2
, setdb_table
, but DON'T change any FK references. - Run
makemigrations
and wrap allapp2
migration instate_operations
(see above)- As above, add a dependency in the
app2
CreateTable
to the latestapp1
migration
- As above, add a dependency in the
- 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. -
Run
makemigrations
but DON'T wrap anything instate_operations
(the FK changes should actually happen). Add a dependency in all theForeignKey
migrations (i.e.AlterField
) to theCreateTable
migration inapp2
(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>'), ]
- Find the migration that includes the
Remove the models from
app1
- Run
makemigrations
and wrap theapp1
migration instate_operations
.- Add a dependency to all of the
ForeignKey
migrations (i.e.AlterField
) from the previous step (may include migrations inapp1
andapp2
). - When I built these migrations, the
DeleteTable
already depended on theAlterField
migrations so I didn't need to manually enforce it (i.e.Alter
beforeDelete
).
- Add a dependency to all of the
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):
- Remove the
db_table
Meta entry - Run
makemigrations
again to generate the database rename - Edit this last migration and make sure it depends on the
DeleteTable
migration. It doesn't seem like it should be necessary as theDelete
should be purely logical, but I've run into errors (e.g.app1_yourmodel
doesn't exist) if I don't.
Sam Buckingham
Small website developer in my spare time using Python with Django.
Updated on July 08, 2022Comments
-
Sam Buckingham almost 2 years
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?
-
Babken Vardanyan over 6 yearsYou might want to consider changing the accepted answer.
-
pradeepcep almost 4 yearsFor 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.
-
JanKanis over 2 yearsIf the model you want to move is a custom User model (or any other model referenced in
settings.py
and having relations to it), the move becomes more complicated. See stackoverflow.com/questions/69473228/… for details
-
-
Sam Buckingham over 9 yearsI do have existing data and a lot of it which I simply cannot lose, it is possibly to do it with this?
-
Chillar Anand over 9 years@KevinChristopherHenry Modified the code. This preserves the existing data.
-
Chillar Anand over 9 years@SamBuckingham Yes, you can try with the modified code to migrate without losing the data.
-
Sam Buckingham over 9 yearsI think that is going to be the best way really, thank you for all the help guys it has been brilliant.
-
Remiz over 9 yearsReally good explanation. This should be the answer, with renaming the table you avoid losing any data.
-
Chillar Anand over 9 yearsThis is the best way to do it and it is far better than mine. Added note at top of my answer.
-
Diego Ponciano over 9 yearsI did this, but when I run "makemigrations" on the newapp after this, it generates a AlterModelTable migration renaming it to None.
-
Nostalg.io about 9 yearsFound 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.
-
FinDev about 9 years@halfnibble what did you do? EDIT: NVM solution here: stackoverflow.com/questions/28053937/…
-
Nostalg.io almost 9 years@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.
-
Nostalg.io almost 9 yearsDue to multiple requests, I have created a detailed answer on FK model migrations with a GitHub example. stackoverflow.com/questions/30601107/…
-
tomcounsell almost 9 yearswhat about the ManyToManyField relationships??
-
jb. almost 9 yearsIMO 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. -
Tim over 8 yearsGreat explanation, just saved some head scratching.
-
duduklein over 8 yearsBesides, 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.
-
TimB over 8 years@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',".
-
Rubén Durá Tarí about 8 years@halfnibble explanation on the last link he posted is perfect, with good examples and everything. Thanks!
-
Nostalg.io about 8 years@RubénDuráTarí Glad I could help. :)
-
pgcd almost 8 yearsWhile 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).
-
markwalker_ almost 8 yearsThis 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. -
outofculture about 7 yearsGeneric Foreign Keys will still break! You'll also need to add a migration to rename the
app_label
indjango_contenttypes
to reflect the change. -
Vladimir Prudnikov about 7 yearsWhy 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. -
Wtower almost 7 years@tomcounsell 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...
-
Wtower almost 7 yearsPossibly also use the
app_label = 'app1'
meta option. -
Mike 'Pomax' Kamermans almost 7 yearsSome 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)? -
Teekin over 6 yearsWow, 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. :)
-
Arnaud P over 6 yearsSince 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 @ozan 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 :)
-
Babken Vardanyan over 6 yearsGenius! This worked great for me for ForeignKey relationships. I suppose this will also work for ManyToMany fields as well.
-
Petter Friberg over 6 yearsPlease 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?
-
James Meakin about 6 yearsThis 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.
-
atm about 6 yearsGood answer! I think you need to add a closing parenthesis to migrations.SeparateDatabaseAndState, right?
-
Mohammad Esmaeilbeygi almost 6 yearsin new app migration file can have all fields declarations and run migrate with
--fake-init
flag and there is no problem with foreign keys -
Deesha over 5 yearsI 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 thatfield1 was declared with a lazy reference to 'app1.myModel' but app 'app1' doesn't provide model 'MyModel'
-
Paul in 't Hout over 5 yearsNote 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.
-
Kireeti K over 5 yearsIf 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?
-
Visores over 4 yearsthis 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 :)
-
seds over 4 yearsWhat 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? -
Megawatt over 4 yearsThis 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 fine
-
Mihai Zamfir about 4 yearsin 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.
-
claytond about 4 yearsI'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. -
cvipul about 4 yearsEach 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?
-
aijogja about 4 yearsit worked perfectly. I have tried on Django 2.2. Thanks you