diff options
| author | kimsoungryoul <kimsoungryoul@gmail.com> | 2022-10-16 14:59:39 +0900 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-12-28 06:28:07 +0100 |
| commit | 78f163a4fb3937aca2e71786fbdd51a0ef39629e (patch) | |
| tree | 7e61c2f8d96b9dab60e317d3483460064327d701 /tests | |
| parent | 68ef274bc505cd44f305c03cbf84cf08826200a8 (diff) | |
Fixed #18468 -- Added support for comments on columns and tables.
Thanks Jared Chung, Tom Carrick, David Smith, Nick Pope, and Mariusz
Felisiak for reviews.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Co-authored-by: Nick Pope <nick@nickpope.me.uk>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/inspectdb/models.py | 8 | ||||
| -rw-r--r-- | tests/inspectdb/tests.py | 18 | ||||
| -rw-r--r-- | tests/introspection/models.py | 8 | ||||
| -rw-r--r-- | tests/introspection/tests.py | 21 | ||||
| -rw-r--r-- | tests/invalid_models_tests/test_models.py | 31 | ||||
| -rw-r--r-- | tests/invalid_models_tests/test_ordinary_fields.py | 32 | ||||
| -rw-r--r-- | tests/invalid_models_tests/test_relative_fields.py | 9 | ||||
| -rw-r--r-- | tests/migrations/test_autodetector.py | 60 | ||||
| -rw-r--r-- | tests/migrations/test_base.py | 14 | ||||
| -rw-r--r-- | tests/migrations/test_operations.py | 31 | ||||
| -rw-r--r-- | tests/schema/tests.py | 201 |
11 files changed, 432 insertions, 1 deletions
diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py index c07cd4def1..5db4d3bc73 100644 --- a/tests/inspectdb/models.py +++ b/tests/inspectdb/models.py @@ -132,3 +132,11 @@ class FuncUniqueConstraint(models.Model): ) ] required_db_features = {"supports_expression_indexes"} + + +class DbComment(models.Model): + rank = models.IntegerField(db_comment="'Rank' column comment") + + class Meta: + db_table_comment = "Custom table comment" + required_db_features = {"supports_comments"} diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py index 7944e5f221..2f26814625 100644 --- a/tests/inspectdb/tests.py +++ b/tests/inspectdb/tests.py @@ -129,6 +129,24 @@ class InspectDBTestCase(TestCase): "null_json_field = models.JSONField(blank=True, null=True)", output ) + @skipUnlessDBFeature("supports_comments") + def test_db_comments(self): + out = StringIO() + call_command("inspectdb", "inspectdb_dbcomment", stdout=out) + output = out.getvalue() + integer_field_type = connection.features.introspected_field_types[ + "IntegerField" + ] + self.assertIn( + f"rank = models.{integer_field_type}(" + f"db_comment=\"'Rank' column comment\")", + output, + ) + self.assertIn( + " db_table_comment = 'Custom table comment'", + output, + ) + @skipUnlessDBFeature("supports_collation_on_charfield") @skipUnless(test_collation, "Language collations are not supported.") def test_char_field_db_collation(self): diff --git a/tests/introspection/models.py b/tests/introspection/models.py index 31c1b0de80..d31eb0cbfa 100644 --- a/tests/introspection/models.py +++ b/tests/introspection/models.py @@ -102,3 +102,11 @@ class UniqueConstraintConditionModel(models.Model): condition=models.Q(color__isnull=True), ), ] + + +class DbCommentModel(models.Model): + name = models.CharField(max_length=15, db_comment="'Name' column comment") + + class Meta: + db_table_comment = "Custom table comment" + required_db_features = {"supports_comments"} diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py index 459a405932..a283aa0769 100644 --- a/tests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -9,6 +9,7 @@ from .models import ( City, Comment, Country, + DbCommentModel, District, Reporter, UniqueConstraintConditionModel, @@ -179,6 +180,26 @@ class IntrospectionTests(TransactionTestCase): [connection.introspection.get_field_type(r[1], r) for r in desc], ) + @skipUnlessDBFeature("supports_comments") + def test_db_comments(self): + with connection.cursor() as cursor: + desc = connection.introspection.get_table_description( + cursor, DbCommentModel._meta.db_table + ) + table_list = connection.introspection.get_table_list(cursor) + self.assertEqual( + ["'Name' column comment"], + [field.comment for field in desc if field.name == "name"], + ) + self.assertEqual( + ["Custom table comment"], + [ + table.comment + for table in table_list + if table.name == "introspection_dbcommentmodel" + ], + ) + # Regression test for #9991 - 'real' types in postgres @skipUnlessDBFeature("has_real_datatype") def test_postgresql_real_type(self): diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index 5a5aeccdf5..c07c83d79d 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -1872,6 +1872,37 @@ class OtherModelTests(SimpleTestCase): ) +@isolate_apps("invalid_models_tests") +class DbTableCommentTests(TestCase): + def test_db_table_comment(self): + class Model(models.Model): + class Meta: + db_table_comment = "Table comment" + + errors = Model.check(databases=self.databases) + expected = ( + [] + if connection.features.supports_comments + else [ + Warning( + f"{connection.display_name} does not support comments on tables " + f"(db_table_comment).", + obj=Model, + id="models.W046", + ), + ] + ) + self.assertEqual(errors, expected) + + def test_db_table_comment_required_db_features(self): + class Model(models.Model): + class Meta: + db_table_comment = "Table comment" + required_db_features = {"supports_comments"} + + self.assertEqual(Model.check(databases=self.databases), []) + + class MultipleAutoFieldsTests(TestCase): def test_multiple_autofields(self): msg = ( diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py index ef7f845a33..4e07c95956 100644 --- a/tests/invalid_models_tests/test_ordinary_fields.py +++ b/tests/invalid_models_tests/test_ordinary_fields.py @@ -1023,3 +1023,35 @@ class JSONFieldTests(TestCase): field = models.JSONField(default=callable_default) self.assertEqual(Model._meta.get_field("field").check(), []) + + +@isolate_apps("invalid_models_tests") +class DbCommentTests(TestCase): + def test_db_comment(self): + class Model(models.Model): + field = models.IntegerField(db_comment="Column comment") + + errors = Model._meta.get_field("field").check(databases=self.databases) + expected = ( + [] + if connection.features.supports_comments + else [ + DjangoWarning( + f"{connection.display_name} does not support comments on columns " + f"(db_comment).", + obj=Model._meta.get_field("field"), + id="fields.W163", + ), + ] + ) + self.assertEqual(errors, expected) + + def test_db_comment_required_db_features(self): + class Model(models.Model): + field = models.IntegerField(db_comment="Column comment") + + class Meta: + required_db_features = {"supports_comments"} + + errors = Model._meta.get_field("field").check(databases=self.databases) + self.assertEqual(errors, []) diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 5b4bb45ff8..075bbaefbc 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -94,7 +94,9 @@ class RelativeFieldTests(SimpleTestCase): name = models.CharField(max_length=20) class ModelM2M(models.Model): - m2m = models.ManyToManyField(Model, null=True, validators=[lambda x: x]) + m2m = models.ManyToManyField( + Model, null=True, validators=[lambda x: x], db_comment="Column comment" + ) field = ModelM2M._meta.get_field("m2m") self.assertEqual( @@ -110,6 +112,11 @@ class RelativeFieldTests(SimpleTestCase): obj=field, id="fields.W341", ), + DjangoWarning( + "db_comment has no effect on ManyToManyField.", + obj=field, + id="fields.W346", + ), ], ) diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index d2f9be75f7..82ddb17543 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -773,6 +773,14 @@ class AutodetectorTests(BaseAutodetectorTests): "verbose_name": "Authi", }, ) + author_with_db_table_comment = ModelState( + "testapp", + "Author", + [ + ("id", models.AutoField(primary_key=True)), + ], + {"db_table_comment": "Table comment"}, + ) author_with_db_table_options = ModelState( "testapp", "Author", @@ -2349,6 +2357,58 @@ class AutodetectorTests(BaseAutodetectorTests): changes, "testapp", 0, 1, name="newauthor", table="author_three" ) + def test_alter_db_table_comment_add(self): + changes = self.get_changes( + [self.author_empty], [self.author_with_db_table_comment] + ) + self.assertNumberMigrations(changes, "testapp", 1) + self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTableComment"]) + self.assertOperationAttributes( + changes, "testapp", 0, 0, name="author", table_comment="Table comment" + ) + + def test_alter_db_table_comment_change(self): + author_with_new_db_table_comment = ModelState( + "testapp", + "Author", + [ + ("id", models.AutoField(primary_key=True)), + ], + {"db_table_comment": "New table comment"}, + ) + changes = self.get_changes( + [self.author_with_db_table_comment], + [author_with_new_db_table_comment], + ) + self.assertNumberMigrations(changes, "testapp", 1) + self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTableComment"]) + self.assertOperationAttributes( + changes, + "testapp", + 0, + 0, + name="author", + table_comment="New table comment", + ) + + def test_alter_db_table_comment_remove(self): + changes = self.get_changes( + [self.author_with_db_table_comment], + [self.author_empty], + ) + self.assertNumberMigrations(changes, "testapp", 1) + self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTableComment"]) + self.assertOperationAttributes( + changes, "testapp", 0, 0, name="author", db_table_comment=None + ) + + def test_alter_db_table_comment_no_changes(self): + changes = self.get_changes( + [self.author_with_db_table_comment], + [self.author_with_db_table_comment], + ) + self.assertNumberMigrations(changes, "testapp", 0) + def test_identical_regex_doesnt_alter(self): from_state = ModelState( "testapp", diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index 3f1559b8d6..f038cd7605 100644 --- a/tests/migrations/test_base.py +++ b/tests/migrations/test_base.py @@ -75,6 +75,20 @@ class MigrationTestBase(TransactionTestCase): def assertColumnCollation(self, table, column, collation, using="default"): self.assertEqual(self._get_column_collation(table, column, using), collation) + def _get_table_comment(self, table, using): + with connections[using].cursor() as cursor: + return next( + t.comment + for t in connections[using].introspection.get_table_list(cursor) + if t.name == table + ) + + def assertTableComment(self, table, comment, using="default"): + self.assertEqual(self._get_table_comment(table, using), comment) + + def assertTableCommentNotExists(self, table, using="default"): + self.assertIn(self._get_table_comment(table, using), [None, ""]) + def assertIndexExists( self, table, columns, value=True, using="default", index_type=None ): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 2bcb38feae..129360629d 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1922,6 +1922,37 @@ class OperationTests(OperationTestBase): operation.database_forwards(app_label, editor, new_state, project_state) self.assertColumnExists(rider_table, "pony_id") + @skipUnlessDBFeature("supports_comments") + def test_alter_model_table_comment(self): + app_label = "test_almotaco" + project_state = self.set_up_test_model(app_label) + pony_table = f"{app_label}_pony" + # Add table comment. + operation = migrations.AlterModelTableComment("Pony", "Custom pony comment") + self.assertEqual(operation.describe(), "Alter Pony table comment") + self.assertEqual(operation.migration_name_fragment, "alter_pony_table_comment") + new_state = project_state.clone() + operation.state_forwards(app_label, new_state) + self.assertEqual( + new_state.models[app_label, "pony"].options["db_table_comment"], + "Custom pony comment", + ) + self.assertTableCommentNotExists(pony_table) + with connection.schema_editor() as editor: + operation.database_forwards(app_label, editor, project_state, new_state) + self.assertTableComment(pony_table, "Custom pony comment") + # Reversal. + with connection.schema_editor() as editor: + operation.database_backwards(app_label, editor, new_state, project_state) + self.assertTableCommentNotExists(pony_table) + # Deconstruction. + definition = operation.deconstruct() + self.assertEqual(definition[0], "AlterModelTableComment") + self.assertEqual(definition[1], []) + self.assertEqual( + definition[2], {"name": "Pony", "table_comment": "Custom pony comment"} + ) + def test_alter_field_pk(self): """ The AlterField operation on primary keys (things like PostgreSQL's diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 9b2b512c1b..e325fa0c88 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -273,6 +273,27 @@ class SchemaTests(TransactionTestCase): if f.name == column ) + def get_column_comment(self, table, column): + with connection.cursor() as cursor: + return next( + f.comment + for f in connection.introspection.get_table_description(cursor, table) + if f.name == column + ) + + def get_table_comment(self, table): + with connection.cursor() as cursor: + return next( + t.comment + for t in connection.introspection.get_table_list(cursor) + if t.name == table + ) + + def assert_column_comment_not_exists(self, table, column): + with connection.cursor() as cursor: + columns = connection.introspection.get_table_description(cursor, table) + self.assertFalse(any([c.name == column and c.comment for c in columns])) + def assertIndexOrder(self, table, index, order): constraints = self.get_constraints(table) self.assertIn(index, constraints) @@ -4390,6 +4411,186 @@ class SchemaTests(TransactionTestCase): ], ) + @skipUnlessDBFeature("supports_comments") + def test_add_db_comment_charfield(self): + comment = "Custom comment" + field = CharField(max_length=255, db_comment=comment) + field.set_attributes_from_name("name_with_comment") + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.add_field(Author, field) + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name_with_comment"), + comment, + ) + + @skipUnlessDBFeature("supports_comments") + def test_add_db_comment_and_default_charfield(self): + comment = "Custom comment with default" + field = CharField(max_length=255, default="Joe Doe", db_comment=comment) + field.set_attributes_from_name("name_with_comment_default") + with connection.schema_editor() as editor: + editor.create_model(Author) + Author.objects.create(name="Before adding a new field") + editor.add_field(Author, field) + + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name_with_comment_default"), + comment, + ) + with connection.cursor() as cursor: + cursor.execute( + f"SELECT name_with_comment_default FROM {Author._meta.db_table};" + ) + for row in cursor.fetchall(): + self.assertEqual(row[0], "Joe Doe") + + @skipUnlessDBFeature("supports_comments") + def test_alter_db_comment(self): + with connection.schema_editor() as editor: + editor.create_model(Author) + # Add comment. + old_field = Author._meta.get_field("name") + new_field = CharField(max_length=255, db_comment="Custom comment") + new_field.set_attributes_from_name("name") + with connection.schema_editor() as editor: + editor.alter_field(Author, old_field, new_field, strict=True) + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name"), + "Custom comment", + ) + # Alter comment. + old_field = new_field + new_field = CharField(max_length=255, db_comment="New custom comment") + new_field.set_attributes_from_name("name") + with connection.schema_editor() as editor: + editor.alter_field(Author, old_field, new_field, strict=True) + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name"), + "New custom comment", + ) + # Remove comment. + old_field = new_field + new_field = CharField(max_length=255) + new_field.set_attributes_from_name("name") + with connection.schema_editor() as editor: + editor.alter_field(Author, old_field, new_field, strict=True) + self.assertIn( + self.get_column_comment(Author._meta.db_table, "name"), + [None, ""], + ) + + @skipUnlessDBFeature("supports_comments", "supports_foreign_keys") + def test_alter_db_comment_foreign_key(self): + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + + comment = "FK custom comment" + old_field = Book._meta.get_field("author") + new_field = ForeignKey(Author, CASCADE, db_comment=comment) + new_field.set_attributes_from_name("author") + with connection.schema_editor() as editor: + editor.alter_field(Book, old_field, new_field, strict=True) + self.assertEqual( + self.get_column_comment(Book._meta.db_table, "author_id"), + comment, + ) + + @skipUnlessDBFeature("supports_comments") + def test_alter_field_type_preserve_comment(self): + with connection.schema_editor() as editor: + editor.create_model(Author) + + comment = "This is the name." + old_field = Author._meta.get_field("name") + new_field = CharField(max_length=255, db_comment=comment) + new_field.set_attributes_from_name("name") + new_field.model = Author + with connection.schema_editor() as editor: + editor.alter_field(Author, old_field, new_field, strict=True) + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name"), + comment, + ) + # Changing a field type should preserve the comment. + old_field = new_field + new_field = CharField(max_length=511, db_comment=comment) + new_field.set_attributes_from_name("name") + new_field.model = Author + with connection.schema_editor() as editor: + editor.alter_field(Author, new_field, old_field, strict=True) + # Comment is preserved. + self.assertEqual( + self.get_column_comment(Author._meta.db_table, "name"), + comment, + ) + + @isolate_apps("schema") + @skipUnlessDBFeature("supports_comments") + def test_db_comment_table(self): + class ModelWithDbTableComment(Model): + class Meta: + app_label = "schema" + db_table_comment = "Custom table comment" + + with connection.schema_editor() as editor: + editor.create_model(ModelWithDbTableComment) + self.isolated_local_models = [ModelWithDbTableComment] + self.assertEqual( + self.get_table_comment(ModelWithDbTableComment._meta.db_table), + "Custom table comment", + ) + # Alter table comment. + old_db_table_comment = ModelWithDbTableComment._meta.db_table_comment + with connection.schema_editor() as editor: + editor.alter_db_table_comment( + ModelWithDbTableComment, old_db_table_comment, "New table comment" + ) + self.assertEqual( + self.get_table_comment(ModelWithDbTableComment._meta.db_table), + "New table comment", + ) + # Remove table comment. + old_db_table_comment = ModelWithDbTableComment._meta.db_table_comment + with connection.schema_editor() as editor: + editor.alter_db_table_comment( + ModelWithDbTableComment, old_db_table_comment, None + ) + self.assertIn( + self.get_table_comment(ModelWithDbTableComment._meta.db_table), + [None, ""], + ) + + @isolate_apps("schema") + @skipUnlessDBFeature("supports_comments", "supports_foreign_keys") + def test_db_comments_from_abstract_model(self): + class AbstractModelWithDbComments(Model): + name = CharField( + max_length=255, db_comment="Custom comment", null=True, blank=True + ) + + class Meta: + app_label = "schema" + abstract = True + db_table_comment = "Custom table comment" + + class ModelWithDbComments(AbstractModelWithDbComments): + pass + + with connection.schema_editor() as editor: + editor.create_model(ModelWithDbComments) + self.isolated_local_models = [ModelWithDbComments] + + self.assertEqual( + self.get_column_comment(ModelWithDbComments._meta.db_table, "name"), + "Custom comment", + ) + self.assertEqual( + self.get_table_comment(ModelWithDbComments._meta.db_table), + "Custom table comment", + ) + @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific") def test_alter_field_add_index_to_charfield(self): # Create the table and verify no initial indexes. |
