diff --git a/django/db/migrations/migration.py b/django/db/migrations/migration.py index ea9d02a94aa..3c7713c5eaf 100644 --- a/django/db/migrations/migration.py +++ b/django/db/migrations/migration.py @@ -1,3 +1,5 @@ +import re + from django.db.migrations.utils import get_migration_name_timestamp from django.db.transaction import atomic @@ -205,7 +207,7 @@ class Migration: return "initial" raw_fragments = [op.migration_name_fragment for op in self.operations] - fragments = [name for name in raw_fragments if name] + fragments = [re.sub(r"\W+", "_", name) for name in raw_fragments if name] if not fragments or len(fragments) != len(self.operations): return "auto_%s" % get_migration_name_timestamp() diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index dd153d2f0b8..d2f9be75f7d 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -5314,6 +5314,20 @@ class MigrationSuggestNameTests(SimpleTestCase): migration = Migration("some_migration", "test_app") self.assertIs(migration.suggest_name().startswith("auto_"), True) + def test_operation_with_invalid_chars_in_suggested_name(self): + class Migration(migrations.Migration): + operations = [ + migrations.AddConstraint( + "Person", + models.UniqueConstraint( + fields=["name"], name="person.name-*~unique!" + ), + ), + ] + + migration = Migration("some_migration", "test_app") + self.assertEqual(migration.suggest_name(), "person_person_name_unique_") + def test_none_name(self): class Migration(migrations.Migration): operations = [migrations.RunSQL("SELECT 1 FROM person;")]