Fixed #29808 -- Fixed initial migration detection when identifiers are case-insensitive.
Thanks Simon Charette for the review.
This commit is contained in:
parent
d0c86a1df4
commit
530dd193f2
|
@ -329,8 +329,11 @@ class MigrationExecutor:
|
||||||
apps = after_state.apps
|
apps = after_state.apps
|
||||||
found_create_model_migration = False
|
found_create_model_migration = False
|
||||||
found_add_field_migration = False
|
found_add_field_migration = False
|
||||||
|
fold_identifier_case = self.connection.features.ignores_table_name_case
|
||||||
with self.connection.cursor() as cursor:
|
with self.connection.cursor() as cursor:
|
||||||
existing_table_names = set(self.connection.introspection.table_names(cursor))
|
existing_table_names = set(self.connection.introspection.table_names(cursor))
|
||||||
|
if fold_identifier_case:
|
||||||
|
existing_table_names = {name.casefold() for name in existing_table_names}
|
||||||
# Make sure all create model and add field operations are done
|
# Make sure all create model and add field operations are done
|
||||||
for operation in migration.operations:
|
for operation in migration.operations:
|
||||||
if isinstance(operation, migrations.CreateModel):
|
if isinstance(operation, migrations.CreateModel):
|
||||||
|
@ -341,7 +344,10 @@ class MigrationExecutor:
|
||||||
model = global_apps.get_model(model._meta.swapped)
|
model = global_apps.get_model(model._meta.swapped)
|
||||||
if should_skip_detecting_model(migration, model):
|
if should_skip_detecting_model(migration, model):
|
||||||
continue
|
continue
|
||||||
if model._meta.db_table not in existing_table_names:
|
db_table = model._meta.db_table
|
||||||
|
if fold_identifier_case:
|
||||||
|
db_table = db_table.casefold()
|
||||||
|
if db_table not in existing_table_names:
|
||||||
return False, project_state
|
return False, project_state
|
||||||
found_create_model_migration = True
|
found_create_model_migration = True
|
||||||
elif isinstance(operation, migrations.AddField):
|
elif isinstance(operation, migrations.AddField):
|
||||||
|
@ -358,7 +364,10 @@ class MigrationExecutor:
|
||||||
|
|
||||||
# Handle implicit many-to-many tables created by AddField.
|
# Handle implicit many-to-many tables created by AddField.
|
||||||
if field.many_to_many:
|
if field.many_to_many:
|
||||||
if field.remote_field.through._meta.db_table not in existing_table_names:
|
through_db_table = field.remote_field.through._meta.db_table
|
||||||
|
if fold_identifier_case:
|
||||||
|
through_db_table = through_db_table.casefold()
|
||||||
|
if through_db_table not in existing_table_names:
|
||||||
return False, project_state
|
return False, project_state
|
||||||
else:
|
else:
|
||||||
found_add_field_migration = True
|
found_add_field_migration = True
|
||||||
|
@ -368,7 +377,12 @@ class MigrationExecutor:
|
||||||
table,
|
table,
|
||||||
)
|
)
|
||||||
for column in columns:
|
for column in columns:
|
||||||
if column.name == field.column:
|
field_column = field.column
|
||||||
|
column_name = column.name
|
||||||
|
if fold_identifier_case:
|
||||||
|
column_name = column_name.casefold()
|
||||||
|
field_column = field_column.casefold()
|
||||||
|
if column_name == field_column:
|
||||||
found_add_field_migration = True
|
found_add_field_migration = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -14,7 +14,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
from django.db.backends.utils import truncate_name
|
from django.db.backends.utils import truncate_name
|
||||||
from django.db.migrations.exceptions import InconsistentMigrationHistory
|
from django.db.migrations.exceptions import InconsistentMigrationHistory
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import UnicodeModel, UnserializableModel
|
from .models import UnicodeModel, UnserializableModel
|
||||||
from .routers import TestRouter
|
from .routers import TestRouter
|
||||||
|
@ -197,6 +197,32 @@ class MigrateTests(MigrationTestBase):
|
||||||
self.assertTableNotExists("migrations_tribble", using=db)
|
self.assertTableNotExists("migrations_tribble", using=db)
|
||||||
self.assertTableNotExists("migrations_book", using=db)
|
self.assertTableNotExists("migrations_book", using=db)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('ignores_table_name_case')
|
||||||
|
def test_migrate_fake_initial_case_insensitive(self):
|
||||||
|
with override_settings(MIGRATION_MODULES={
|
||||||
|
'migrations': 'migrations.test_fake_initial_case_insensitive.initial',
|
||||||
|
}):
|
||||||
|
call_command('migrate', 'migrations', '0001', verbosity=0)
|
||||||
|
call_command('migrate', 'migrations', 'zero', fake=True, verbosity=0)
|
||||||
|
|
||||||
|
with override_settings(MIGRATION_MODULES={
|
||||||
|
'migrations': 'migrations.test_fake_initial_case_insensitive.fake_initial',
|
||||||
|
}):
|
||||||
|
out = io.StringIO()
|
||||||
|
call_command(
|
||||||
|
'migrate',
|
||||||
|
'migrations',
|
||||||
|
'0001',
|
||||||
|
fake_initial=True,
|
||||||
|
stdout=out,
|
||||||
|
verbosity=1,
|
||||||
|
no_color=True,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
'migrations.0001_initial... faked',
|
||||||
|
out.getvalue().lower(),
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_fake_split_initial"})
|
@override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations_fake_split_initial"})
|
||||||
def test_migrate_fake_split_initial(self):
|
def test_migrate_fake_split_initial(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
'fakeinitialmodel',
|
||||||
|
[
|
||||||
|
('id', models.AutoField(primary_key=True)),
|
||||||
|
('field', models.CharField(max_length=20)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'migrations_mIxEd_cAsE_iNiTiAl_mOdEl',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='fakeinitialmodel',
|
||||||
|
name='field_mixed_case',
|
||||||
|
field=models.CharField(max_length=20, db_column='fIeLd_mIxEd_cAsE'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='fakeinitialmodel',
|
||||||
|
name='fake_initial_model',
|
||||||
|
field=models.ManyToManyField(to='migrations.fakeinitialmodel', db_table='m2m_mIxEd_cAsE'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='fakeinitialmodel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True)),
|
||||||
|
('field', models.CharField(max_length=20)),
|
||||||
|
('field_mixed_case', models.CharField(max_length=20, db_column='FiEld_MiXeD_CaSe')),
|
||||||
|
(
|
||||||
|
'fake_initial_mode',
|
||||||
|
models.ManyToManyField('migrations.FakeInitialModel', db_table='m2m_MiXeD_CaSe'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'migrations_MiXeD_CaSe_InItIaL_MoDel',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
Loading…
Reference in New Issue