summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorkimsoungryoul <kimsoungryoul@gmail.com>2022-10-16 14:59:39 +0900
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-12-28 06:28:07 +0100
commit78f163a4fb3937aca2e71786fbdd51a0ef39629e (patch)
tree7e61c2f8d96b9dab60e317d3483460064327d701 /tests
parent68ef274bc505cd44f305c03cbf84cf08826200a8 (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.py8
-rw-r--r--tests/inspectdb/tests.py18
-rw-r--r--tests/introspection/models.py8
-rw-r--r--tests/introspection/tests.py21
-rw-r--r--tests/invalid_models_tests/test_models.py31
-rw-r--r--tests/invalid_models_tests/test_ordinary_fields.py32
-rw-r--r--tests/invalid_models_tests/test_relative_fields.py9
-rw-r--r--tests/migrations/test_autodetector.py60
-rw-r--r--tests/migrations/test_base.py14
-rw-r--r--tests/migrations/test_operations.py31
-rw-r--r--tests/schema/tests.py201
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.