Skip to content

Commit

Permalink
Fixed #31700 -- Made makemigrations command display meaningful symbol…
Browse files Browse the repository at this point in the history
…s for each operation.
  • Loading branch information
AMK9978 authored and felixxm committed Jan 17, 2024
1 parent c7e986f commit 27a3eee
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 19 deletions.
13 changes: 12 additions & 1 deletion django/contrib/postgres/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
)
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
Expand Down Expand Up @@ -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" % (
Expand All @@ -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)
Expand Down Expand Up @@ -213,6 +216,8 @@ def remove_collation(self, schema_editor):
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
Expand All @@ -236,6 +241,8 @@ def migration_name_fragment(self):
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
Expand All @@ -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(
Expand Down Expand Up @@ -293,6 +302,8 @@ def migration_name_fragment(self):
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
Expand Down
4 changes: 2 additions & 2 deletions django/core/management/commands/makemigrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ def write_migration_files(self, changes, update_previous_migration_paths=None):
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:
Expand Down Expand Up @@ -456,7 +456,7 @@ def all_items_equal(seq):
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.
Expand Down
20 changes: 20 additions & 0 deletions django/db/migrations/operations/base.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -85,6 +98,13 @@ def describe(self):
"""
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):
"""
Expand Down
10 changes: 9 additions & 1 deletion django/db/migrations/operations/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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):
Expand Down Expand Up @@ -75,6 +75,8 @@ def reduce(self, operation, app_label):
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)
Expand Down Expand Up @@ -154,6 +156,8 @@ def reduce(self, operation, app_label):
class RemoveField(FieldOperation):
"""Remove a field from a model."""

category = OperationCategory.REMOVAL

def deconstruct(self):
kwargs = {
"model_name": self.model_name,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -270,6 +276,8 @@ def reduce(self, operation, app_label):
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
Expand Down
17 changes: 16 additions & 1 deletion django/db/migrations/operations/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -41,6 +41,7 @@ def can_reduce_through(self, operation, app_label):
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):
Expand Down Expand Up @@ -347,6 +348,8 @@ def reduce(self, operation, app_label):
class DeleteModel(ModelOperation):
"""Drop a model's table."""

category = OperationCategory.REMOVAL

def deconstruct(self):
kwargs = {
"name": self.name,
Expand Down Expand Up @@ -382,6 +385,8 @@ def migration_name_fragment(self):
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
Expand Down Expand Up @@ -499,6 +504,8 @@ def reduce(self, operation, app_label):


class ModelOptionOperation(ModelOperation):
category = OperationCategory.ALTERATION

def reduce(self, operation, app_label):
if (
isinstance(operation, (self.__class__, DeleteModel))
Expand Down Expand Up @@ -849,6 +856,8 @@ def model_name_lower(self):
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:
Expand Down Expand Up @@ -911,6 +920,8 @@ def reduce(self, operation, app_label):
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
Expand Down Expand Up @@ -954,6 +965,8 @@ def migration_name_fragment(self):
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(
Expand Down Expand Up @@ -1104,6 +1117,7 @@ def reduce(self, operation, app_label):


class AddConstraint(IndexOperation):
category = OperationCategory.ADDITION
option_name = "constraints"

def __init__(self, model_name, constraint):
Expand Down Expand Up @@ -1154,6 +1168,7 @@ def reduce(self, operation, app_label):


class RemoveConstraint(IndexOperation):
category = OperationCategory.REMOVAL
option_name = "constraints"

def __init__(self, model_name, name):
Expand Down
5 changes: 4 additions & 1 deletion django/db/migrations/operations/special.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.db import router

from .base import Operation
from .base import Operation, OperationCategory


class SeparateDatabaseAndState(Operation):
Expand All @@ -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):
Expand Down Expand Up @@ -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__(
Expand Down Expand Up @@ -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__(
Expand Down
4 changes: 2 additions & 2 deletions docs/intro/tutorial02.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/contrib/gis/tutorial.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
42 changes: 41 additions & 1 deletion docs/ref/migration-operations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down
9 changes: 7 additions & 2 deletions docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/migrations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 27a3eee

Please sign in to comment.