I would like to request feedback on my approach for moving a model from one app to another in Django. I have come up with what I believe is the best way to achieve this, which involves four separate operations spread across at least three different migrations:
1. Rename table operation: This operation renames the physical table name in the database. It should be a SeparateDatabaseAndState operation that only changes the database, without modifying any state. 2. Create table operation: This operation moves the model code to the new app and creates a CreateModel operation only in state, using a SeparateDatabaseAndState operation. 3. Update foreign keys: This step involves updating all foreign keys that point to the moved model. 4. Delete old model table: Finally, the old model table can be deleted, but only in state, using a SeparateDatabaseAndState operation.
However, I am still exploring the best approach to solve this problem. I believe creating a new operation (possibly called “MoveModel”), similar to RenameModel, might be the best solution. I also noticed that in RenameModel, operations related to renaming columns in M2M tables are done in the database_forwards method using the _alter_many_to_many method.
I am uncertain whether the move operation should be designed to work with deleted apps. Furthermore, I am currently exploring scenarios where the old app may no longer exist, and I am unsure if this ticket should address such cases.
If anyone in the Django community has suggestions or experience with this issue, I would greatly appreciate any input. Thank you!
Hi again everyone… I’m still 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!
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:
The database operation AlterModelTable, renaming the table for the moved model
1.1. (suppose it is ‘core_category’ → ‘categories_category’)
The state operation CreateModel
The AlterField of the foreign keys pointing to the moved field
3.1. Supose it is core.category → categories.category
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.
Thank you for your suggestion on testing scenarios where the moving models have Generic Foreign Keys. I have successfully tested this scenario by moving the Comment model to another app and doing the same with Post and Profile, without any issues. I have also created a test case for this scenario which you can find here.
However, I am still unsure about how to handle some specific scenarios and would appreciate your guidance. In particular, I am concerned about cases where external factors such as indexes, triggers, and views may cause issues. As they are defined outside of Django, I am unsure of the best approach to testing them. Do you have any suggestions or examples that could help me with this?
I followed your tip and created experimental branches where I created these objects before and after moving the model. You can find these branches in this repository:
On these branches I created created this index/views/triggers:
-- View
CREATE OR REPLACE VIEW core_product_category AS
SELECT
core_product.id,
core_product.name,
core_category.name AS category_name
FROM core_product
INNER JOIN core_category ON core_product.category_id = core_category.id;
-- Index
CREATE INDEX core_category_name_idx ON core_category (name);
-- Function / Trigger
CREATE OR REPLACE FUNCTION core_category_name_uppercase()
RETURNS TRIGGER AS $$
BEGIN
NEW.name = UPPER(NEW.name);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER core_category_name_uppercase
BEFORE INSERT OR UPDATE ON core_category
FOR EACH ROW EXECUTE PROCEDURE core_category_name_uppercase();
To my surprise, these objects were updated correctly, and still working after the moving model migration. I haven’t checked yet, but I assume that the AlterModelTable operation somehow finds these objects and updates their references to the moved table.
That is so cool! I wouldn’t have guessed that. I wonder if that holds true on the other supported database engines as well. Great work to trace that down!