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?
Another hacky alternative if the data is not big or too complicated, but still important to maintain, is to:
I am removing the old answer as may result in data loss. As ozan mentioned, we can create 2 migrations one in each app.
First migration to remove model from 1st app.
Edit migration file to include these operations.
Second migration which depends on first migration and create the new table in 2nd app. After moving model code to 2nd app
and edit migration file to something like this.
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.
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:
makemigrations
.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: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,becomes
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: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.app2.migrations.0004_auto_<date>
and add the dependency you just copied to itsdependencies
list.If you have
ForeignKey
relationship(s) to the model you're moving, the above may not work. This happens because:ForeignKey
changesForeignKey
changes instate_operations
so we need to ensure they are separate from the table operations.The "minimum" set of operations differ depending on the situation, but the following procedure should work for most/all
ForeignKey
migrations:app1
toapp2
, setdb_table
, but DON'T change any FK references.makemigrations
and wrap allapp2
migration instate_operations
(see above)app2
CreateTable
to the latestapp1
migrationmodels.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: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:Add the
CreateModel
migration as as a dependency:Remove the models from
app1
makemigrations
and wrap theapp1
migration instate_operations
.ForeignKey
migrations (i.e.AlterField
) from the previous step (may include migrations inapp1
andapp2
).DeleteTable
already depended on theAlterField
migrations so I didn't need to manually enforce it (i.e.Alter
beforeDelete
).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. byForeignKey
) to content types, you'll need to create a migration to update theContentType
table.I wanted to cleanup after myself (Meta options and table names) so I used the following procedure (from @Michael):
db_table
Meta entrymakemigrations
again to generate the database renameDeleteTable
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.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
In the migration:
Add to new app
First, copy the model to the new app's model.py, then:
This will generate a migration with a naive
CreateModel
operation as the sole operation. Wrap that in aSeparateDatabaseAndState
operation such that we don't try to recreate the table. Also include the prior migration as a dependency: