Fixed #29868 -- Retained database constraints on SQLite table rebuilds.
Refs #11964. Thanks Scott Stevens for testing this upcoming feature and the report.
This commit is contained in:
parent
f77fc56c96
commit
95bda03f2d
|
@ -126,8 +126,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
else:
|
else:
|
||||||
super().alter_field(model, old_field, new_field, strict=strict)
|
super().alter_field(model, old_field, new_field, strict=strict)
|
||||||
|
|
||||||
def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None,
|
def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None):
|
||||||
add_constraint=None, remove_constraint=None):
|
|
||||||
"""
|
"""
|
||||||
Shortcut to transform a model from old_model into new_model
|
Shortcut to transform a model from old_model into new_model
|
||||||
|
|
||||||
|
@ -224,13 +223,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
]
|
]
|
||||||
|
|
||||||
constraints = list(model._meta.constraints)
|
constraints = list(model._meta.constraints)
|
||||||
if add_constraint:
|
|
||||||
constraints.append(add_constraint)
|
|
||||||
if remove_constraint:
|
|
||||||
constraints = [
|
|
||||||
constraint for constraint in constraints
|
|
||||||
if remove_constraint.name != constraint.name
|
|
||||||
]
|
|
||||||
|
|
||||||
# Construct a new model for the new state
|
# Construct a new model for the new state
|
||||||
meta_contents = {
|
meta_contents = {
|
||||||
|
@ -383,7 +375,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
self.delete_model(old_field.remote_field.through)
|
self.delete_model(old_field.remote_field.through)
|
||||||
|
|
||||||
def add_constraint(self, model, constraint):
|
def add_constraint(self, model, constraint):
|
||||||
self._remake_table(model, add_constraint=constraint)
|
self._remake_table(model)
|
||||||
|
|
||||||
def remove_constraint(self, model, constraint):
|
def remove_constraint(self, model, constraint):
|
||||||
self._remake_table(model, remove_constraint=constraint)
|
self._remake_table(model)
|
||||||
|
|
|
@ -819,6 +819,7 @@ class AddConstraint(IndexOperation):
|
||||||
def state_forwards(self, app_label, state):
|
def state_forwards(self, app_label, state):
|
||||||
model_state = state.models[app_label, self.model_name_lower]
|
model_state = state.models[app_label, self.model_name_lower]
|
||||||
model_state.options[self.option_name] = [*model_state.options[self.option_name], self.constraint]
|
model_state.options[self.option_name] = [*model_state.options[self.option_name], self.constraint]
|
||||||
|
state.reload_model(app_label, self.model_name_lower, delay=True)
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
model = to_state.apps.get_model(app_label, self.model_name)
|
model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
|
@ -851,9 +852,10 @@ class RemoveConstraint(IndexOperation):
|
||||||
model_state = state.models[app_label, self.model_name_lower]
|
model_state = state.models[app_label, self.model_name_lower]
|
||||||
constraints = model_state.options[self.option_name]
|
constraints = model_state.options[self.option_name]
|
||||||
model_state.options[self.option_name] = [c for c in constraints if c.name != self.name]
|
model_state.options[self.option_name] = [c for c in constraints if c.name != self.name]
|
||||||
|
state.reload_model(app_label, self.model_name_lower, delay=True)
|
||||||
|
|
||||||
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
||||||
model = from_state.apps.get_model(app_label, self.model_name)
|
model = to_state.apps.get_model(app_label, self.model_name)
|
||||||
if self.allow_migrate_model(schema_editor.connection.alias, model):
|
if self.allow_migrate_model(schema_editor.connection.alias, model):
|
||||||
from_model_state = from_state.models[app_label, self.model_name_lower]
|
from_model_state = from_state.models[app_label, self.model_name_lower]
|
||||||
constraint = from_model_state.get_constraint_by_name(self.name)
|
constraint = from_model_state.get_constraint_by_name(self.name)
|
||||||
|
|
|
@ -54,7 +54,7 @@ class OperationTestBase(MigrationTestBase):
|
||||||
def set_up_test_model(
|
def set_up_test_model(
|
||||||
self, app_label, second_model=False, third_model=False, index=False, multicol_index=False,
|
self, app_label, second_model=False, third_model=False, index=False, multicol_index=False,
|
||||||
related_model=False, mti_model=False, proxy_model=False, manager_model=False,
|
related_model=False, mti_model=False, proxy_model=False, manager_model=False,
|
||||||
unique_together=False, options=False, db_table=None, index_together=False, check_constraint=False):
|
unique_together=False, options=False, db_table=None, index_together=False, constraints=None):
|
||||||
"""
|
"""
|
||||||
Creates a test model state and database table.
|
Creates a test model state and database table.
|
||||||
"""
|
"""
|
||||||
|
@ -107,10 +107,11 @@ class OperationTestBase(MigrationTestBase):
|
||||||
"Pony",
|
"Pony",
|
||||||
models.Index(fields=["pink", "weight"], name="pony_test_idx")
|
models.Index(fields=["pink", "weight"], name="pony_test_idx")
|
||||||
))
|
))
|
||||||
if check_constraint:
|
if constraints:
|
||||||
|
for constraint in constraints:
|
||||||
operations.append(migrations.AddConstraint(
|
operations.append(migrations.AddConstraint(
|
||||||
"Pony",
|
"Pony",
|
||||||
models.CheckConstraint(check=models.Q(pink__gt=2), name="pony_test_constraint")
|
constraint,
|
||||||
))
|
))
|
||||||
if second_model:
|
if second_model:
|
||||||
operations.append(migrations.CreateModel(
|
operations.append(migrations.CreateModel(
|
||||||
|
@ -1788,11 +1789,24 @@ class OperationTests(OperationTestBase):
|
||||||
gt_operation.state_forwards("test_addconstraint", new_state)
|
gt_operation.state_forwards("test_addconstraint", new_state)
|
||||||
self.assertEqual(len(new_state.models["test_addconstraint", "pony"].options["constraints"]), 1)
|
self.assertEqual(len(new_state.models["test_addconstraint", "pony"].options["constraints"]), 1)
|
||||||
Pony = new_state.apps.get_model("test_addconstraint", "Pony")
|
Pony = new_state.apps.get_model("test_addconstraint", "Pony")
|
||||||
|
self.assertEqual(len(Pony._meta.constraints), 1)
|
||||||
# Test the database alteration
|
# Test the database alteration
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
gt_operation.database_forwards("test_addconstraint", editor, project_state, new_state)
|
gt_operation.database_forwards("test_addconstraint", editor, project_state, new_state)
|
||||||
with self.assertRaises(IntegrityError), transaction.atomic():
|
with self.assertRaises(IntegrityError), transaction.atomic():
|
||||||
Pony.objects.create(pink=1, weight=1.0)
|
Pony.objects.create(pink=1, weight=1.0)
|
||||||
|
# Add another one.
|
||||||
|
lt_check = models.Q(pink__lt=100)
|
||||||
|
lt_constraint = models.CheckConstraint(check=lt_check, name="test_constraint_pony_pink_lt_100")
|
||||||
|
lt_operation = migrations.AddConstraint("Pony", lt_constraint)
|
||||||
|
lt_operation.state_forwards("test_addconstraint", new_state)
|
||||||
|
self.assertEqual(len(new_state.models["test_addconstraint", "pony"].options["constraints"]), 2)
|
||||||
|
Pony = new_state.apps.get_model("test_addconstraint", "Pony")
|
||||||
|
self.assertEqual(len(Pony._meta.constraints), 2)
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
lt_operation.database_forwards("test_addconstraint", editor, project_state, new_state)
|
||||||
|
with self.assertRaises(IntegrityError), transaction.atomic():
|
||||||
|
Pony.objects.create(pink=100, weight=1.0)
|
||||||
# Test reversal
|
# Test reversal
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
gt_operation.database_backwards("test_addconstraint", editor, new_state, project_state)
|
gt_operation.database_backwards("test_addconstraint", editor, new_state, project_state)
|
||||||
|
@ -1805,28 +1819,43 @@ class OperationTests(OperationTestBase):
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_table_check_constraints')
|
@skipUnlessDBFeature('supports_table_check_constraints')
|
||||||
def test_remove_constraint(self):
|
def test_remove_constraint(self):
|
||||||
project_state = self.set_up_test_model("test_removeconstraint", check_constraint=True)
|
project_state = self.set_up_test_model("test_removeconstraint", constraints=[
|
||||||
operation = migrations.RemoveConstraint("Pony", "pony_test_constraint")
|
models.CheckConstraint(check=models.Q(pink__gt=2), name="test_constraint_pony_pink_gt_2"),
|
||||||
self.assertEqual(operation.describe(), "Remove constraint pony_test_constraint from model Pony")
|
models.CheckConstraint(check=models.Q(pink__lt=100), name="test_constraint_pony_pink_lt_100"),
|
||||||
|
])
|
||||||
|
gt_operation = migrations.RemoveConstraint("Pony", "test_constraint_pony_pink_gt_2")
|
||||||
|
self.assertEqual(gt_operation.describe(), "Remove constraint test_constraint_pony_pink_gt_2 from model Pony")
|
||||||
# Test state alteration
|
# Test state alteration
|
||||||
new_state = project_state.clone()
|
new_state = project_state.clone()
|
||||||
operation.state_forwards("test_removeconstraint", new_state)
|
gt_operation.state_forwards("test_removeconstraint", new_state)
|
||||||
self.assertEqual(len(new_state.models["test_removeconstraint", "pony"].options['constraints']), 0)
|
self.assertEqual(len(new_state.models["test_removeconstraint", "pony"].options['constraints']), 1)
|
||||||
Pony = new_state.apps.get_model("test_removeconstraint", "Pony")
|
Pony = new_state.apps.get_model("test_removeconstraint", "Pony")
|
||||||
|
self.assertEqual(len(Pony._meta.constraints), 1)
|
||||||
# Test database alteration
|
# Test database alteration
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
operation.database_forwards("test_removeconstraint", editor, project_state, new_state)
|
gt_operation.database_forwards("test_removeconstraint", editor, project_state, new_state)
|
||||||
Pony.objects.create(pink=1, weight=1.0).delete()
|
Pony.objects.create(pink=1, weight=1.0).delete()
|
||||||
|
with self.assertRaises(IntegrityError), transaction.atomic():
|
||||||
|
Pony.objects.create(pink=100, weight=1.0)
|
||||||
|
# Remove the other one.
|
||||||
|
lt_operation = migrations.RemoveConstraint("Pony", "test_constraint_pony_pink_lt_100")
|
||||||
|
lt_operation.state_forwards("test_removeconstraint", new_state)
|
||||||
|
self.assertEqual(len(new_state.models["test_removeconstraint", "pony"].options['constraints']), 0)
|
||||||
|
Pony = new_state.apps.get_model("test_removeconstraint", "Pony")
|
||||||
|
self.assertEqual(len(Pony._meta.constraints), 0)
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
lt_operation.database_forwards("test_removeconstraint", editor, project_state, new_state)
|
||||||
|
Pony.objects.create(pink=100, weight=1.0).delete()
|
||||||
# Test reversal
|
# Test reversal
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
operation.database_backwards("test_removeconstraint", editor, new_state, project_state)
|
gt_operation.database_backwards("test_removeconstraint", editor, new_state, project_state)
|
||||||
with self.assertRaises(IntegrityError), transaction.atomic():
|
with self.assertRaises(IntegrityError), transaction.atomic():
|
||||||
Pony.objects.create(pink=1, weight=1.0)
|
Pony.objects.create(pink=1, weight=1.0)
|
||||||
# Test deconstruction
|
# Test deconstruction
|
||||||
definition = operation.deconstruct()
|
definition = gt_operation.deconstruct()
|
||||||
self.assertEqual(definition[0], "RemoveConstraint")
|
self.assertEqual(definition[0], "RemoveConstraint")
|
||||||
self.assertEqual(definition[1], [])
|
self.assertEqual(definition[1], [])
|
||||||
self.assertEqual(definition[2], {'model_name': "Pony", 'name': "pony_test_constraint"})
|
self.assertEqual(definition[2], {'model_name': "Pony", 'name': "test_constraint_pony_pink_gt_2"})
|
||||||
|
|
||||||
def test_alter_model_options(self):
|
def test_alter_model_options(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue