fix: add o2o field does not create constraint when migrating (#396)
* fix: add o2o field does not create constraint when migrating * Add testcase and update changelog * docs: update migrating list * refactor: use `_handle_o2o_fields` instead of `is_o2o=True` * Remove unused line
This commit is contained in:
parent
1acb9ed1e7
commit
f93faa8afb
@ -5,6 +5,7 @@
|
|||||||
### [0.8.1](Unreleased)
|
### [0.8.1](Unreleased)
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
- fix: add o2o field does not create constraint when migrating. (#396)
|
||||||
- fix: intermediate table for m2m relation not created. (#394)
|
- fix: intermediate table for m2m relation not created. (#394)
|
||||||
- Migrate add m2m field with custom through generate duplicated table. (#393)
|
- Migrate add m2m field with custom through generate duplicated table. (#393)
|
||||||
- Migrate drop the wrong m2m field when model have multi m2m fields. (#376)
|
- Migrate drop the wrong m2m field when model have multi m2m fields. (#376)
|
||||||
|
@ -281,6 +281,68 @@ class Migrate:
|
|||||||
if add:
|
if add:
|
||||||
cls._add_operator(cls.drop_m2m(table), upgrade, True)
|
cls._add_operator(cls.drop_m2m(table), upgrade, True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _handle_relational(
|
||||||
|
cls,
|
||||||
|
key: str,
|
||||||
|
old_model_describe: Dict,
|
||||||
|
new_model_describe: Dict,
|
||||||
|
model: Type[Model],
|
||||||
|
old_models: Dict,
|
||||||
|
new_models: Dict,
|
||||||
|
upgrade=True,
|
||||||
|
) -> None:
|
||||||
|
old_fk_fields = cast(List[dict], old_model_describe.get(key))
|
||||||
|
new_fk_fields = cast(List[dict], new_model_describe.get(key))
|
||||||
|
|
||||||
|
old_fk_fields_name: List[str] = [i.get("name", "") for i in old_fk_fields]
|
||||||
|
new_fk_fields_name: List[str] = [i.get("name", "") for i in new_fk_fields]
|
||||||
|
|
||||||
|
# add
|
||||||
|
for new_fk_field_name in set(new_fk_fields_name).difference(set(old_fk_fields_name)):
|
||||||
|
fk_field = cls.get_field_by_name(new_fk_field_name, new_fk_fields)
|
||||||
|
if fk_field.get("db_constraint"):
|
||||||
|
ref_describe = cast(dict, new_models[fk_field["python_type"]])
|
||||||
|
sql = cls._add_fk(model, fk_field, ref_describe)
|
||||||
|
cls._add_operator(sql, upgrade, fk_m2m_index=True)
|
||||||
|
# drop
|
||||||
|
for old_fk_field_name in set(old_fk_fields_name).difference(set(new_fk_fields_name)):
|
||||||
|
old_fk_field = cls.get_field_by_name(old_fk_field_name, cast(List[dict], old_fk_fields))
|
||||||
|
if old_fk_field.get("db_constraint"):
|
||||||
|
ref_describe = cast(dict, old_models[old_fk_field["python_type"]])
|
||||||
|
sql = cls._drop_fk(model, old_fk_field, ref_describe)
|
||||||
|
cls._add_operator(sql, upgrade, fk_m2m_index=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _handle_fk_fields(
|
||||||
|
cls,
|
||||||
|
old_model_describe: Dict,
|
||||||
|
new_model_describe: Dict,
|
||||||
|
model: Type[Model],
|
||||||
|
old_models: Dict,
|
||||||
|
new_models: Dict,
|
||||||
|
upgrade=True,
|
||||||
|
) -> None:
|
||||||
|
key = "fk_fields"
|
||||||
|
cls._handle_relational(
|
||||||
|
key, old_model_describe, new_model_describe, model, old_models, new_models, upgrade
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _handle_o2o_fields(
|
||||||
|
cls,
|
||||||
|
old_model_describe: Dict,
|
||||||
|
new_model_describe: Dict,
|
||||||
|
model: Type[Model],
|
||||||
|
old_models: Dict,
|
||||||
|
new_models: Dict,
|
||||||
|
upgrade=True,
|
||||||
|
) -> None:
|
||||||
|
key = "o2o_fields"
|
||||||
|
cls._handle_relational(
|
||||||
|
key, old_model_describe, new_model_describe, model, old_models, new_models, upgrade
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def diff_models(
|
def diff_models(
|
||||||
cls, old_models: Dict[str, dict], new_models: Dict[str, dict], upgrade=True
|
cls, old_models: Dict[str, dict], new_models: Dict[str, dict], upgrade=True
|
||||||
@ -334,6 +396,13 @@ class Migrate:
|
|||||||
# current only support rename pk
|
# current only support rename pk
|
||||||
if action == "change" and option == "name":
|
if action == "change" and option == "name":
|
||||||
cls._add_operator(cls._rename_field(model, *change), upgrade)
|
cls._add_operator(cls._rename_field(model, *change), upgrade)
|
||||||
|
# fk fields
|
||||||
|
args = (old_model_describe, new_model_describe, model, old_models, new_models)
|
||||||
|
cls._handle_fk_fields(*args, upgrade=upgrade)
|
||||||
|
# o2o fields
|
||||||
|
cls._handle_o2o_fields(*args, upgrade=upgrade)
|
||||||
|
old_o2o_columns = [i["raw_field"] for i in old_model_describe.get("o2o_fields", [])]
|
||||||
|
new_o2o_columns = [i["raw_field"] for i in new_model_describe.get("o2o_fields", [])]
|
||||||
# m2m fields
|
# m2m fields
|
||||||
cls._handle_m2m_fields(
|
cls._handle_m2m_fields(
|
||||||
old_model_describe, new_model_describe, model, new_models, upgrade
|
old_model_describe, new_model_describe, model, new_models, upgrade
|
||||||
@ -424,7 +493,10 @@ class Migrate:
|
|||||||
),
|
),
|
||||||
upgrade,
|
upgrade,
|
||||||
)
|
)
|
||||||
if new_data_field["indexed"]:
|
if (
|
||||||
|
new_data_field["indexed"]
|
||||||
|
and new_data_field["db_column"] not in new_o2o_columns
|
||||||
|
):
|
||||||
cls._add_operator(
|
cls._add_operator(
|
||||||
cls._add_index(
|
cls._add_index(
|
||||||
model, (new_data_field["db_column"],), new_data_field["unique"]
|
model, (new_data_field["db_column"],), new_data_field["unique"]
|
||||||
@ -447,7 +519,10 @@ class Migrate:
|
|||||||
cls._remove_field(model, db_column),
|
cls._remove_field(model, db_column),
|
||||||
upgrade,
|
upgrade,
|
||||||
)
|
)
|
||||||
if old_data_field["indexed"]:
|
if (
|
||||||
|
old_data_field["indexed"]
|
||||||
|
and old_data_field["db_column"] not in old_o2o_columns
|
||||||
|
):
|
||||||
is_unique_field = old_data_field.get("unique")
|
is_unique_field = old_data_field.get("unique")
|
||||||
cls._add_operator(
|
cls._add_operator(
|
||||||
cls._drop_index(model, {db_column}, is_unique_field),
|
cls._drop_index(model, {db_column}, is_unique_field),
|
||||||
@ -455,38 +530,6 @@ class Migrate:
|
|||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
old_fk_fields = cast(List[dict], old_model_describe.get("fk_fields"))
|
|
||||||
new_fk_fields = cast(List[dict], new_model_describe.get("fk_fields"))
|
|
||||||
|
|
||||||
old_fk_fields_name: List[str] = [i.get("name", "") for i in old_fk_fields]
|
|
||||||
new_fk_fields_name: List[str] = [i.get("name", "") for i in new_fk_fields]
|
|
||||||
|
|
||||||
# add fk
|
|
||||||
for new_fk_field_name in set(new_fk_fields_name).difference(
|
|
||||||
set(old_fk_fields_name)
|
|
||||||
):
|
|
||||||
fk_field = cls.get_field_by_name(new_fk_field_name, new_fk_fields)
|
|
||||||
if fk_field.get("db_constraint"):
|
|
||||||
ref_describe = cast(dict, new_models[fk_field["python_type"]])
|
|
||||||
cls._add_operator(
|
|
||||||
cls._add_fk(model, fk_field, ref_describe),
|
|
||||||
upgrade,
|
|
||||||
fk_m2m_index=True,
|
|
||||||
)
|
|
||||||
# drop fk
|
|
||||||
for old_fk_field_name in set(old_fk_fields_name).difference(
|
|
||||||
set(new_fk_fields_name)
|
|
||||||
):
|
|
||||||
old_fk_field = cls.get_field_by_name(
|
|
||||||
old_fk_field_name, cast(List[dict], old_fk_fields)
|
|
||||||
)
|
|
||||||
if old_fk_field.get("db_constraint"):
|
|
||||||
ref_describe = cast(dict, old_models[old_fk_field["python_type"]])
|
|
||||||
cls._add_operator(
|
|
||||||
cls._drop_fk(model, old_fk_field, ref_describe),
|
|
||||||
upgrade,
|
|
||||||
fk_m2m_index=True,
|
|
||||||
)
|
|
||||||
# change fields
|
# change fields
|
||||||
for field_name in set(new_data_fields_name).intersection(set(old_data_fields_name)):
|
for field_name in set(new_data_fields_name).intersection(set(old_data_fields_name)):
|
||||||
old_data_field = cls.get_field_by_name(field_name, old_data_fields)
|
old_data_field = cls.get_field_by_name(field_name, old_data_fields)
|
||||||
|
@ -40,6 +40,7 @@ class Email(Model):
|
|||||||
is_primary = fields.BooleanField(default=False)
|
is_primary = fields.BooleanField(default=False)
|
||||||
address = fields.CharField(max_length=200)
|
address = fields.CharField(max_length=200)
|
||||||
users: fields.ManyToManyRelation[User] = fields.ManyToManyField("models.User")
|
users: fields.ManyToManyRelation[User] = fields.ManyToManyField("models.User")
|
||||||
|
config: fields.OneToOneRelation["Config"] = fields.OneToOneField("models.Config")
|
||||||
|
|
||||||
|
|
||||||
def default_name():
|
def default_name():
|
||||||
@ -91,6 +92,8 @@ class Config(Model):
|
|||||||
"models.User", description="User"
|
"models.User", description="User"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
email: fields.OneToOneRelation["Email"]
|
||||||
|
|
||||||
|
|
||||||
class NewModel(Model):
|
class NewModel(Model):
|
||||||
name = fields.CharField(max_length=50)
|
name = fields.CharField(max_length=50)
|
||||||
|
@ -875,6 +875,7 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
- drop field: User.avatar
|
- drop field: User.avatar
|
||||||
- add index: Email.email
|
- add index: Email.email
|
||||||
- add many to many: Email.users
|
- add many to many: Email.users
|
||||||
|
- add one to one: Email.config
|
||||||
- remove unique: Category.title
|
- remove unique: Category.title
|
||||||
- add unique: User.username
|
- add unique: User.username
|
||||||
- change column: length User.password
|
- change column: length User.password
|
||||||
@ -914,6 +915,8 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
"ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT",
|
"ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT",
|
||||||
"ALTER TABLE `config` MODIFY COLUMN `value` JSON NOT NULL",
|
"ALTER TABLE `config` MODIFY COLUMN `value` JSON NOT NULL",
|
||||||
"ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL",
|
"ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL",
|
||||||
|
"ALTER TABLE `email` ADD CONSTRAINT `fk_email_config_76a9dc71` FOREIGN KEY (`config_id`) REFERENCES `config` (`id`) ON DELETE CASCADE",
|
||||||
|
"ALTER TABLE `email` ADD `config_id` INT NOT NULL UNIQUE",
|
||||||
"ALTER TABLE `configs` RENAME TO `config`",
|
"ALTER TABLE `configs` RENAME TO `config`",
|
||||||
"ALTER TABLE `product` DROP COLUMN `uuid`",
|
"ALTER TABLE `product` DROP COLUMN `uuid`",
|
||||||
"ALTER TABLE `product` DROP INDEX `uuid`",
|
"ALTER TABLE `product` DROP INDEX `uuid`",
|
||||||
@ -954,6 +957,8 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
"ALTER TABLE `config` ALTER COLUMN `status` SET DEFAULT 1",
|
"ALTER TABLE `config` ALTER COLUMN `status` SET DEFAULT 1",
|
||||||
"ALTER TABLE `email` ADD `user_id` INT NOT NULL",
|
"ALTER TABLE `email` ADD `user_id` INT NOT NULL",
|
||||||
"ALTER TABLE `email` DROP COLUMN `address`",
|
"ALTER TABLE `email` DROP COLUMN `address`",
|
||||||
|
"ALTER TABLE `email` DROP COLUMN `config_id`",
|
||||||
|
"ALTER TABLE `email` DROP FOREIGN KEY `fk_email_config_76a9dc71`",
|
||||||
"ALTER TABLE `config` RENAME TO `configs`",
|
"ALTER TABLE `config` RENAME TO `configs`",
|
||||||
"ALTER TABLE `product` RENAME COLUMN `pic` TO `image`",
|
"ALTER TABLE `product` RENAME COLUMN `pic` TO `image`",
|
||||||
"ALTER TABLE `email` RENAME COLUMN `email_id` TO `id`",
|
"ALTER TABLE `email` RENAME COLUMN `email_id` TO `id`",
|
||||||
@ -1007,6 +1012,8 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL',
|
'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL',
|
||||||
'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"',
|
'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"',
|
||||||
'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL',
|
'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL',
|
||||||
|
'ALTER TABLE "email" ADD CONSTRAINT "fk_email_config_76a9dc71" FOREIGN KEY ("config_id") REFERENCES "config" ("id") ON DELETE CASCADE',
|
||||||
|
'ALTER TABLE "email" ADD "config_id" INT NOT NULL UNIQUE',
|
||||||
'DROP INDEX IF EXISTS "uid_product_uuid_d33c18"',
|
'DROP INDEX IF EXISTS "uid_product_uuid_d33c18"',
|
||||||
'ALTER TABLE "product" DROP COLUMN "uuid"',
|
'ALTER TABLE "product" DROP COLUMN "uuid"',
|
||||||
'ALTER TABLE "product" ALTER COLUMN "view_num" SET DEFAULT 0',
|
'ALTER TABLE "product" ALTER COLUMN "view_num" SET DEFAULT 0',
|
||||||
@ -1048,6 +1055,8 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "email" DROP COLUMN "address"',
|
'ALTER TABLE "email" DROP COLUMN "address"',
|
||||||
'ALTER TABLE "email" RENAME COLUMN "email_id" TO "id"',
|
'ALTER TABLE "email" RENAME COLUMN "email_id" TO "id"',
|
||||||
'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL',
|
'ALTER TABLE "email" ALTER COLUMN "is_primary" TYPE BOOL USING "is_primary"::BOOL',
|
||||||
|
'ALTER TABLE "email" DROP COLUMN "config_id"',
|
||||||
|
'ALTER TABLE "email" DROP CONSTRAINT IF EXISTS "fk_email_config_76a9dc71"',
|
||||||
'ALTER TABLE "product" ADD "uuid" INT NOT NULL UNIQUE',
|
'ALTER TABLE "product" ADD "uuid" INT NOT NULL UNIQUE',
|
||||||
'CREATE UNIQUE INDEX "uid_product_uuid_d33c18" ON "product" ("uuid")',
|
'CREATE UNIQUE INDEX "uid_product_uuid_d33c18" ON "product" ("uuid")',
|
||||||
'ALTER TABLE "product" ALTER COLUMN "view_num" DROP DEFAULT',
|
'ALTER TABLE "product" ALTER COLUMN "view_num" DROP DEFAULT',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user