def test_pre_deploy_operations(self): pre_deploy_operation = Operation() pre_deploy_operation.stage = Stage.PRE_DEPLOY operations = [CreateModel("model", []), pre_deploy_operation] for operation in operations: with self.subTest(operation=operation): self.assertIs(get_operation_stage(operation), Stage.PRE_DEPLOY)
class Migration(BaseMigration): """Represents initial migration.""" initial: bool = True dependencies: List[str] = [] operations: List[CreateModel] = [ CreateModel( name="Quote", fields=[ ( "id", AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("quote", TextField()), ("author", CharField(max_length=100)), ("source", URLField(blank=True, null=True)), ["cover", URLField(blank=True, null=True)], ("added", DateTimeField(auto_now_add=True)), ("edited", DateTimeField(auto_now=True)), ], options={"ordering": ["-added"]}, ), ]
def reduce_create_model_add_field(self, operation, other, in_between): if operation.name_lower == other.model_name_lower: # Don't allow optimizations of FKs through models they reference if hasattr(other.field, "remote_field") and other.field.remote_field: for between in in_between: # Check that it doesn't point to the model app_label, object_name = self.model_to_key( other.field.remote_field.model) if between.references_model(object_name, app_label): return None # Check that it's not through the model if getattr(other.field.remote_field, "through", None): app_label, object_name = self.model_to_key( other.field.remote_field.through) if between.references_model(object_name, app_label): return None # OK, that's fine return [ CreateModel( operation.name, fields=operation.fields + [(other.name, other.field)], options=operation.options, bases=operation.bases, managers=operation.managers, ) ]
def test_unique_index_migrations(): index = UniqueIndex(fields=["name", "other_name"], name="index1") ops = [ CreateModel( name="mymodel", fields=[ ("name", models.TextField()), ("other_name", models.TextField()), ], options={ # "indexes": [index], }, ), AddIndex(model_name="mymodel", index=index), ] with filtered_schema_editor("CREATE UNIQUE INDEX") as calls: apply_migration(ops) calls = [call[0] for _, call, _ in calls["CREATE UNIQUE INDEX"]] db_table = "tests_mymodel" query = 'CREATE UNIQUE INDEX "index1" ON "{0}" ("name", "other_name")' assert str(calls[0]) == query.format(db_table)
def test_ciui_migrations(): """Tests whether migrations for case sensitive indexes are being created as expected.""" index_1 = CaseInsensitiveUniqueIndex( fields=["name", "other_name"], name="index1" ) ops = [ CreateModel( name="mymodel", fields=[ ("name", models.CharField(max_length=255)), ("other_name", models.CharField(max_length=255)), ], ), AddIndex(model_name="mymodel", index=index_1), ] with filtered_schema_editor("CREATE UNIQUE INDEX") as calls: apply_migration(ops) sql = str([call[0] for _, call, _ in calls["CREATE UNIQUE INDEX"]][0]) expected_sql = 'CREATE UNIQUE INDEX "index1" ON "tests_mymodel" (LOWER("name"), LOWER("other_name"))' assert sql == expected_sql
def test_ambiguous_operations(self): self.migration.operations = [ CreateModel("model", []), DeleteModel("model") ] with self.assertRaises(AmbiguousStage): must_post_deploy_migration(self.migration)
def test_ambiguous_operations(self): self.migration.operations = [ CreateModel("model", []), DeleteModel("model") ] with self.assertRaises(AmbiguousStage): get_migration_stage(self.migration)
def reduce_create_model_remove_field(self, operation, other, in_between): if operation.name_lower == other.model_name_lower: return [ CreateModel( operation.name, fields=[(n, v) for n, v in operation.fields if n.lower() != other.name_lower], options=operation.options, bases=operation.bases, managers=operation.managers, ) ]
def test_operations_stages(self): self.assertIsNone(get_migration_stage(self.migration)) self.migration.operations = [CreateModel("model", [])] self.assertEqual(get_migration_stage(self.migration), Stage.PRE_DEPLOY) self.migration.operations = [ DeleteModel("model"), RemoveField("model", "field"), ] self.assertEqual(get_migration_stage(self.migration), Stage.POST_DEPLOY)
def add_create_view_model(self, model: Model, operation: CreateModel): """Adds a :see:PostgresCreateViewModel operation to the list of operations to execute in the migration.""" view_options = model._view_meta.original_attrs _, args, kwargs = operation.deconstruct() self.add( operations.PostgresCreateViewModel( *args, **kwargs, view_options=view_options ) )
def reduce_create_model_rename_field(self, operation, other, in_between): if operation.name_lower == other.model_name_lower: return [ CreateModel( operation.name, fields=[(other.new_name if n == other.old_name else n, v) for n, v in operation.fields], options=operation.options, bases=operation.bases, managers=operation.managers, ) ]
def test_stage_fallback_setting(self): self.migration.operations = [ CreateModel("model", []), DeleteModel("model") ] with self.assertRaises(AmbiguousStage): get_migration_stage(self.migration) overrides = ["tests.migration", "tests"] for stage, override in product(Stage, overrides): with self.subTest(stage=stage, override=override), self.settings( MIGRATION_STAGES_FALLBACK={override: stage}): self.assertIs(get_migration_stage(self.migration), stage)
def add_delete_model(self, operation: CreateModel): """Adds the specified :see:Deletemodel operation to the list of operations to execute in the migration.""" model = self.autodetector.old_apps.get_model(self.app_label, operation.name) if not issubclass(model, PostgresPartitionedModel): return self.add(operation) _, args, kwargs = operation.deconstruct() return self.add( operations.PostgresDeletePartitionedModel(*args, **kwargs))
def reduce_create_model_rename_model(self, operation, other, in_between): """ Folds a model rename into its create """ if operation.name_lower == other.old_name_lower: return [ CreateModel( other.new_name, fields=operation.fields, options=operation.options, bases=operation.bases, managers=operation.managers, ) ]
class PartitionOperationsTests(SimpleTestCase): pre_deploy_operations = [ CreateModel("model", []), ] post_deploy_operations = [ DeleteModel("model"), ] def test_empty(self): self.assertEqual(partition_operations([], "migrations"), ([], [])) def test_pre_deploy_only(self): self.assertEqual( partition_operations(self.pre_deploy_operations, "migrations"), (self.pre_deploy_operations, []), ) def test_post_deploy_only(self): self.assertEqual( partition_operations(self.post_deploy_operations, "migrations"), ([], self.post_deploy_operations), ) def test_mixed(self): self.assertEqual( partition_operations( self.pre_deploy_operations + self.post_deploy_operations, "migrations"), (self.pre_deploy_operations, self.post_deploy_operations), ) def test_mixed_reorder(self): post_deploy_operations = [DeleteModel("other")] self.assertEqual( partition_operations( post_deploy_operations + self.pre_deploy_operations, "migrations"), (self.pre_deploy_operations, post_deploy_operations), ) def test_ambiguous(self): with self.assertRaises(AmbiguousStage): partition_operations( self.post_deploy_operations + self.pre_deploy_operations, "migrations")
def add_create_materialized_view_model(self, operation: CreateModel): """Adds a :see:PostgresCreateMaterializedViewModel operation to the list of operations to execute in the migration.""" if django.VERSION >= (4, 0): model_state = self.autodetector.to_state.models[ self.app_label, operation.name.lower()] view_options = model_state.view_options else: model = self.autodetector.new_apps.get_model( self.app_label, operation.name) view_options = model._view_meta.original_attrs _, args, kwargs = operation.deconstruct() self.add( operations.PostgresCreateMaterializedViewModel( *args, **kwargs, view_options=view_options))
def add_create_model(self, operation: CreateModel): """Adds the specified :see:CreateModel operation to the list of operations to execute in the migration.""" model = self.autodetector.new_apps.get_model(self.app_label, operation.name) if not issubclass(model, PostgresPartitionedModel): return self.add(operation) partitioning_options = model._partitioning_meta.original_attrs _, args, kwargs = operation.deconstruct() self.add( operations.PostgresAddDefaultPartition(model_name=model.__name__, name="default")) self.add( operations.PostgresCreatePartitionedModel( *args, **kwargs, partitioning_options=partitioning_options))
def add_create_partitioned_model( self, model: Model, operation: CreateModel ): """Adds a :see:PostgresCreatePartitionedModel operation to the list of operations to execute in the migration.""" partitioning_options = model._partitioning_meta.original_attrs _, args, kwargs = operation.deconstruct() self.add( operations.PostgresAddDefaultPartition( model_name=model.__name__, name="default" ) ) self.add( operations.PostgresCreatePartitionedModel( *args, **kwargs, partitioning_options=partitioning_options ) )
def test_cui_migrations(): """Tests whether the migrations are properly generated and executed.""" index_1 = ConditionalUniqueIndex( fields=["name", "other_name"], condition='"name" IS NOT NULL', name="index1", ) index_2 = ConditionalUniqueIndex(fields=["other_name"], condition='"name" IS NULL', name="index2") ops = [ CreateModel( name="mymodel", fields=[ ("id", models.IntegerField(primary_key=True)), ("name", models.CharField(max_length=255, null=True)), ("other_name", models.CharField(max_length=255)), ], options={ # "indexes": [index_1, index_2], }, ), AddIndex(model_name="mymodel", index=index_1), AddIndex(model_name="mymodel", index=index_2), ] with filtered_schema_editor("CREATE UNIQUE INDEX") as calls: apply_migration(ops) calls = [call[0] for _, call, _ in calls["CREATE UNIQUE INDEX"]] db_table = "tests_mymodel" query = 'CREATE UNIQUE INDEX "index1" ON "{0}" ("name", "other_name") WHERE "name" IS NOT NULL' assert str(calls[0]) == query.format(db_table) query = 'CREATE UNIQUE INDEX "index2" ON "{0}" ("other_name") WHERE "name" IS NULL' assert str(calls[1]) == query.format(db_table)
def add_create_partitioned_model(self, operation: CreateModel): """Adds a :see:PostgresCreatePartitionedModel operation to the list of operations to execute in the migration.""" if django.VERSION >= (4, 0): model_state = self.autodetector.to_state.models[ self.app_label, operation.name.lower()] partitioning_options = model_state.partitioning_options else: model = self.autodetector.new_apps.get_model( self.app_label, operation.name) partitioning_options = model._partitioning_meta.original_attrs _, args, kwargs = operation.deconstruct() if partitioning_options["method"] != PostgresPartitioningMethod.HASH: self.add( operations.PostgresAddDefaultPartition( model_name=operation.name, name="default")) self.add( operations.PostgresCreatePartitionedModel( *args, **kwargs, partitioning_options=partitioning_options))