diff --git a/aerich/cli.py b/aerich/cli.py index 94c6a01..ba0353f 100644 --- a/aerich/cli.py +++ b/aerich/cli.py @@ -220,9 +220,9 @@ async def history(ctx: Context): @click.pass_context @coro async def init( - ctx: Context, - tortoise_orm, - location, + ctx: Context, + tortoise_orm, + location, ): config_file = ctx.obj["config_file"] name = ctx.obj["name"] diff --git a/aerich/ddl/__init__.py b/aerich/ddl/__init__.py index 287e38f..47442c4 100644 --- a/aerich/ddl/__init__.py +++ b/aerich/ddl/__init__.py @@ -61,12 +61,12 @@ class BaseDDL: def _get_default(self, model: "Type[Model]", field_describe: dict): db_table = model._meta.db_table - default = field_describe.get('default') - db_column = field_describe.get('db_column') + default = field_describe.get("default") + db_column = field_describe.get("db_column") auto_now_add = field_describe.get("auto_now_add", False) - auto_now = field_describe.get( "auto_now", False) + auto_now = field_describe.get("auto_now", False) if default is not None or auto_now_add: - if field_describe.get('field_type')in ['UUIDField', 'TextField', 'JSONField']: + if field_describe.get("field_type") in ["UUIDField", "TextField", "JSONField"]: default = "" else: try: @@ -103,13 +103,13 @@ class BaseDDL: if description else "", is_primary_key=is_pk, - default=self._get_default(model,field_describe), + default=self._get_default(model, field_describe), ), ) def drop_column(self, model: "Type[Model]", field_describe: dict): return self._DROP_COLUMN_TEMPLATE.format( - table_name=model._meta.db_table, column_name=field_describe.get('db_column') + table_name=model._meta.db_table, column_name=field_describe.get("db_column") ) def modify_column(self, model: "Type[Model]", field_object: Field): @@ -141,7 +141,7 @@ class BaseDDL: ) def change_column( - self, model: "Type[Model]", old_column_name: str, new_column_name: str, new_column_type: str + self, model: "Type[Model]", old_column_name: str, new_column_name: str, new_column_type: str ): return self._CHANGE_COLUMN_TEMPLATE.format( table_name=model._meta.db_table, @@ -168,34 +168,35 @@ class BaseDDL: table_name=model._meta.db_table, ) - def add_fk(self, model: "Type[Model]", field_describe: dict, field_describe_target: dict): + def add_fk(self, model: "Type[Model]", field_describe: dict, reference_table_describe: dict): db_table = model._meta.db_table db_column = field_describe.get("raw_field") + reference_id = reference_table_describe.get("pk_field").get("db_column") fk_name = self.schema_generator._generate_fk_name( from_table=db_table, from_field=db_column, - to_table=field_describe.get('name'), - to_field=db_column, + to_table=reference_table_describe.get("table"), + to_field=reference_table_describe.get("pk_field").get("db_column"), ) return self._ADD_FK_TEMPLATE.format( table_name=db_table, fk_name=fk_name, db_column=db_column, - table=field_describe.get('name'), - field=db_column, - on_delete=field_describe.get('on_delete'), + table=field_describe.get("name"), + field=reference_id, + on_delete=field_describe.get("on_delete"), ) - def drop_fk(self, model: "Type[Model]", field_describe: dict, field_describe_target: dict): + def drop_fk(self, model: "Type[Model]", field_describe: dict, reference_table_describe: dict): db_table = model._meta.db_table return self._DROP_FK_TEMPLATE.format( table_name=db_table, fk_name=self.schema_generator._generate_fk_name( from_table=db_table, - from_field=field_describe.get('raw_field'), - to_table=field_describe.get('name'), - to_field=field_describe_target.get('db_column'), + from_field=field_describe.get("raw_field"), + to_table=reference_table_describe.get("table"), + to_field=reference_table_describe.get("pk_field").get("db_column"), ), ) diff --git a/aerich/migrate.py b/aerich/migrate.py index 7f9b930..a69a97c 100644 --- a/aerich/migrate.py +++ b/aerich/migrate.py @@ -177,7 +177,86 @@ class Migrate: if new_model_str not in old_models.keys(): cls._add_operator(cls.add_model(cls._get_model(new_model_str)), upgrade) else: - cls.diff_model(old_models.get(new_model_str), new_model_describe, upgrade) + old_model_describe = old_models.get(new_model_str) + + old_unique_together = old_model_describe.get("unique_together") + new_unique_together = new_model_describe.get("unique_together") + + old_data_fields = old_model_describe.get("data_fields") + new_data_fields = new_model_describe.get("data_fields") + + old_data_fields_name = list(map(lambda x: x.get("name"), old_data_fields)) + new_data_fields_name = list(map(lambda x: x.get("name"), new_data_fields)) + + model = cls._get_model(new_model_describe.get("name").split(".")[1]) + # add fields + for new_data_field_name in set(new_data_fields_name).difference( + set(old_data_fields_name) + ): + cls._add_operator( + cls._add_field( + model, + next( + filter( + lambda x: x.get("name") == new_data_field_name, new_data_fields + ) + ), + ), + upgrade, + ) + # remove fields + for old_data_field_name in set(old_data_fields_name).difference( + set(new_data_fields_name) + ): + cls._add_operator( + cls._remove_field( + model, + next( + filter( + lambda x: x.get("name") == old_data_field_name, old_data_fields + ) + ), + ), + upgrade, + ) + old_fk_fields = old_model_describe.get("fk_fields") + new_fk_fields = new_model_describe.get("fk_fields") + + old_fk_fields_name = list(map(lambda x: x.get("name"), old_fk_fields)) + new_fk_fields_name = list(map(lambda x: x.get("name"), new_fk_fields)) + + # add fk + for new_fk_field_name in set(new_fk_fields_name).difference( + set(old_fk_fields_name) + ): + fk_field = next( + filter(lambda x: x.get("name") == new_fk_field_name, new_fk_fields) + ) + cls._add_operator( + cls._add_fk(model, fk_field, old_models.get(fk_field.get("python_type"))), + upgrade, + ) + # drop fk + for old_fk_field_name in set(old_fk_fields_name).difference( + set(new_fk_fields_name) + ): + old_fk_field = next( + filter(lambda x: x.get("name") == old_fk_field_name, old_fk_fields) + ) + cls._add_operator( + cls._drop_fk( + model, old_fk_field, old_models.get(old_fk_field.get("python_type")) + ), + upgrade, + ) + # change fields + for field_name in set(new_data_fields_name).intersection(set(old_data_fields_name)): + old_data_field = next( + filter(lambda x: x.get("name") == field_name, old_data_fields) + ) + new_data_field = next( + filter(lambda x: x.get("name") == field_name, new_data_fields) + ) for old_model in old_models: if old_model not in new_models.keys(): @@ -195,59 +274,6 @@ class Migrate: def remove_model(cls, model: Type[Model]): return cls.ddl.drop_table(model) - @classmethod - def diff_model(cls, old_model_describe: dict, new_model_describe: dict, upgrade=True): - """ - diff single model - :param old_model_describe: - :param new_model_describe: - :param upgrade: - :return: - """ - - old_unique_together = old_model_describe.get('unique_together') - new_unique_together = new_model_describe.get('unique_together') - - old_data_fields = old_model_describe.get('data_fields') - new_data_fields = new_model_describe.get('data_fields') - - old_data_fields_name = list(map(lambda x: x.get('name'), old_data_fields)) - new_data_fields_name = list(map(lambda x: x.get('name'), new_data_fields)) - - model = cls._get_model(new_model_describe.get('name').split('.')[1]) - # add fields - for new_data_field_name in set(new_data_fields_name).difference(set(old_data_fields_name)): - cls._add_operator( - cls._add_field(model, next(filter(lambda x: x.get('name') == new_data_field_name, new_data_fields))), - upgrade) - # remove fields - for old_data_field_name in set(old_data_fields_name).difference(set(new_data_fields_name)): - cls._add_operator( - cls._remove_field(model, next(filter(lambda x: x.get('name') == old_data_field_name, old_data_fields))), - upgrade) - - old_fk_fields = old_model_describe.get('fk_fields') - new_fk_fields = new_model_describe.get('fk_fields') - - old_fk_fields_name = list(map(lambda x: x.get('name'), old_fk_fields)) - new_fk_fields_name = list(map(lambda x: x.get('name'), new_fk_fields)) - - # add fk - for new_fk_field_name in set(new_fk_fields_name).difference(set(old_fk_fields_name)): - fk_field = next(filter(lambda x: x.get('name') == new_fk_field_name, new_fk_fields)) - cls._add_operator( - cls._add_fk(model, fk_field, - next(filter(lambda x: x.get('db_column') == fk_field.get('raw_field'), new_data_fields))), - upgrade) - # drop fk - for old_fk_field_name in set(old_fk_fields_name).difference(set(new_fk_fields_name)): - old_fk_field = next(filter(lambda x: x.get('name') == old_fk_field_name, old_fk_fields)) - cls._add_operator( - cls._drop_fk( - model, old_fk_field, - next(filter(lambda x: x.get('db_column') == old_fk_field.get('raw_field'), old_data_fields))), - upgrade) - @classmethod def _resolve_fk_fields_name(cls, model: Type[Model], fields_name: Tuple[str]): ret = [] @@ -312,8 +338,8 @@ class Migrate: return cls.ddl.modify_column(model, field) @classmethod - def _drop_fk(cls, model: Type[Model], field_describe: dict, field_describe_target: dict): - return cls.ddl.drop_fk(model, field_describe, field_describe_target) + def _drop_fk(cls, model: Type[Model], field_describe: dict, reference_table_describe: dict): + return cls.ddl.drop_fk(model, field_describe, reference_table_describe) @classmethod def _remove_field(cls, model: Type[Model], field_describe: dict): @@ -333,14 +359,14 @@ class Migrate: ) @classmethod - def _add_fk(cls, model: Type[Model], field_describe: dict, field_describe_target: dict): + def _add_fk(cls, model: Type[Model], field_describe: dict, reference_table_describe: dict): """ add fk :param model: :param field: :return: """ - return cls.ddl.add_fk(model, field_describe, field_describe_target) + return cls.ddl.add_fk(model, field_describe, reference_table_describe) @classmethod def _merge_operators(cls): diff --git a/poetry.lock b/poetry.lock index a0bc04d..8233d6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -495,23 +495,30 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tortoise-orm" -version = "0.16.20" +version = "0.16.21" description = "Easy async ORM for python, built with relations in mind" category = "main" optional = false -python-versions = ">=3.7,<4.0" +python-versions = "^3.7" +develop = false [package.dependencies] -aiosqlite = ">=0.16.0,<0.17.0" -iso8601 = ">=0.1.13,<0.2.0" -pypika = ">=0.44.0,<0.45.0" -pytz = ">=2020.4,<2021.0" +aiosqlite = "^0.16.0" +iso8601 = "^0.1.13" +pypika = "^0.44.0" +pytz = "^2020.4" [package.extras] -docs = ["pygments", "cloud-sptheme", "docutils", "sphinx"] -aiomysql = ["aiomysql"] +accel = ["ciso8601 (>=2.1.2,<3.0.0)", "uvloop (>=0.14.0,<0.15.0)", "python-rapidjson"] asyncpg = ["asyncpg"] -accel = ["ciso8601 (>=2.1.2,<3.0.0)", "python-rapidjson", "uvloop (>=0.14.0,<0.15.0)"] +aiomysql = ["aiomysql"] +docs = ["sphinx", "cloud-sptheme", "pygments", "docutils"] + +[package.source] +type = "git" +url = "https://github.com/tortoise/tortoise-orm.git" +reference = "develop" +resolved_reference = "37bb36ef3a715b03d18c30452764b348eac21c21" [[package]] name = "typed-ast" @@ -547,7 +554,7 @@ dbdrivers = ["aiomysql", "asyncpg"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "9adf7beba99d615c71a9148391386c9016cbafc7c11c5fc3ad81c8ec61026236" +content-hash = "57603697a31bfe9829bf3706b607c62edb7a3c1b18f45db6752e2d2261f0db41" [metadata.files] aiomysql = [ @@ -828,10 +835,7 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -tortoise-orm = [ - {file = "tortoise-orm-0.16.20.tar.gz", hash = "sha256:a501eab941fcf2cea5bf58d2ac6cb3420457c307106985c06aa6282f1ac86687"}, - {file = "tortoise_orm-0.16.20-py3-none-any.whl", hash = "sha256:de8802b2d03a3b6802e684d4afef9e86ae5ffed3b83ad096ef22723a066b54bb"}, -] +tortoise-orm = [] typed-ast = [ {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, diff --git a/pyproject.toml b/pyproject.toml index 01cf9a4..25fcf0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ include = ["CHANGELOG.md", "LICENSE", "README.md"] [tool.poetry.dependencies] python = "^3.7" -tortoise-orm = "^0.16.21" +tortoise-orm = { git = "https://github.com/tortoise/tortoise-orm.git", branch = "develop" } click = "*" pydantic = "*" aiomysql = { version = "*", optional = true } diff --git a/tests/models.py b/tests/models.py index a3ee9ac..765e32d 100644 --- a/tests/models.py +++ b/tests/models.py @@ -27,13 +27,13 @@ class User(Model): last_login = fields.DatetimeField(description="Last Login", default=datetime.datetime.now) is_active = fields.BooleanField(default=True, description="Is Active") is_superuser = fields.BooleanField(default=False, description="Is SuperUser") - avatar = fields.CharField(max_length=200, default="") intro = fields.TextField(default="") class Email(Model): - email = fields.CharField(max_length=200) + email = fields.CharField(max_length=200, index=True) is_primary = fields.BooleanField(default=False) + address = fields.CharField(max_length=200) class Category(Model): @@ -60,3 +60,4 @@ class Config(Model): key = fields.CharField(max_length=20) value = fields.JSONField() status: Status = fields.IntEnumField(Status, default=Status.on) + user = fields.ForeignKeyField("models.User", description="User") diff --git a/tests/diff_models.py b/tests/old_models.py similarity index 81% rename from tests/diff_models.py rename to tests/old_models.py index 6c591a4..ef0fd66 100644 --- a/tests/diff_models.py +++ b/tests/old_models.py @@ -24,7 +24,7 @@ class Status(IntEnum): class User(Model): username = fields.CharField(max_length=20) password = fields.CharField(max_length=200) - last_login_at = fields.DatetimeField(description="Last Login", default=datetime.datetime.now) + last_login = fields.DatetimeField(description="Last Login", default=datetime.datetime.now) is_active = fields.BooleanField(default=True, description="Is Active") is_superuser = fields.BooleanField(default=False, description="Is SuperUser") avatar = fields.CharField(max_length=200, default="") @@ -34,17 +34,18 @@ class User(Model): class Email(Model): email = fields.CharField(max_length=200) is_primary = fields.BooleanField(default=False) - user = fields.ForeignKeyField("diff_models.User", db_constraint=True) + user = fields.ForeignKeyField("models.User", db_constraint=False) class Category(Model): slug = fields.CharField(max_length=200) - user = fields.ForeignKeyField("diff_models.User", description="User") + name = fields.CharField(max_length=200) + user = fields.ForeignKeyField("models.User", description="User") created_at = fields.DatetimeField(auto_now_add=True) class Product(Model): - categories = fields.ManyToManyField("diff_models.Category") + categories = fields.ManyToManyField("models.Category") name = fields.CharField(max_length=50) view_num = fields.IntField(description="View Num") sort = fields.IntField() diff --git a/tests/test_ddl.py b/tests/test_ddl.py index 410d5a4..174bc51 100644 --- a/tests/test_ddl.py +++ b/tests/test_ddl.py @@ -180,7 +180,9 @@ def test_drop_index(): def test_add_fk(): - ret = Migrate.ddl.add_fk(Category, Category._meta.fields_map.get("user").describe(False)) + ret = Migrate.ddl.add_fk( + Category, Category._meta.fields_map.get("user").describe(False), User.describe(False) + ) if isinstance(Migrate.ddl, MysqlDDL): assert ( ret @@ -194,7 +196,9 @@ def test_add_fk(): def test_drop_fk(): - ret = Migrate.ddl.drop_fk(Category, Category._meta.fields_map.get("user")) + ret = Migrate.ddl.drop_fk( + Category, Category._meta.fields_map.get("user").describe(False), User.describe(False) + ) if isinstance(Migrate.ddl, MysqlDDL): assert ret == "ALTER TABLE `category` DROP FOREIGN KEY `fk_category_user_e2e3874c`" elif isinstance(Migrate.ddl, PostgresDDL): diff --git a/tests/test_migrate.py b/tests/test_migrate.py index 9601d16..f6ff6b9 100644 --- a/tests/test_migrate.py +++ b/tests/test_migrate.py @@ -1,25 +1,753 @@ import pytest -from pytest_mock import MockerFixture -from tortoise import Tortoise from aerich.ddl.mysql import MysqlDDL from aerich.ddl.postgres import PostgresDDL from aerich.ddl.sqlite import SqliteDDL from aerich.exceptions import NotSupportError from aerich.migrate import Migrate +from aerich.utils import get_models_describe + +old_models_describe = { + "models.Category": { + "name": "models.Category", + "app": "models", + "table": "category", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "slug", + "field_type": "CharField", + "db_column": "slug", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "name", + "field_type": "CharField", + "db_column": "name", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "created_at", + "field_type": "DatetimeField", + "db_column": "created_at", + "python_type": "datetime.datetime", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"readOnly": True}, + "db_field_types": { + "": "TIMESTAMP", + "mysql": "DATETIME(6)", + "postgres": "TIMESTAMPTZ", + }, + }, + { + "name": "user_id", + "field_type": "IntField", + "db_column": "user_id", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "User", + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + ], + "fk_fields": [ + { + "name": "user", + "field_type": "ForeignKeyFieldInstance", + "python_type": "models.User", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "User", + "docstring": None, + "constraints": {}, + "raw_field": "user_id", + "on_delete": "CASCADE", + } + ], + "backward_fk_fields": [], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [ + { + "name": "products", + "field_type": "ManyToManyFieldInstance", + "python_type": "models.Product", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + } + ], + }, + "models.Config": { + "name": "models.Config", + "app": "models", + "table": "config", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "label", + "field_type": "CharField", + "db_column": "label", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "key", + "field_type": "CharField", + "db_column": "key", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 20}, + "db_field_types": {"": "VARCHAR(20)"}, + }, + { + "name": "value", + "field_type": "JSONField", + "db_column": "value", + "python_type": "Union[dict, list]", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + "db_field_types": {"": "TEXT", "postgres": "JSONB"}, + }, + { + "name": "status", + "field_type": "IntEnumFieldInstance", + "db_column": "status", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": 1, + "description": "on: 1\noff: 0", + "docstring": None, + "constraints": {"ge": -32768, "le": 32767}, + "db_field_types": {"": "SMALLINT"}, + }, + ], + "fk_fields": [], + "backward_fk_fields": [], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [], + }, + "models.Email": { + "name": "models.Email", + "app": "models", + "table": "email", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "email", + "field_type": "CharField", + "db_column": "email", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "is_primary", + "field_type": "BooleanField", + "db_column": "is_primary", + "python_type": "bool", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": False, + "description": None, + "docstring": None, + "constraints": {}, + "db_field_types": {"": "BOOL", "sqlite": "INT"}, + }, + { + "name": "user_id", + "field_type": "IntField", + "db_column": "user_id", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + ], + "fk_fields": [ + { + "name": "user", + "field_type": "ForeignKeyFieldInstance", + "python_type": "models.User", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + "raw_field": "user_id", + "on_delete": "CASCADE", + } + ], + "backward_fk_fields": [], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [], + }, + "models.Product": { + "name": "models.Product", + "app": "models", + "table": "product", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "name", + "field_type": "CharField", + "db_column": "name", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 50}, + "db_field_types": {"": "VARCHAR(50)"}, + }, + { + "name": "view_num", + "field_type": "IntField", + "db_column": "view_num", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "View Num", + "docstring": None, + "constraints": {"ge": -2147483648, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + { + "name": "sort", + "field_type": "IntField", + "db_column": "sort", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": -2147483648, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + { + "name": "is_reviewed", + "field_type": "BooleanField", + "db_column": "is_reviewed", + "python_type": "bool", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "Is Reviewed", + "docstring": None, + "constraints": {}, + "db_field_types": {"": "BOOL", "sqlite": "INT"}, + }, + { + "name": "type", + "field_type": "IntEnumFieldInstance", + "db_column": "type", + "python_type": "int", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "Product Type", + "docstring": None, + "constraints": {"ge": -32768, "le": 32767}, + "db_field_types": {"": "SMALLINT"}, + }, + { + "name": "image", + "field_type": "CharField", + "db_column": "image", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "body", + "field_type": "TextField", + "db_column": "body", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + "db_field_types": {"": "TEXT", "mysql": "LONGTEXT"}, + }, + { + "name": "created_at", + "field_type": "DatetimeField", + "db_column": "created_at", + "python_type": "datetime.datetime", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"readOnly": True}, + "db_field_types": { + "": "TIMESTAMP", + "mysql": "DATETIME(6)", + "postgres": "TIMESTAMPTZ", + }, + }, + ], + "fk_fields": [], + "backward_fk_fields": [], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [ + { + "name": "categories", + "field_type": "ManyToManyFieldInstance", + "python_type": "models.Category", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + } + ], + }, + "models.User": { + "name": "models.User", + "app": "models", + "table": "user", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "username", + "field_type": "CharField", + "db_column": "username", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 20}, + "db_field_types": {"": "VARCHAR(20)"}, + }, + { + "name": "password", + "field_type": "CharField", + "db_column": "password", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "last_login", + "field_type": "DatetimeField", + "db_column": "last_login", + "python_type": "datetime.datetime", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": "", + "description": "Last Login", + "docstring": None, + "constraints": {}, + "db_field_types": { + "": "TIMESTAMP", + "mysql": "DATETIME(6)", + "postgres": "TIMESTAMPTZ", + }, + }, + { + "name": "is_active", + "field_type": "BooleanField", + "db_column": "is_active", + "python_type": "bool", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": True, + "description": "Is Active", + "docstring": None, + "constraints": {}, + "db_field_types": {"": "BOOL", "sqlite": "INT"}, + }, + { + "name": "is_superuser", + "field_type": "BooleanField", + "db_column": "is_superuser", + "python_type": "bool", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": False, + "description": "Is SuperUser", + "docstring": None, + "constraints": {}, + "db_field_types": {"": "BOOL", "sqlite": "INT"}, + }, + { + "name": "avatar", + "field_type": "CharField", + "db_column": "avatar", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": "", + "description": None, + "docstring": None, + "constraints": {"max_length": 200}, + "db_field_types": {"": "VARCHAR(200)"}, + }, + { + "name": "intro", + "field_type": "TextField", + "db_column": "intro", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": "", + "description": None, + "docstring": None, + "constraints": {}, + "db_field_types": {"": "TEXT", "mysql": "LONGTEXT"}, + }, + ], + "fk_fields": [], + "backward_fk_fields": [ + { + "name": "categorys", + "field_type": "BackwardFKRelation", + "python_type": "models.Category", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": "User", + "docstring": None, + "constraints": {}, + }, + { + "name": "emails", + "field_type": "BackwardFKRelation", + "python_type": "models.Email", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + }, + ], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [], + }, + "models.Aerich": { + "name": "models.Aerich", + "app": "models", + "table": "aerich", + "abstract": False, + "description": None, + "docstring": None, + "unique_together": [], + "pk_field": { + "name": "id", + "field_type": "IntField", + "db_column": "id", + "python_type": "int", + "generated": True, + "Noneable": False, + "unique": True, + "indexed": True, + "default": None, + "description": None, + "docstring": None, + "constraints": {"ge": 1, "le": 2147483647}, + "db_field_types": {"": "INT"}, + }, + "data_fields": [ + { + "name": "version", + "field_type": "CharField", + "db_column": "version", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 255}, + "db_field_types": {"": "VARCHAR(255)"}, + }, + { + "name": "app", + "field_type": "CharField", + "db_column": "app", + "python_type": "str", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {"max_length": 20}, + "db_field_types": {"": "VARCHAR(20)"}, + }, + { + "name": "content", + "field_type": "JSONField", + "db_column": "content", + "python_type": "Union[dict, list]", + "generated": False, + "Noneable": False, + "unique": False, + "indexed": False, + "default": None, + "description": None, + "docstring": None, + "constraints": {}, + "db_field_types": {"": "TEXT", "postgres": "JSONB"}, + }, + ], + "fk_fields": [], + "backward_fk_fields": [], + "o2o_fields": [], + "backward_o2o_fields": [], + "m2m_fields": [], + }, +} -def test_migrate(mocker: MockerFixture): - mocker.patch("click.prompt", return_value=True) - apps = Tortoise.apps - models = apps.get("models") - diff_models = apps.get("diff_models") - Migrate.diff_models(diff_models, models) +def test_migrate(): + """ + models.py diff with old_models.py + - add field: Email.address + - add fk: Config.user + - drop fk: Email.user + - drop field: User.avatar + - add index: Email.email + - remove unique: User.username + """ + models_describe = get_models_describe("models") + Migrate.diff_models(old_models_describe, models_describe) if isinstance(Migrate.ddl, SqliteDDL): with pytest.raises(NotSupportError): - Migrate.diff_models(models, diff_models, False) + Migrate.diff_models(models_describe, old_models_describe, False) else: - Migrate.diff_models(models, diff_models, False) + Migrate.diff_models(models_describe, old_models_describe, False) Migrate._merge_operators() if isinstance(Migrate.ddl, MysqlDDL): assert Migrate.upgrade_operators == [