From 8cefe68c9b9278bb07e8c40f101b677f3e8ee547 Mon Sep 17 00:00:00 2001 From: Mykola Solodukha Date: Thu, 5 Dec 2024 11:41:58 +0200 Subject: [PATCH] [BUG] Sort m2m fields before comparing them with `diff(...)` (#271) * :bug: Sort m2m fields before comparing them with `diff(...)` * Add test case and upgrade changelog --------- Co-authored-by: Waket Zheng --- CHANGELOG.md | 5 +++++ aerich/migrate.py | 4 ++++ pyproject.toml | 2 +- tests/models.py | 5 +++++ tests/test_migrate.py | 7 ++++++- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef26cdd..6885008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.8 +### [0.8.1](Unreleased) + +#### Fixed +- Sort m2m fields before comparing them with diff. (#271) + ### [0.8.0](../../releases/tag/v0.8.0) - 2024-12-04 - Fix the issue of parameter concatenation when generating ORM with inspectdb (#331) diff --git a/aerich/migrate.py b/aerich/migrate.py index 71bb93d..ed1605c 100644 --- a/aerich/migrate.py +++ b/aerich/migrate.py @@ -271,6 +271,10 @@ class Migrate: # m2m fields old_m2m_fields = cast(List[dict], old_model_describe.get("m2m_fields")) new_m2m_fields = cast(List[dict], new_model_describe.get("m2m_fields")) + if old_m2m_fields and len(new_m2m_fields) >= 2: + length = len(old_m2m_fields) + field_index = {f["name"]: i for i, f in enumerate(new_m2m_fields)} + new_m2m_fields.sort(key=lambda field: field_index.get(field["name"], length)) for action, _, change in diff(old_m2m_fields, new_m2m_fields): if change[0][0] == "db_constraint": continue diff --git a/pyproject.toml b/pyproject.toml index d089b40..68e7165 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aerich" -version = "0.8.0" +version = "0.8.1" description = "A database migrations tool for Tortoise ORM." authors = ["long2ice "] license = "Apache-2.0" diff --git a/tests/models.py b/tests/models.py index 084df10..3f7d243 100644 --- a/tests/models.py +++ b/tests/models.py @@ -31,6 +31,8 @@ class User(Model): intro = fields.TextField(default="") longitude = fields.DecimalField(max_digits=10, decimal_places=8) + products: fields.ManyToManyRelation["Product"] + class Email(Model): email_id = fields.IntField(primary_key=True) @@ -56,6 +58,9 @@ class Category(Model): class Product(Model): categories: fields.ManyToManyRelation[Category] = fields.ManyToManyField("models.Category") + users: fields.ManyToManyRelation[User] = fields.ManyToManyField( + "models.User", related_name="products" + ) name = fields.CharField(max_length=50) view_num = fields.IntField(description="View Num", default=0) sort = fields.IntField() diff --git a/tests/test_migrate.py b/tests/test_migrate.py index 78efb3d..48f53f2 100644 --- a/tests/test_migrate.py +++ b/tests/test_migrate.py @@ -826,7 +826,7 @@ def should_add_user_id_column_type_alter_sql() -> bool: if tortoise.__version__ < "0.21": return False # tortoise-orm>=0.21 changes IntField constraints - # from {"ge": 1, "le": 2147483647} to {"ge": -2147483648,"le": 2147483647} + # from {"ge": 1, "le": 2147483647} to {"ge": -2147483648, "le": 2147483647} data_fields = cast(List[dict], old_models_describe["models.Category"]["data_fields"]) user_id_constraints = data_fields[-1]["constraints"] return tortoise.fields.data.IntField.constraints != user_id_constraints @@ -846,6 +846,7 @@ def test_migrate(mocker: MockerFixture): - add unique: User.username - change column: length User.password - add unique_together: (name,type) of Product + - add one more many to many field: Product.users - drop unique field: Config.name - alter default: Config.status - rename column: Product.image -> Product.pic @@ -902,6 +903,7 @@ def test_migrate(mocker: MockerFixture): "ALTER TABLE `category` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)", "ALTER TABLE `product` MODIFY COLUMN `body` LONGTEXT NOT NULL", "ALTER TABLE `email` MODIFY COLUMN `is_primary` BOOL NOT NULL DEFAULT 0", + "CREATE TABLE `product_user` (\n `product_id` INT NOT NULL REFERENCES `product` (`id`) ON DELETE CASCADE,\n `user_id` INT NOT NULL REFERENCES `user` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4", } expected_downgrade_operators = { "ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL", @@ -928,6 +930,7 @@ def test_migrate(mocker: MockerFixture): "ALTER TABLE `user` MODIFY COLUMN `password` VARCHAR(200) NOT NULL", "DROP TABLE IF EXISTS `email_user`", "DROP TABLE IF EXISTS `newmodel`", + "DROP TABLE IF EXISTS `product_user`", "ALTER TABLE `user` MODIFY COLUMN `intro` LONGTEXT NOT NULL", "ALTER TABLE `config` MODIFY COLUMN `value` TEXT NOT NULL", "ALTER TABLE `category` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)", @@ -987,6 +990,7 @@ def test_migrate(mocker: MockerFixture): 'CREATE TABLE IF NOT EXISTS "newmodel" (\n "id" SERIAL NOT NULL PRIMARY KEY,\n "name" VARCHAR(50) NOT NULL\n);\nCOMMENT ON COLUMN "config"."user_id" IS \'User\'', 'CREATE UNIQUE INDEX "uid_product_name_869427" ON "product" ("name", "type_db_alias")', 'CREATE UNIQUE INDEX "uid_user_usernam_9987ab" ON "user" ("username")', + 'CREATE TABLE "product_user" (\n "product_id" INT NOT NULL REFERENCES "product" ("id") ON DELETE CASCADE,\n "user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE\n)', } expected_downgrade_operators = { 'CREATE UNIQUE INDEX "uid_category_title_f7fc03" ON "category" ("title")', @@ -1018,6 +1022,7 @@ def test_migrate(mocker: MockerFixture): 'ALTER TABLE "product" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ', 'ALTER TABLE "product" ALTER COLUMN "is_reviewed" TYPE BOOL USING "is_reviewed"::BOOL', 'ALTER TABLE "product" ALTER COLUMN "body" TYPE TEXT USING "body"::TEXT', + 'DROP TABLE IF EXISTS "product_user"', 'DROP INDEX "idx_product_name_869427"', 'DROP INDEX "idx_email_email_4a1a33"', 'DROP INDEX "uid_user_usernam_9987ab"',