Fixed #31700 -- Made makemigrations command display meaningful symbols for each operation.

This commit is contained in:
Amir Karimi 2023-09-16 05:41:22 +03:30 committed by Mariusz Felisiak
parent c7e986fc9f
commit 27a3eee721
14 changed files with 214 additions and 19 deletions

View File

@ -5,12 +5,13 @@ from django.contrib.postgres.signals import (
)
from django.db import NotSupportedError, router
from django.db.migrations import AddConstraint, AddIndex, RemoveIndex
from django.db.migrations.operations.base import Operation
from django.db.migrations.operations.base import Operation, OperationCategory
from django.db.models.constraints import CheckConstraint
class CreateExtension(Operation):
reversible = True
category = OperationCategory.ADDITION
def __init__(self, name):
self.name = name
@ -120,6 +121,7 @@ class AddIndexConcurrently(NotInTransactionMixin, AddIndex):
"""Create an index using PostgreSQL's CREATE INDEX CONCURRENTLY syntax."""
atomic = False
category = OperationCategory.ADDITION
def describe(self):
return "Concurrently create index %s on field(s) %s of model %s" % (
@ -145,6 +147,7 @@ class RemoveIndexConcurrently(NotInTransactionMixin, RemoveIndex):
"""Remove an index using PostgreSQL's DROP INDEX CONCURRENTLY syntax."""
atomic = False
category = OperationCategory.REMOVAL
def describe(self):
return "Concurrently remove index %s from %s" % (self.name, self.model_name)
@ -213,6 +216,8 @@ class CollationOperation(Operation):
class CreateCollation(CollationOperation):
"""Create a collation."""
category = OperationCategory.ADDITION
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor != "postgresql" or not router.allow_migrate(
schema_editor.connection.alias, app_label
@ -236,6 +241,8 @@ class CreateCollation(CollationOperation):
class RemoveCollation(CollationOperation):
"""Remove a collation."""
category = OperationCategory.REMOVAL
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor != "postgresql" or not router.allow_migrate(
schema_editor.connection.alias, app_label
@ -262,6 +269,8 @@ class AddConstraintNotValid(AddConstraint):
NOT VALID syntax.
"""
category = OperationCategory.ADDITION
def __init__(self, model_name, constraint):
if not isinstance(constraint, CheckConstraint):
raise TypeError(
@ -293,6 +302,8 @@ class AddConstraintNotValid(AddConstraint):
class ValidateConstraint(Operation):
"""Validate a table NOT VALID constraint."""
category = OperationCategory.ALTERATION
def __init__(self, model_name, name):
self.model_name = model_name
self.name = name

View File

@ -348,7 +348,7 @@ class Command(BaseCommand):
migration_string = self.get_relative_path(writer.path)
self.log(" %s\n" % self.style.MIGRATE_LABEL(migration_string))
for operation in migration.operations:
self.log(" - %s" % operation.describe())
self.log(" %s" % operation.formatted_description())
if self.scriptable:
self.stdout.write(migration_string)
if not self.dry_run:
@ -456,7 +456,7 @@ class Command(BaseCommand):
for migration in merge_migrations:
self.log(self.style.MIGRATE_LABEL(" Branch %s" % migration.name))
for operation in migration.merged_operations:
self.log(" - %s" % operation.describe())
self.log(" %s" % operation.formatted_description())
if questioner.ask_merge(app_label):
# If they still want to merge it, then write out an empty
# file depending on the migrations needing merging.

View File

@ -1,6 +1,17 @@
import enum
from django.db import router
class OperationCategory(str, enum.Enum):
ADDITION = "+"
REMOVAL = "-"
ALTERATION = "~"
PYTHON = "p"
SQL = "s"
MIXED = "?"
class Operation:
"""
Base class for migration operations.
@ -33,6 +44,8 @@ class Operation:
serialization_expand_args = []
category = None
def __new__(cls, *args, **kwargs):
# We capture the arguments to make returning them trivial
self = object.__new__(cls)
@ -85,6 +98,13 @@ class Operation:
"""
return "%s: %s" % (self.__class__.__name__, self._constructor_args)
def formatted_description(self):
"""Output a description prefixed by a category symbol."""
description = self.describe()
if self.category is None:
return f"{OperationCategory.MIXED.value} {description}"
return f"{self.category.value} {description}"
@property
def migration_name_fragment(self):
"""

View File

@ -2,7 +2,7 @@ from django.db.migrations.utils import field_references
from django.db.models import NOT_PROVIDED
from django.utils.functional import cached_property
from .base import Operation
from .base import Operation, OperationCategory
class FieldOperation(Operation):
@ -75,6 +75,8 @@ class FieldOperation(Operation):
class AddField(FieldOperation):
"""Add a field to a model."""
category = OperationCategory.ADDITION
def __init__(self, model_name, name, field, preserve_default=True):
self.preserve_default = preserve_default
super().__init__(model_name, name, field)
@ -154,6 +156,8 @@ class AddField(FieldOperation):
class RemoveField(FieldOperation):
"""Remove a field from a model."""
category = OperationCategory.REMOVAL
def deconstruct(self):
kwargs = {
"model_name": self.model_name,
@ -201,6 +205,8 @@ class AlterField(FieldOperation):
new field.
"""
category = OperationCategory.ALTERATION
def __init__(self, model_name, name, field, preserve_default=True):
self.preserve_default = preserve_default
super().__init__(model_name, name, field)
@ -270,6 +276,8 @@ class AlterField(FieldOperation):
class RenameField(FieldOperation):
"""Rename a field on the model. Might affect db_column too."""
category = OperationCategory.ALTERATION
def __init__(self, model_name, old_name, new_name):
self.old_name = old_name
self.new_name = new_name

View File

@ -1,5 +1,5 @@
from django.db import models
from django.db.migrations.operations.base import Operation
from django.db.migrations.operations.base import Operation, OperationCategory
from django.db.migrations.state import ModelState
from django.db.migrations.utils import field_references, resolve_relation
from django.db.models.options import normalize_together
@ -41,6 +41,7 @@ class ModelOperation(Operation):
class CreateModel(ModelOperation):
"""Create a model's table."""
category = OperationCategory.ADDITION
serialization_expand_args = ["fields", "options", "managers"]
def __init__(self, name, fields, options=None, bases=None, managers=None):
@ -347,6 +348,8 @@ class CreateModel(ModelOperation):
class DeleteModel(ModelOperation):
"""Drop a model's table."""
category = OperationCategory.REMOVAL
def deconstruct(self):
kwargs = {
"name": self.name,
@ -382,6 +385,8 @@ class DeleteModel(ModelOperation):
class RenameModel(ModelOperation):
"""Rename a model."""
category = OperationCategory.ALTERATION
def __init__(self, old_name, new_name):
self.old_name = old_name
self.new_name = new_name
@ -499,6 +504,8 @@ class RenameModel(ModelOperation):
class ModelOptionOperation(ModelOperation):
category = OperationCategory.ALTERATION
def reduce(self, operation, app_label):
if (
isinstance(operation, (self.__class__, DeleteModel))
@ -849,6 +856,8 @@ class IndexOperation(Operation):
class AddIndex(IndexOperation):
"""Add an index on a model."""
category = OperationCategory.ADDITION
def __init__(self, model_name, index):
self.model_name = model_name
if not index.name:
@ -911,6 +920,8 @@ class AddIndex(IndexOperation):
class RemoveIndex(IndexOperation):
"""Remove an index from a model."""
category = OperationCategory.REMOVAL
def __init__(self, model_name, name):
self.model_name = model_name
self.name = name
@ -954,6 +965,8 @@ class RemoveIndex(IndexOperation):
class RenameIndex(IndexOperation):
"""Rename an index."""
category = OperationCategory.ALTERATION
def __init__(self, model_name, new_name, old_name=None, old_fields=None):
if not old_name and not old_fields:
raise ValueError(
@ -1104,6 +1117,7 @@ class RenameIndex(IndexOperation):
class AddConstraint(IndexOperation):
category = OperationCategory.ADDITION
option_name = "constraints"
def __init__(self, model_name, constraint):
@ -1154,6 +1168,7 @@ class AddConstraint(IndexOperation):
class RemoveConstraint(IndexOperation):
category = OperationCategory.REMOVAL
option_name = "constraints"
def __init__(self, model_name, name):

View File

@ -1,6 +1,6 @@
from django.db import router
from .base import Operation
from .base import Operation, OperationCategory
class SeparateDatabaseAndState(Operation):
@ -11,6 +11,7 @@ class SeparateDatabaseAndState(Operation):
that affect the state or not the database, or so on.
"""
category = OperationCategory.MIXED
serialization_expand_args = ["database_operations", "state_operations"]
def __init__(self, database_operations=None, state_operations=None):
@ -68,6 +69,7 @@ class RunSQL(Operation):
by this SQL change, in case it's custom column/table creation/deletion.
"""
category = OperationCategory.SQL
noop = ""
def __init__(
@ -138,6 +140,7 @@ class RunPython(Operation):
Run Python code in a context suitable for doing versioned ORM operations.
"""
category = OperationCategory.PYTHON
reduces_to_sql = False
def __init__(

View File

@ -241,8 +241,8 @@ You should see something similar to the following:
Migrations for 'polls':
polls/migrations/0001_initial.py
- Create model Question
- Create model Choice
+ Create model Question
+ Create model Choice
By running ``makemigrations``, you're telling Django that you've made
some changes to your models (in this case, you've made new ones) and that

View File

@ -241,7 +241,7 @@ create a database migration:
$ python manage.py makemigrations
Migrations for 'world':
world/migrations/0001_initial.py:
- Create model WorldBorder
+ Create model WorldBorder
Let's look at the SQL that will generate the table for the ``WorldBorder``
model:

View File

@ -475,6 +475,42 @@ operations.
For an example using ``SeparateDatabaseAndState``, see
:ref:`changing-a-manytomanyfield-to-use-a-through-model`.
Operation category
==================
.. versionadded:: 5.1
.. currentmodule:: django.db.migrations.operations.base
.. class:: OperationCategory
Categories of migration operation used by the :djadmin:`makemigrations`
command to display meaningful symbols.
.. attribute:: ADDITION
*Symbol*: ``+``
.. attribute:: REMOVAL
*Symbol*: ``-``
.. attribute:: ALTERATION
*Symbol*: ``~``
.. attribute:: PYTHON
*Symbol*: ``p``
.. attribute:: SQL
*Symbol*: ``s``
.. attribute:: MIXED
*Symbol*: ``?``
.. _writing-your-own-migration-operation:
Writing your own
@ -495,6 +531,10 @@ structure of an ``Operation`` looks like this::
# If this is False, Django will refuse to reverse past this operation.
reversible = False
# This categorizes the operation. The corresponding symbol will be
# display by the makemigrations command.
category = OperationCategory.ADDITION
def __init__(self, arg1, arg2):
# Operations are usually instantiated with arguments in migration
# files. Store the values of them on self for later use.
@ -516,7 +556,7 @@ structure of an ``Operation`` looks like this::
pass
def describe(self):
# This is used to describe what the operation does in console output.
# This is used to describe what the operation does.
return "Custom Operation"
@property

View File

@ -178,12 +178,17 @@ Logging
Management Commands
~~~~~~~~~~~~~~~~~~~
* ...
* :djadmin:`makemigrations` command now displays meaningful symbols for each
operation to highlight :class:`operation categories
<django.db.migrations.operations.base.OperationCategory>`.
Migrations
~~~~~~~~~~
* ...
* The new ``Operation.category`` attribute allows specifying an
:class:`operation category
<django.db.migrations.operations.base.OperationCategory>` used by the
:djadmin:`makemigrations` to display a meaningful symbol for the operation.
Models
~~~~~~

View File

@ -118,7 +118,7 @@ field and remove a model - and then run :djadmin:`makemigrations`:
$ python manage.py makemigrations
Migrations for 'books':
books/migrations/0003_auto.py:
- Alter field author on book
~ Alter field author on book
Your models will be scanned and compared to the versions currently
contained in your migration files, and then a new set of migrations

View File

@ -2141,7 +2141,7 @@ class MakeMigrationsTests(MigrationTestBase):
)
# Normal --dry-run output
self.assertIn("- Add field silly_char to sillymodel", out.getvalue())
self.assertIn("+ Add field silly_char to sillymodel", out.getvalue())
# Additional output caused by verbosity 3
# The complete migrations file that would be written
@ -2171,7 +2171,7 @@ class MakeMigrationsTests(MigrationTestBase):
)
initial_file = os.path.join(migration_dir, "0001_initial.py")
self.assertEqual(out.getvalue(), f"{initial_file}\n")
self.assertIn(" - Create model ModelWithCustomBase\n", err.getvalue())
self.assertIn(" + Create model ModelWithCustomBase\n", err.getvalue())
@mock.patch("builtins.input", return_value="Y")
def test_makemigrations_scriptable_merge(self, mock_input):
@ -2216,7 +2216,7 @@ class MakeMigrationsTests(MigrationTestBase):
self.assertTrue(os.path.exists(initial_file))
# Command output indicates the migration is created.
self.assertIn(" - Create model SillyModel", out.getvalue())
self.assertIn(" + Create model SillyModel", out.getvalue())
@override_settings(MIGRATION_MODULES={"migrations": "some.nonexistent.path"})
def test_makemigrations_migrations_modules_nonexistent_toplevel_package(self):
@ -2321,12 +2321,12 @@ class MakeMigrationsTests(MigrationTestBase):
out.getvalue().lower(),
"merging conflicting_app_with_dependencies\n"
" branch 0002_conflicting_second\n"
" - create model something\n"
" + create model something\n"
" branch 0002_second\n"
" - delete model tribble\n"
" - remove field silly_field from author\n"
" - add field rating to author\n"
" - create model book\n"
" + add field rating to author\n"
" + create model book\n"
"\n"
"merging will only work if the operations printed above do not "
"conflict\n"

View File

@ -4,6 +4,7 @@ from decimal import Decimal
from django.core.exceptions import FieldDoesNotExist
from django.db import IntegrityError, connection, migrations, models, transaction
from django.db.migrations.migration import Migration
from django.db.migrations.operations.base import Operation
from django.db.migrations.operations.fields import FieldOperation
from django.db.migrations.state import ModelState, ProjectState
from django.db.models import F
@ -47,6 +48,7 @@ class OperationTests(OperationTestBase):
],
)
self.assertEqual(operation.describe(), "Create model Pony")
self.assertEqual(operation.formatted_description(), "+ Create model Pony")
self.assertEqual(operation.migration_name_fragment, "pony")
# Test the state alteration
project_state = ProjectState()
@ -710,6 +712,7 @@ class OperationTests(OperationTestBase):
# Test the state alteration
operation = migrations.DeleteModel("Pony")
self.assertEqual(operation.describe(), "Delete model Pony")
self.assertEqual(operation.formatted_description(), "- Delete model Pony")
self.assertEqual(operation.migration_name_fragment, "delete_pony")
new_state = project_state.clone()
operation.state_forwards("test_dlmo", new_state)
@ -790,6 +793,9 @@ class OperationTests(OperationTestBase):
# Test the state alteration
operation = migrations.RenameModel("Pony", "Horse")
self.assertEqual(operation.describe(), "Rename model Pony to Horse")
self.assertEqual(
operation.formatted_description(), "~ Rename model Pony to Horse"
)
self.assertEqual(operation.migration_name_fragment, "rename_pony_horse")
# Test initial state and database
self.assertIn(("test_rnmo", "pony"), project_state.models)
@ -1350,6 +1356,9 @@ class OperationTests(OperationTestBase):
models.FloatField(null=True, default=5),
)
self.assertEqual(operation.describe(), "Add field height to Pony")
self.assertEqual(
operation.formatted_description(), "+ Add field height to Pony"
)
self.assertEqual(operation.migration_name_fragment, "pony_height")
project_state, new_state = self.make_test_state("test_adfl", operation)
self.assertEqual(len(new_state.models["test_adfl", "pony"].fields), 6)
@ -1906,6 +1915,9 @@ class OperationTests(OperationTestBase):
# Test the state alteration
operation = migrations.RemoveField("Pony", "pink")
self.assertEqual(operation.describe(), "Remove field pink from Pony")
self.assertEqual(
operation.formatted_description(), "- Remove field pink from Pony"
)
self.assertEqual(operation.migration_name_fragment, "remove_pony_pink")
new_state = project_state.clone()
operation.state_forwards("test_rmfl", new_state)
@ -1952,6 +1964,10 @@ class OperationTests(OperationTestBase):
self.assertEqual(
operation.describe(), "Rename table for Pony to test_almota_pony_2"
)
self.assertEqual(
operation.formatted_description(),
"~ Rename table for Pony to test_almota_pony_2",
)
self.assertEqual(operation.migration_name_fragment, "alter_pony_table")
new_state = project_state.clone()
operation.state_forwards("test_almota", new_state)
@ -2093,6 +2109,9 @@ class OperationTests(OperationTestBase):
"Pony", "pink", models.IntegerField(null=True)
)
self.assertEqual(operation.describe(), "Alter field pink on Pony")
self.assertEqual(
operation.formatted_description(), "~ Alter field pink on Pony"
)
self.assertEqual(operation.migration_name_fragment, "alter_pony_pink")
new_state = project_state.clone()
operation.state_forwards("test_alfl", new_state)
@ -2403,6 +2422,9 @@ class OperationTests(OperationTestBase):
# Add table comment.
operation = migrations.AlterModelTableComment("Pony", "Custom pony comment")
self.assertEqual(operation.describe(), "Alter Pony table comment")
self.assertEqual(
operation.formatted_description(), "~ 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)
@ -3073,6 +3095,9 @@ class OperationTests(OperationTestBase):
project_state = self.set_up_test_model("test_rnfl")
operation = migrations.RenameField("Pony", "pink", "blue")
self.assertEqual(operation.describe(), "Rename field pink on Pony to blue")
self.assertEqual(
operation.formatted_description(), "~ Rename field pink on Pony to blue"
)
self.assertEqual(operation.migration_name_fragment, "rename_pink_pony_blue")
new_state = project_state.clone()
operation.state_forwards("test_rnfl", new_state)
@ -3326,6 +3351,10 @@ class OperationTests(OperationTestBase):
self.assertEqual(
operation.describe(), "Alter unique_together for Pony (1 constraint(s))"
)
self.assertEqual(
operation.formatted_description(),
"~ Alter unique_together for Pony (1 constraint(s))",
)
self.assertEqual(
operation.migration_name_fragment,
"alter_pony_unique_together",
@ -3478,6 +3507,10 @@ class OperationTests(OperationTestBase):
operation.describe(),
"Create index test_adin_pony_pink_idx on field(s) pink of model Pony",
)
self.assertEqual(
operation.formatted_description(),
"+ Create index test_adin_pony_pink_idx on field(s) pink of model Pony",
)
self.assertEqual(
operation.migration_name_fragment,
"pony_test_adin_pony_pink_idx",
@ -3511,6 +3544,9 @@ class OperationTests(OperationTestBase):
self.assertIndexExists("test_rmin_pony", ["pink", "weight"])
operation = migrations.RemoveIndex("Pony", "pony_test_idx")
self.assertEqual(operation.describe(), "Remove index pony_test_idx from Pony")
self.assertEqual(
operation.formatted_description(), "- Remove index pony_test_idx from Pony"
)
self.assertEqual(
operation.migration_name_fragment,
"remove_pony_pony_test_idx",
@ -3565,6 +3601,10 @@ class OperationTests(OperationTestBase):
operation.describe(),
"Rename index pony_pink_idx on Pony to new_pony_test_idx",
)
self.assertEqual(
operation.formatted_description(),
"~ Rename index pony_pink_idx on Pony to new_pony_test_idx",
)
self.assertEqual(
operation.migration_name_fragment,
"rename_pony_pink_idx_new_pony_test_idx",
@ -3807,6 +3847,10 @@ class OperationTests(OperationTestBase):
self.assertEqual(
operation.describe(), "Alter index_together for Pony (0 constraint(s))"
)
self.assertEqual(
operation.formatted_description(),
"~ Alter index_together for Pony (0 constraint(s))",
)
def test_add_constraint(self):
project_state = self.set_up_test_model("test_addconstraint")
@ -3819,6 +3863,10 @@ class OperationTests(OperationTestBase):
gt_operation.describe(),
"Create constraint test_add_constraint_pony_pink_gt_2 on model Pony",
)
self.assertEqual(
gt_operation.formatted_description(),
"+ Create constraint test_add_constraint_pony_pink_gt_2 on model Pony",
)
self.assertEqual(
gt_operation.migration_name_fragment,
"pony_test_add_constraint_pony_pink_gt_2",
@ -4024,6 +4072,10 @@ class OperationTests(OperationTestBase):
gt_operation.describe(),
"Remove constraint test_remove_constraint_pony_pink_gt_2 from model Pony",
)
self.assertEqual(
gt_operation.formatted_description(),
"- Remove constraint test_remove_constraint_pony_pink_gt_2 from model Pony",
)
self.assertEqual(
gt_operation.migration_name_fragment,
"remove_pony_test_remove_constraint_pony_pink_gt_2",
@ -4564,6 +4616,9 @@ class OperationTests(OperationTestBase):
"Pony", {"permissions": [("can_groom", "Can groom")]}
)
self.assertEqual(operation.describe(), "Change Meta options on Pony")
self.assertEqual(
operation.formatted_description(), "~ Change Meta options on Pony"
)
self.assertEqual(operation.migration_name_fragment, "alter_pony_options")
new_state = project_state.clone()
operation.state_forwards("test_almoop", new_state)
@ -4630,6 +4685,10 @@ class OperationTests(OperationTestBase):
self.assertEqual(
operation.describe(), "Set order_with_respect_to on Rider to pony"
)
self.assertEqual(
operation.formatted_description(),
"~ Set order_with_respect_to on Rider to pony",
)
self.assertEqual(
operation.migration_name_fragment,
"alter_rider_order_with_respect_to",
@ -4705,6 +4764,7 @@ class OperationTests(OperationTestBase):
],
)
self.assertEqual(operation.describe(), "Change managers on Pony")
self.assertEqual(operation.formatted_description(), "~ Change managers on Pony")
self.assertEqual(operation.migration_name_fragment, "alter_pony_managers")
managers = project_state.models["test_almoma", "pony"].managers
self.assertEqual(managers, [])
@ -4840,6 +4900,7 @@ class OperationTests(OperationTestBase):
],
)
self.assertEqual(operation.describe(), "Raw SQL operation")
self.assertEqual(operation.formatted_description(), "s Raw SQL operation")
# Test the state alteration
new_state = project_state.clone()
operation.state_forwards("test_runsql", new_state)
@ -5034,6 +5095,7 @@ class OperationTests(OperationTestBase):
inner_method, reverse_code=inner_method_reverse
)
self.assertEqual(operation.describe(), "Raw Python operation")
self.assertEqual(operation.formatted_description(), "p Raw Python operation")
# Test the state alteration does nothing
new_state = project_state.clone()
operation.state_forwards("test_runpython", new_state)
@ -5565,6 +5627,10 @@ class OperationTests(OperationTestBase):
self.assertEqual(
operation.describe(), "Custom state/database change combination"
)
self.assertEqual(
operation.formatted_description(),
"? Custom state/database change combination",
)
# Test the state alteration
new_state = project_state.clone()
operation.state_forwards("test_separatedatabaseandstate", new_state)
@ -6073,3 +6139,9 @@ class FieldOperationTests(SimpleTestCase):
self.assertIs(
operation.references_field("Through", "second", "migrations"), True
)
class BaseOperationTests(SimpleTestCase):
def test_formatted_description_no_category(self):
operation = Operation()
self.assertEqual(operation.formatted_description(), "? Operation: ((), {})")

View File

@ -59,6 +59,10 @@ class AddIndexConcurrentlyTests(OperationTestBase):
operation.describe(),
"Concurrently create index pony_pink_idx on field(s) pink of model Pony",
)
self.assertEqual(
operation.formatted_description(),
"+ Concurrently create index pony_pink_idx on field(s) pink of model Pony",
)
operation.state_forwards(self.app_label, new_state)
self.assertEqual(
len(new_state.models[self.app_label, "pony"].options["indexes"]), 1
@ -154,6 +158,10 @@ class RemoveIndexConcurrentlyTests(OperationTestBase):
operation.describe(),
"Concurrently remove index pony_pink_idx from Pony",
)
self.assertEqual(
operation.formatted_description(),
"- Concurrently remove index pony_pink_idx from Pony",
)
operation.state_forwards(self.app_label, new_state)
self.assertEqual(
len(new_state.models[self.app_label, "pony"].options["indexes"]), 0
@ -190,6 +198,9 @@ class CreateExtensionTests(PostgreSQLTestCase):
@override_settings(DATABASE_ROUTERS=[NoMigrationRouter()])
def test_no_allow_migrate(self):
operation = CreateExtension("tablefunc")
self.assertEqual(
operation.formatted_description(), "+ Creates extension tablefunc"
)
project_state = ProjectState()
new_state = project_state.clone()
# Don't create an extension.
@ -287,6 +298,7 @@ class CreateCollationTests(PostgreSQLTestCase):
operation = CreateCollation("C_test", locale="C")
self.assertEqual(operation.migration_name_fragment, "create_collation_c_test")
self.assertEqual(operation.describe(), "Create collation C_test")
self.assertEqual(operation.formatted_description(), "+ Create collation C_test")
project_state = ProjectState()
new_state = project_state.clone()
# Create a collation.
@ -418,6 +430,7 @@ class RemoveCollationTests(PostgreSQLTestCase):
operation = RemoveCollation("C_test", locale="C")
self.assertEqual(operation.migration_name_fragment, "remove_collation_c_test")
self.assertEqual(operation.describe(), "Remove collation C_test")
self.assertEqual(operation.formatted_description(), "- Remove collation C_test")
project_state = ProjectState()
new_state = project_state.clone()
# Remove a collation.
@ -470,6 +483,10 @@ class AddConstraintNotValidTests(OperationTestBase):
operation.describe(),
f"Create not valid constraint {constraint_name} on model Pony",
)
self.assertEqual(
operation.formatted_description(),
f"+ Create not valid constraint {constraint_name} on model Pony",
)
self.assertEqual(
operation.migration_name_fragment,
f"pony_{constraint_name}_not_valid",
@ -530,6 +547,10 @@ class ValidateConstraintTests(OperationTestBase):
operation.describe(),
f"Validate constraint {constraint_name} on model Pony",
)
self.assertEqual(
operation.formatted_description(),
f"~ Validate constraint {constraint_name} on model Pony",
)
self.assertEqual(
operation.migration_name_fragment,
f"pony_validate_{constraint_name}",