Opened 8 years ago
Last modified 2 months ago
#24686 assigned New feature
Support for Moving a model between two Django apps
Reported by: | Alex Rothberg | Owned by: | Bhuvnesh |
---|---|---|---|
Component: | Migrations | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | jedie | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | yes |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
An issue that I run into is that at some point one of my apps becomes so large that I decide to split the application into a number of separate logical units. Right now the Django migration system leaves me on my own to write a correct migration to support updating the db to reflect the new multi-app layout.
There is an SO post that attempts to outline a solution but I would suggest trying to detect these model moves and then auto-generating these migrations.
Change History (31)
comment:1 Changed 8 years ago by
Component: | Uncategorized → Migrations |
---|---|
Needs documentation: | set |
Triage Stage: | Unreviewed → Accepted |
Version: | 1.8 → master |
comment:2 Changed 8 years ago by
I'm not sure how the plumbing works for migrations, but I would think the auto-detect logic would work like renaming of fields where if a model with the same name is deleted in one app and created in another the user is prompted with "did you move this model...".
comment:3 Changed 8 years ago by
An actual implementation would supersede #24016.
I don't think the operation to move a model to another app has to be significantly more complex that the current RenameModel
. The autodetection, on first sight, is just a matter of removing this check: https://github.com/django/django/blob/master/django/db/migrations/autodetector.py#L421
I do have some concerns about dependencies. If the migration lives in the old app, the autodetector won't be able to detect that the next migration in the new app depends on the RenameModel
migration in the old app. Likewise, if the migration lives in the new app, the next migration in the old app won't have a dependency on the migration that moves the model. The last case might not be a problem, but I can't say that off the top of my head.
comment:4 follow-up: 5 Changed 8 years ago by
Crazy question, but what about project level migrations?
comment:5 Changed 8 years ago by
Referring back to the example solution from SO:
As soon as you remove the old_app
from installed apps and apply the migrations on an empty database the SeparateDatabaseAndState
operation in new_app
will only create the model in memory but no database table. Therefore it's not an option to migrate data between apps to get rid of one app it this simple form.
Replying to cancan101:
Crazy question, but what about project level migrations?
What about them? You can define settings.MIGRATION_MODULES
and put all app migrations in your project. But I advice you to not do that unless there is a 3rd party app that doesn't have Django migration support. Then this is the way to go to add migrations for that particular app.
Replying to knbk:
I do have some concerns about dependencies. If the migration lives in the old app, the autodetector won't be able to detect that the next migration in the new app depends on the
RenameModel
migration in the old app. Likewise, if the migration lives in the new app, the next migration in the old app won't have a dependency on the migration that moves the model. The last case might not be a problem, but I can't say that off the top of my head.
Sounds about right to me. You will eventually run into a hellhole of dependencies.
comment:6 Changed 8 years ago by
@MarkusH Are you saying that the solution posted on SO won't work?
I was suggesting the project level migrations to address the issues raised by @knbk so that the migrator could see both apps.
comment:7 Changed 8 years ago by
@cancan101, I'm saying that you will have trouble to get the suggested migrations work on an empty database once old_app
is removed from the list of installed apps. To be specific, since you already need to fix the migration dependencies it's probably not that big of a deal to also drop the SeparateDatabaseAndState
and replace it with a CreateModel
operation.
comment:8 Changed 7 years ago by
I also ran into this today, when pulling some models into a separate app.
The stackoverflow post does not work anymore as soon as there are cross-app foreign keys, but this example here does.
In the solution outlined there, a migration inside the new app depends on a migration of the old app (see here), so loading all migrations should at least break as soon if you remove the old app from INSTALLED_APPS ;-)
Given the currently needed amount of manual work, we should really try to detect these movements and produce migrations like above.
comment:9 Changed 7 years ago by
Maybe this could be separated into two separate tasks.
We could first create database operation like django.db.models.MoveToApp
. This alone could save a lot of manual work and confusion for developers.
Second stage would be creating the autodetection mechanism, which would make migrations with this operation automatically.
comment:10 Changed 6 years ago by
In the meantime, I've built a management command to do just that. You can find it at alexei/django-move-model
If someone cared to take a look and submit some feedback, I'd really appreciate it.
comment:11 Changed 4 years ago by
Cc: | jedie added |
---|
comment:12 Changed 4 years ago by
Hi, I have add a method to MigrationAutodetector
to let it find moved models, originally as a fork of .generate_renamed_models()
. It will create at least 2 migrations with SeparateDatabaseAndState
to do the job.
i have create a PR for it. I try not to alter anywhere else in MigrationAutodetector
, maybe others could do it better without this constrain.
I have only one problem, I need to mention the migration of rem_app_label ( commented in PR ) just before new created one ( which I create ).
without it forward plan may works, but backward plan may be incorrect leading to broken backward migration or even data loss.
Adding it manually is not an acceptable answer for Django project.
I dig in code but couldn't find it. anyone can help me?
comment:13 Changed 4 years ago by
Hi again.
Unlike my previous comment and approach, I found that RenameModel
operation is well written even for moving models, so I update it and MigrationAutodetector
to handle moving models.
The move mechanism is completely without SeparateDatabaseAndState
and it will produce at least 2 migrations one for old_app and one for new_app like below.
00zz_new_created_migration_at_old_app.py
class Migration(migrations.Migration): dependencies = [ ('new_app', '00xx_some_migration'), ('old_app', '00yy_pre_migration'), # ... ] operations = [ operations.RenameModel( old_name='MyModel', new_name='MyModel', new_app_label='new_app', ), ]
00vv_new_created_migration_at_new_app.py
class Migration(migrations.Migration): initial = True dependencies = [ ('new_app', '00xx_some_migration'), ('old_app', '00zz_new_created_migration_at_old_app'), ] operations = [ operations.MoveModelPlaceholder( old_name='MyModel', new_name='MyModel', new_app_label='new_app', comment='this is a required noop operation to construct dependencies for moving model', ), ]
note that MoveModelPlaceholder
is a noop operation to construct the dependencies and let forward and backward migrations without problem.
the code is in PR
comment:15 Changed 3 years ago by
Owner: | changed from nobody to Abhishek Bera |
---|---|
Status: | new → assigned |
comment:16 Changed 3 years ago by
Hi, I am new to django development so I started with this documentation ticket. I am struggling with the level of detail I should mention in the document. So far I just updated the param and mentioned what it does. Here is the diff :
ddiff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index d1620cce8e..f4a444e8e9 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt ``RenameField`` --------------- -.. class:: RenameField(model_name, old_name, new_name) +.. class:: RenameField(model_name, old_name, new_name, new_app_label=None) Changes a field's name (and, unless :attr:`~django.db.models.Field.db_column` -is set, its column name). +is set, its column name) or moves the Model to `new_app_label`.
Also, what should be the branch name for this PR?
comment:17 Changed 3 years ago by
Update: This is the commit if someone needs it https://github.com/berabhishek/django/pull/1/commits/2a92314a3effca22a43219fcbdfdc6add9ccaf66
comment:18 Changed 3 years ago by
Owner: | Abhishek Bera deleted |
---|---|
Status: | assigned → new |
comment:19 Changed 3 years ago by
Needs tests: | set |
---|
comment:20 Changed 8 months ago by
Owner: | set to Durval Carvalho |
---|---|
Status: | new → assigned |
comment:21 Changed 8 months ago by
Hi there! I've started working on this ticket and I've opened a PR with my progress so far at https://github.com/django/django/pull/16523. I'm having a little difficulty understanding the autodetector.py code but I believe it's just a matter of spending more time with it. I discovered this ticket through the gsoc2023 blog post for Django and I'm planning to create a proposed solution once I figure out how to implement it. I'm excited to be able to collaborate with my favorite framework!
comment:22 Changed 7 months ago by
I would like to ask for feedback on my approach. I have found what I believe to be the best way to move a model from one app to another in Django. It involves four separate operations in at least 3 different migrations:
1. Rename table operation: renames the physical table name on the database. This operation should be a SeparateDatabaseAndState operation and only change the database, without changing any state.
2. Create table operation: moves the model code to the new app, and creates a CreateModel only in state, with a SeparateDatabaseAndState operation.
3. Update all foreign keys that point to the moved model.
4. Delete the old model table, but only in state, using a SeparateDatabaseAndState operation.
I've implemented this logic in a new method called "generate_moved_models" in MigrationAutodetector. I've discovered that the current implementation only handles simple ForeignKeys and not more complex cases like ManyToManyFields. This is because the current approach generates at least one AlterField with the fields ManyToOneRel and ManyToManyField, which cannot be serialized at the time of writing operations to the migration files.
I'm still trying to figure out the best approach to tackle this issue. I'm unsure whether extending the serializer_factory to support these types of fields or creating a new operation (probably "MoveModel") similar to RenameModel would be the better solution. I noticed that in the RenameModel the operations related to column renaming in the M2M tables are done in the database_forwards method using the _alter_many_to_many.
If anyone in the Django community has any suggestions or experience with this issue, I would greatly appreciate any input. Thank you!
comment:23 Changed 7 months ago by
Hi everyone, I'm working on this ticket and I want to make sure that the new functionality will meet all the necessary scenarios. So far, I've identified some scenarios that the feature should cover, such as moving a single model, moving multiple models that are independent, and moving models from an app that no longer exists. Are there any other scenarios that I should consider? Your feedback would be greatly appreciated. Thank you!
comment:24 Changed 7 months ago by
Recently, I’ve been thinking about how reverse migrations might work with moved model operations.
To illustrate, let’s take an example of a scenario where the following operations were performed:
1. The database operation AlterModelTable, renaming the table for the moved model 1.1. (suppose it is ‘core_category’ → ‘categories_category’) 2. The state operation CreateModel 3. The AlterField of the foreign keys pointing to the moved field 3.1. Supose it is core.category → categories.category 4. The state operation DeleteModel
By default, the reverse of these operations will be 4 → 3 → 2 ->1. However, it’s not possible to execute operation number 3 because the table hasn’t been renamed yet. The reverse operation will try to update the foreign key field back to core.category and this will be translated to the core_category table, however this table does not exist in the database at this point.
As far as I understand, the reverse operation needs to follow the same sequence as the initial operation (1->2->3->4), but with the fields reversed, ‘categories_category’ → ‘core_category’ and categories.category → core.category.
To resolve this and return to the previous state, one option is to move the model back to its original app and then run the “makemigrations” command again. However, keep in mind that this will generate new migrations instead of reverting the ones that have already been executed.
Does this approach seem reasonable to you?
comment:25 Changed 6 months ago by
Needs documentation: | unset |
---|---|
Needs tests: | unset |
comment:27 Changed 6 months ago by
Personally, I'd rather add a new migration operation than abuse SeparateDatabaseAndState()
.
comment:28 Changed 6 months ago by
Needs documentation: | set |
---|
comment:29 Changed 5 months ago by
Owner: | changed from Durval Carvalho to Bhuvnesh |
---|
comment:30 Changed 3 months ago by
If that can be of any help this problem and a potential solution that doesn't involve using SeparateDatabaseAndState
was discussed in a related ticket https://code.djangoproject.com/ticket/34673#comment:2
Not sure about feasibility of detecting/auto-generating, but if not, we could certainly document the SeparateDatabaseAndState solution in docs/howto/writing-migrations.txt.