Skip to content

Commit

Permalink
Refs #31703 -- Moved MigrationAutodetector.suggest_name() to Migration.
Browse files Browse the repository at this point in the history
Allows expanding the method to inspect additional attributes of the
migration instance. For example, the Migration.initial attribute.
  • Loading branch information
jdufresne authored and felixxm committed Jun 24, 2020
1 parent 7988351 commit bce180d
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 38 deletions.
33 changes: 8 additions & 25 deletions django/db/migrations/autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
from django.db.migrations.operations.models import AlterModelOptions
from django.db.migrations.optimizer import MigrationOptimizer
from django.db.migrations.questioner import MigrationQuestioner
from django.db.migrations.utils import (
COMPILED_REGEX_TYPE, RegexObject, get_migration_name_timestamp,
)
from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject
from django.utils.topological_sort import stable_topological_sort


Expand Down Expand Up @@ -1265,13 +1263,14 @@ def arrange_for_graph(self, changes, graph, migration_name=None):
for i, migration in enumerate(migrations):
if i == 0 and app_leaf:
migration.dependencies.append(app_leaf)
if i == 0 and not app_leaf:
new_name = "0001_%s" % migration_name if migration_name else "0001_initial"
new_name_parts = ['%04i' % next_number]
if migration_name:
new_name_parts.append(migration_name)
elif i == 0 and not app_leaf:
new_name_parts.append('initial')
else:
new_name = "%04i_%s" % (
next_number,
migration_name or self.suggest_name(migration.operations)[:100],
)
new_name_parts.append(migration.suggest_name()[:100])
new_name = '_'.join(new_name_parts)
name_map[(app_label, migration.name)] = (app_label, new_name)
next_number += 1
migration.name = new_name
Expand Down Expand Up @@ -1306,22 +1305,6 @@ def _trim_to_apps(self, changes, app_labels):
del changes[app_label]
return changes

@classmethod
def suggest_name(cls, ops):
"""
Given a set of operations, suggest a name for the migration they might
represent. Names are not guaranteed to be unique, but put some effort
into the fallback name to avoid VCS conflicts if possible.
"""
name = None
if len(ops) == 1:
name = ops[0].migration_name_fragment
elif len(ops) > 1 and all(isinstance(o, operations.CreateModel) for o in ops):
name = '_'.join(sorted(o.migration_name_fragment for o in ops))
if name is None:
name = 'auto_%s' % get_migration_name_timestamp()
return name

@classmethod
def parse_number(cls, name):
"""
Expand Down
20 changes: 20 additions & 0 deletions django/db/migrations/migration.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.db.migrations import operations
from django.db.migrations.utils import get_migration_name_timestamp
from django.db.transaction import atomic

from .exceptions import IrreversibleError
Expand Down Expand Up @@ -175,6 +177,24 @@ def unapply(self, project_state, schema_editor, collect_sql=False):
operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
return project_state

def suggest_name(self):
"""
Suggest a name for the operations this migration might represent. Names
are not guaranteed to be unique, but put some effort into the fallback
name to avoid VCS conflicts if possible.
"""
name = None
if len(self.operations) == 1:
name = self.operations[0].migration_name_fragment
elif (
len(self.operations) > 1 and
all(isinstance(o, operations.CreateModel) for o in self.operations)
):
name = '_'.join(sorted(o.migration_name_fragment for o in self.operations))
if name is None:
name = 'auto_%s' % get_migration_name_timestamp()
return name


class SwappableTuple(tuple):
"""
Expand Down
40 changes: 27 additions & 13 deletions tests/migrations/test_autodetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2481,25 +2481,39 @@ def test_add_model_with_field_removed_from_base_model(self):
self.assertOperationAttributes(changes, 'app', 0, 1, name='book')


class AutodetectorSuggestNameTests(SimpleTestCase):
class MigrationSuggestNameTests(SimpleTestCase):
def test_single_operation(self):
ops = [migrations.CreateModel('Person', fields=[])]
self.assertEqual(MigrationAutodetector.suggest_name(ops), 'person')
ops = [migrations.DeleteModel('Person')]
self.assertEqual(MigrationAutodetector.suggest_name(ops), 'delete_person')
class Migration(migrations.Migration):
operations = [migrations.CreateModel('Person', fields=[])]

migration = Migration('0001_initial', 'test_app')
self.assertEqual(migration.suggest_name(), 'person')

class Migration(migrations.Migration):
operations = [migrations.DeleteModel('Person')]

migration = Migration('0002_initial', 'test_app')
self.assertEqual(migration.suggest_name(), 'delete_person')

def test_two_create_models(self):
ops = [
migrations.CreateModel('Person', fields=[]),
migrations.CreateModel('Animal', fields=[]),
]
self.assertEqual(MigrationAutodetector.suggest_name(ops), 'animal_person')
class Migration(migrations.Migration):
operations = [
migrations.CreateModel('Person', fields=[]),
migrations.CreateModel('Animal', fields=[]),
]

migration = Migration('0001_initial', 'test_app')
self.assertEqual(migration.suggest_name(), 'animal_person')

def test_none_name(self):
ops = [migrations.RunSQL('SELECT 1 FROM person;')]
suggest_name = MigrationAutodetector.suggest_name(ops)
class Migration(migrations.Migration):
operations = [migrations.RunSQL('SELECT 1 FROM person;')]

migration = Migration('0001_initial', 'test_app')
suggest_name = migration.suggest_name()
self.assertIs(suggest_name.startswith('auto_'), True)

def test_auto(self):
suggest_name = MigrationAutodetector.suggest_name([])
migration = migrations.Migration('0001_initial', 'test_app')
suggest_name = migration.suggest_name()
self.assertIs(suggest_name.startswith('auto_'), True)

0 comments on commit bce180d

Please sign in to comment.