diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index a6edc2cd6ef..89e2dceeaf8 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -9,9 +9,7 @@ from django.db.migrations.migration import Migration 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 @@ -1265,13 +1263,14 @@ class MigrationAutodetector: 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 @@ -1306,22 +1305,6 @@ class MigrationAutodetector: 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): """ diff --git a/django/db/migrations/migration.py b/django/db/migrations/migration.py index fe5e228c6c5..08fdd1914fd 100644 --- a/django/db/migrations/migration.py +++ b/django/db/migrations/migration.py @@ -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 @@ -175,6 +177,24 @@ class Migration: 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): """ diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index ab52b8116d6..dddda94eb26 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -2481,25 +2481,39 @@ class AutodetectorTests(TestCase): 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)