mirror of https://github.com/django/django.git
Refs #22608 -- Made app_label required when optimizing migrations.
This paved the way for the removal of lot of logic when app_label was not specified.
This commit is contained in:
parent
911545da1d
commit
25bf15c0da
|
@ -369,7 +369,7 @@ class MigrationAutodetector:
|
||||||
# Optimize migrations
|
# Optimize migrations
|
||||||
for app_label, migrations in self.migrations.items():
|
for app_label, migrations in self.migrations.items():
|
||||||
for migration in migrations:
|
for migration in migrations:
|
||||||
migration.operations = MigrationOptimizer().optimize(migration.operations, app_label=app_label)
|
migration.operations = MigrationOptimizer().optimize(migration.operations, app_label)
|
||||||
|
|
||||||
def check_dependency(self, operation, dependency):
|
def check_dependency(self, operation, dependency):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -113,7 +113,7 @@ class Operation:
|
||||||
|
|
||||||
return router.allow_migrate_model(connection_alias, model)
|
return router.allow_migrate_model(connection_alias, model)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
"""
|
"""
|
||||||
Return either a list of operations the actual operation should be
|
Return either a list of operations the actual operation should be
|
||||||
replaced with or a boolean that indicates whether or not the specified
|
replaced with or a boolean that indicates whether or not the specified
|
||||||
|
|
|
@ -60,9 +60,9 @@ class FieldOperation(Operation):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
return (
|
return (
|
||||||
super().reduce(operation, app_label=app_label) or
|
super().reduce(operation, app_label) or
|
||||||
not operation.references_field(self.model_name, self.name, app_label)
|
not operation.references_field(self.model_name, self.name, app_label)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ class AddField(FieldOperation):
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Add field %s to %s" % (self.name, self.model_name)
|
return "Add field %s to %s" % (self.name, self.model_name)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if isinstance(operation, FieldOperation) and self.is_same_field_operation(operation):
|
if isinstance(operation, FieldOperation) and self.is_same_field_operation(operation):
|
||||||
if isinstance(operation, AlterField):
|
if isinstance(operation, AlterField):
|
||||||
return [
|
return [
|
||||||
|
@ -142,7 +142,7 @@ class AddField(FieldOperation):
|
||||||
field=self.field,
|
field=self.field,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
return super().reduce(operation, app_label=app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class RemoveField(FieldOperation):
|
class RemoveField(FieldOperation):
|
||||||
|
@ -186,11 +186,11 @@ class RemoveField(FieldOperation):
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Remove field %s from %s" % (self.name, self.model_name)
|
return "Remove field %s from %s" % (self.name, self.model_name)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
from .models import DeleteModel
|
from .models import DeleteModel
|
||||||
if isinstance(operation, DeleteModel) and operation.name_lower == self.model_name_lower:
|
if isinstance(operation, DeleteModel) and operation.name_lower == self.model_name_lower:
|
||||||
return [operation]
|
return [operation]
|
||||||
return super().reduce(operation, app_label=app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class AlterField(FieldOperation):
|
class AlterField(FieldOperation):
|
||||||
|
@ -256,7 +256,7 @@ class AlterField(FieldOperation):
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Alter field %s on %s" % (self.name, self.model_name)
|
return "Alter field %s on %s" % (self.name, self.model_name)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if isinstance(operation, RemoveField) and self.is_same_field_operation(operation):
|
if isinstance(operation, RemoveField) and self.is_same_field_operation(operation):
|
||||||
return [operation]
|
return [operation]
|
||||||
elif isinstance(operation, RenameField) and self.is_same_field_operation(operation):
|
elif isinstance(operation, RenameField) and self.is_same_field_operation(operation):
|
||||||
|
@ -268,7 +268,7 @@ class AlterField(FieldOperation):
|
||||||
field=self.field,
|
field=self.field,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
return super().reduce(operation, app_label=app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class RenameField(FieldOperation):
|
class RenameField(FieldOperation):
|
||||||
|
@ -382,7 +382,7 @@ class RenameField(FieldOperation):
|
||||||
name.lower() == self.new_name_lower
|
name.lower() == self.new_name_lower
|
||||||
)
|
)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if (isinstance(operation, RenameField) and
|
if (isinstance(operation, RenameField) and
|
||||||
self.is_same_model_operation(operation) and
|
self.is_same_model_operation(operation) and
|
||||||
self.new_name_lower == operation.old_name_lower):
|
self.new_name_lower == operation.old_name_lower):
|
||||||
|
@ -396,6 +396,6 @@ class RenameField(FieldOperation):
|
||||||
# Skip `FieldOperation.reduce` as we want to run `references_field`
|
# Skip `FieldOperation.reduce` as we want to run `references_field`
|
||||||
# against self.new_name.
|
# against self.new_name.
|
||||||
return (
|
return (
|
||||||
super(FieldOperation, self).reduce(operation, app_label=app_label) or
|
super(FieldOperation, self).reduce(operation, app_label) or
|
||||||
not operation.references_field(self.model_name, self.new_name, app_label)
|
not operation.references_field(self.model_name, self.new_name, app_label)
|
||||||
)
|
)
|
||||||
|
|
|
@ -31,9 +31,9 @@ class ModelOperation(Operation):
|
||||||
def references_model(self, name, app_label=None):
|
def references_model(self, name, app_label=None):
|
||||||
return name.lower() == self.name_lower
|
return name.lower() == self.name_lower
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
return (
|
return (
|
||||||
super().reduce(operation, app_label=app_label) or
|
super().reduce(operation, app_label) or
|
||||||
not operation.references_model(self.name, app_label)
|
not operation.references_model(self.name, app_label)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class CreateModel(ModelOperation):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if (isinstance(operation, DeleteModel) and
|
if (isinstance(operation, DeleteModel) and
|
||||||
self.name_lower == operation.name_lower and
|
self.name_lower == operation.name_lower and
|
||||||
not self.options.get("proxy", False)):
|
not self.options.get("proxy", False)):
|
||||||
|
@ -236,7 +236,7 @@ class CreateModel(ModelOperation):
|
||||||
managers=self.managers,
|
managers=self.managers,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
return super().reduce(operation, app_label=app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class DeleteModel(ModelOperation):
|
class DeleteModel(ModelOperation):
|
||||||
|
@ -411,7 +411,7 @@ class RenameModel(ModelOperation):
|
||||||
def describe(self):
|
def describe(self):
|
||||||
return "Rename model %s to %s" % (self.old_name, self.new_name)
|
return "Rename model %s to %s" % (self.old_name, self.new_name)
|
||||||
|
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if (isinstance(operation, RenameModel) and
|
if (isinstance(operation, RenameModel) and
|
||||||
self.new_name_lower == operation.old_name_lower):
|
self.new_name_lower == operation.old_name_lower):
|
||||||
return [
|
return [
|
||||||
|
@ -423,16 +423,16 @@ class RenameModel(ModelOperation):
|
||||||
# Skip `ModelOperation.reduce` as we want to run `references_model`
|
# Skip `ModelOperation.reduce` as we want to run `references_model`
|
||||||
# against self.new_name.
|
# against self.new_name.
|
||||||
return (
|
return (
|
||||||
super(ModelOperation, self).reduce(operation, app_label=app_label) or
|
super(ModelOperation, self).reduce(operation, app_label) or
|
||||||
not operation.references_model(self.new_name, app_label)
|
not operation.references_model(self.new_name, app_label)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModelOptionOperation(ModelOperation):
|
class ModelOptionOperation(ModelOperation):
|
||||||
def reduce(self, operation, app_label=None):
|
def reduce(self, operation, app_label):
|
||||||
if isinstance(operation, (self.__class__, DeleteModel)) and self.name_lower == operation.name_lower:
|
if isinstance(operation, (self.__class__, DeleteModel)) and self.name_lower == operation.name_lower:
|
||||||
return [operation]
|
return [operation]
|
||||||
return super().reduce(operation, app_label=app_label)
|
return super().reduce(operation, app_label)
|
||||||
|
|
||||||
|
|
||||||
class AlterModelTable(ModelOptionOperation):
|
class AlterModelTable(ModelOptionOperation):
|
||||||
|
|
|
@ -9,7 +9,7 @@ class MigrationOptimizer:
|
||||||
nothing.
|
nothing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def optimize(self, operations, app_label=None):
|
def optimize(self, operations, app_label):
|
||||||
"""
|
"""
|
||||||
Main optimization entry point. Pass in a list of Operation instances,
|
Main optimization entry point. Pass in a list of Operation instances,
|
||||||
get out a new list of Operation instances.
|
get out a new list of Operation instances.
|
||||||
|
@ -25,11 +25,10 @@ class MigrationOptimizer:
|
||||||
The inner loop is run until the starting list is the same as the result
|
The inner loop is run until the starting list is the same as the result
|
||||||
list, and then the result is returned. This means that operation
|
list, and then the result is returned. This means that operation
|
||||||
optimization must be stable and always return an equal or shorter list.
|
optimization must be stable and always return an equal or shorter list.
|
||||||
|
|
||||||
The app_label argument is optional, but if you pass it you'll get more
|
|
||||||
efficient optimization.
|
|
||||||
"""
|
"""
|
||||||
# Internal tracking variable for test assertions about # of loops
|
# Internal tracking variable for test assertions about # of loops
|
||||||
|
if app_label is None:
|
||||||
|
raise TypeError('app_label must be a str.')
|
||||||
self._iterations = 0
|
self._iterations = 0
|
||||||
while True:
|
while True:
|
||||||
result = self.optimize_inner(operations, app_label)
|
result = self.optimize_inner(operations, app_label)
|
||||||
|
@ -38,7 +37,7 @@ class MigrationOptimizer:
|
||||||
return result
|
return result
|
||||||
operations = result
|
operations = result
|
||||||
|
|
||||||
def optimize_inner(self, operations, app_label=None):
|
def optimize_inner(self, operations, app_label):
|
||||||
"""Inner optimization loop."""
|
"""Inner optimization loop."""
|
||||||
new_operations = []
|
new_operations = []
|
||||||
for i, operation in enumerate(operations):
|
for i, operation in enumerate(operations):
|
||||||
|
|
|
@ -23,7 +23,7 @@ class OptimizerTests(SimpleTestCase):
|
||||||
return serializer_factory(value).serialize()[0]
|
return serializer_factory(value).serialize()[0]
|
||||||
|
|
||||||
def assertOptimizesTo(self, operations, expected, exact=None, less_than=None, app_label=None):
|
def assertOptimizesTo(self, operations, expected, exact=None, less_than=None, app_label=None):
|
||||||
result, iterations = self.optimize(operations, app_label)
|
result, iterations = self.optimize(operations, app_label or 'migrations')
|
||||||
result = [self.serialize(f) for f in result]
|
result = [self.serialize(f) for f in result]
|
||||||
expected = [self.serialize(f) for f in expected]
|
expected = [self.serialize(f) for f in expected]
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
@ -39,6 +39,11 @@ class OptimizerTests(SimpleTestCase):
|
||||||
def assertDoesNotOptimize(self, operations, **kwargs):
|
def assertDoesNotOptimize(self, operations, **kwargs):
|
||||||
self.assertOptimizesTo(operations, operations, **kwargs)
|
self.assertOptimizesTo(operations, operations, **kwargs)
|
||||||
|
|
||||||
|
def test_none_app_label(self):
|
||||||
|
optimizer = MigrationOptimizer()
|
||||||
|
with self.assertRaisesMessage(TypeError, 'app_label must be a str'):
|
||||||
|
optimizer.optimize([], None)
|
||||||
|
|
||||||
def test_single(self):
|
def test_single(self):
|
||||||
"""
|
"""
|
||||||
The optimizer does nothing on a single operation,
|
The optimizer does nothing on a single operation,
|
||||||
|
@ -212,16 +217,8 @@ class OptimizerTests(SimpleTestCase):
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
# This should not work - FK should block it
|
# Operations should be optimized if the FK references a model from the
|
||||||
self.assertDoesNotOptimize(
|
# other app.
|
||||||
[
|
|
||||||
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
|
||||||
migrations.CreateModel("Bar", [("other", models.ForeignKey("testapp.Foo", models.CASCADE))]),
|
|
||||||
migrations.DeleteModel("Foo"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
# The same operations should be optimized if app_label is specified and
|
|
||||||
# a FK references a model from the other app.
|
|
||||||
self.assertOptimizesTo(
|
self.assertOptimizesTo(
|
||||||
[
|
[
|
||||||
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
||||||
|
@ -235,6 +232,13 @@ class OptimizerTests(SimpleTestCase):
|
||||||
)
|
)
|
||||||
# But it shouldn't work if a FK references a model with the same
|
# But it shouldn't work if a FK references a model with the same
|
||||||
# app_label.
|
# app_label.
|
||||||
|
self.assertDoesNotOptimize(
|
||||||
|
[
|
||||||
|
migrations.CreateModel('Foo', [('name', models.CharField(max_length=255))]),
|
||||||
|
migrations.CreateModel('Bar', [('other', models.ForeignKey('Foo', models.CASCADE))]),
|
||||||
|
migrations.DeleteModel('Foo'),
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertDoesNotOptimize(
|
self.assertDoesNotOptimize(
|
||||||
[
|
[
|
||||||
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
||||||
|
@ -244,12 +248,20 @@ class OptimizerTests(SimpleTestCase):
|
||||||
app_label="testapp",
|
app_label="testapp",
|
||||||
)
|
)
|
||||||
# This should not work - bases should block it
|
# This should not work - bases should block it
|
||||||
|
self.assertDoesNotOptimize(
|
||||||
|
[
|
||||||
|
migrations.CreateModel('Foo', [('name', models.CharField(max_length=255))]),
|
||||||
|
migrations.CreateModel('Bar', [('size', models.IntegerField())], bases=('Foo',)),
|
||||||
|
migrations.DeleteModel('Foo'),
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertDoesNotOptimize(
|
self.assertDoesNotOptimize(
|
||||||
[
|
[
|
||||||
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
migrations.CreateModel("Foo", [("name", models.CharField(max_length=255))]),
|
||||||
migrations.CreateModel("Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)),
|
migrations.CreateModel("Bar", [("size", models.IntegerField())], bases=("testapp.Foo",)),
|
||||||
migrations.DeleteModel("Foo"),
|
migrations.DeleteModel("Foo"),
|
||||||
],
|
],
|
||||||
|
app_label='testapp',
|
||||||
)
|
)
|
||||||
# The same operations should be optimized if app_label and none of
|
# The same operations should be optimized if app_label and none of
|
||||||
# bases belong to that app.
|
# bases belong to that app.
|
||||||
|
@ -293,6 +305,7 @@ class OptimizerTests(SimpleTestCase):
|
||||||
('reviewer', models.ForeignKey('test_app.Reviewer', models.CASCADE)),
|
('reviewer', models.ForeignKey('test_app.Reviewer', models.CASCADE)),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
app_label='test_app',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_create_model_add_field(self):
|
def test_create_model_add_field(self):
|
||||||
|
|
Loading…
Reference in New Issue