Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: migration with duplicate renaming of columns in some cases #395

Open
wants to merge 13 commits into
base: dev
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### [0.8.1](Unreleased)

#### Fixed
- Migration with duplicate renaming of columns in some cases. (#395)
- fix: intermediate table for m2m relation not created. (#394)
- Migrate add m2m field with custom through generate duplicated table. (#393)
- Migrate drop the wrong m2m field when model have multi m2m fields. (#376)
Expand Down
68 changes: 48 additions & 20 deletions aerich/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ class Migrate:
_upgrade_m2m: List[str] = []
_downgrade_m2m: List[str] = []
_aerich = Aerich.__name__
_rename_old: List[str] = []
_rename_new: List[str] = []
_rename_fields: Dict[str, Dict[str, str]] = {} # {'model': {'old_field': 'new_field'}}
waketzheng marked this conversation as resolved.
Show resolved Hide resolved

ddl: BaseDDL
ddl_class: Type[BaseDDL]
Expand Down Expand Up @@ -295,6 +294,7 @@ def diff_models(
_aerich = f"{cls.app}.{cls._aerich}"
old_models.pop(_aerich, None)
new_models.pop(_aerich, None)
models_with_rename_field: Set[str] = set()

for new_model_str, new_model_describe in new_models.items():
model = cls._get_model(new_model_describe["name"].split(".")[1])
Expand Down Expand Up @@ -367,38 +367,70 @@ def diff_models(
new_data_fields_name = cast(List[str], [i.get("name") for i in new_data_fields])

# add fields or rename fields
rename_fields: Dict[str, str] = {}
waketzheng marked this conversation as resolved.
Show resolved Hide resolved
for new_data_field_name in set(new_data_fields_name).difference(
set(old_data_fields_name)
):
new_data_field = cls.get_field_by_name(new_data_field_name, new_data_fields)
model_rename_fields = cls._rename_fields.get(new_model_str)
is_rename = False
for old_data_field in old_data_fields:
field_type = new_data_field.get("field_type")
db_column = new_data_field.get("db_column")
for old_data_field in sorted(
old_data_fields,
key=lambda f: (
f.get("field_type") != field_type,
# old field whose name have more same characters with new field's
# should be put in front of the other
set(new_data_field_name).symmetric_difference(set(f.get("name", ""))),
),
):
changes = list(diff(old_data_field, new_data_field))
old_data_field_name = cast(str, old_data_field.get("name"))
if len(changes) == 2:
# rename field
name_diff = (old_data_field_name, new_data_field_name)
column_diff = (
old_data_field.get("db_column"),
new_data_field.get("db_column"),
)
column_diff = (old_data_field.get("db_column"), db_column)
if (
changes[0] == ("change", "name", name_diff)
and changes[1] == ("change", "db_column", column_diff)
and old_data_field_name not in new_data_fields_name
):
if upgrade:
if old_data_field_name in rename_fields or (
new_data_field_name in rename_fields.values()
):
continue
prefix = f"({new_model_str}) "
if new_model_str not in models_with_rename_field:
if models_with_rename_field:
# When there are multi rename fields with different models,
# print a empty line to warn that is another model
prefix = "\n" + prefix
models_with_rename_field.add(new_model_str)
is_rename = click.prompt(
f"Rename {old_data_field_name} to {new_data_field_name}?",
f"{prefix}Rename {old_data_field_name} to {new_data_field_name}?",
default=True,
type=bool,
show_choices=True,
)
if is_rename:
rename_fields[old_data_field_name] = new_data_field_name
else:
is_rename = old_data_field_name in cls._rename_new
is_rename = False
if model_rename_fields and (
rename_to := model_rename_fields.get(new_data_field_name)
):
is_rename = True
if rename_to != old_data_field_name:
continue
if is_rename:
cls._rename_new.append(new_data_field_name)
cls._rename_old.append(old_data_field_name)
if upgrade:
if new_model_str not in cls._rename_fields:
cls._rename_fields[new_model_str] = {}
cls._rename_fields[new_model_str][
old_data_field_name
] = new_data_field_name
# only MySQL8+ has rename syntax
if (
cls.dialect == "mysql"
Expand All @@ -417,13 +449,7 @@ def diff_models(
upgrade,
)
if not is_rename:
cls._add_operator(
cls._add_field(
model,
new_data_field,
),
upgrade,
)
cls._add_operator(cls._add_field(model, new_data_field), upgrade)
if new_data_field["indexed"]:
cls._add_operator(
cls._add_index(
Expand All @@ -433,12 +459,14 @@ def diff_models(
True,
)
# remove fields
model_rename_fields = cls._rename_fields.get(new_model_str)
for old_data_field_name in set(old_data_fields_name).difference(
set(new_data_fields_name)
):
# don't remove field if is renamed
if (upgrade and old_data_field_name in cls._rename_old) or (
not upgrade and old_data_field_name in cls._rename_new
if model_rename_fields and (
(upgrade and old_data_field_name in model_rename_fields)
or (not upgrade and old_data_field_name in model_rename_fields.values())
):
continue
old_data_field = cls.get_field_by_name(old_data_field_name, old_data_fields)
Expand Down
1 change: 1 addition & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Product(Model):
pic = fields.CharField(max_length=200)
body = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
is_deleted = fields.BooleanField(default=False)

class Meta:
unique_together = (("name", "type"),)
Expand Down
3 changes: 2 additions & 1 deletion tests/old_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ class Product(Model):
name = fields.CharField(max_length=50)
view_num = fields.IntField(description="View Num")
sort = fields.IntField()
is_reviewed = fields.BooleanField(description="Is Reviewed")
is_review = fields.BooleanField(description="Is Reviewed")
type = fields.IntEnumField(
ProductType, description="Product Type", source_field="type_db_alias"
)
image = fields.CharField(max_length=200)
body = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
is_delete = fields.BooleanField(default=False)


class Config(Model):
Expand Down
Loading
Loading