Skip to content

Commit 8e3f22f

Browse files
charettestimgraham
authored andcommitted
Fixed #27731 -- Implemented CreateModel/AlterFooOperation reduction.
This should alleviate the side effects of disabling the AlterFooOperation reduction with RemoveField to fix refs #28862 during migration squashing because CreateModel can perform a reduction with RemoveField. Thanks Nick Pope for the review.
1 parent ed7898e commit 8e3f22f

File tree

4 files changed

+95
-20
lines changed

4 files changed

+95
-20
lines changed

django/db/migrations/operations/models.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ def reduce(self, operation, app_label=None):
142142
managers=self.managers,
143143
),
144144
]
145+
elif isinstance(operation, FieldRelatedOptionOperation) and self.name_lower == operation.name_lower:
146+
option_value = getattr(operation, operation.option_name)
147+
return [
148+
CreateModel(
149+
self.name,
150+
fields=self.fields,
151+
options={**self.options, **{operation.option_name: option_value}},
152+
bases=self.bases,
153+
managers=self.managers,
154+
),
155+
]
145156
elif isinstance(operation, FieldOperation) and self.name_lower == operation.model_name_lower:
146157
if isinstance(operation, AddField):
147158
return [
@@ -167,6 +178,18 @@ def reduce(self, operation, app_label=None):
167178
),
168179
]
169180
elif isinstance(operation, RemoveField):
181+
options = self.options.copy()
182+
for option_name in ('unique_together', 'index_together'):
183+
option = options.pop(option_name, None)
184+
if option:
185+
option = set(filter(bool, (
186+
tuple(f for f in fields if f != operation.name_lower) for fields in option
187+
)))
188+
if option:
189+
options[option_name] = option
190+
order_with_respect_to = options.get('order_with_respect_to')
191+
if order_with_respect_to == operation.name_lower:
192+
del options['order_with_respect_to']
170193
return [
171194
CreateModel(
172195
self.name,
@@ -175,20 +198,31 @@ def reduce(self, operation, app_label=None):
175198
for n, v in self.fields
176199
if n.lower() != operation.name_lower
177200
],
178-
options=self.options,
201+
options=options,
179202
bases=self.bases,
180203
managers=self.managers,
181204
),
182205
]
183206
elif isinstance(operation, RenameField):
207+
options = self.options.copy()
208+
for option_name in ('unique_together', 'index_together'):
209+
option = options.get(option_name)
210+
if option:
211+
options[option_name] = {
212+
tuple(operation.new_name if f == operation.old_name else f for f in fields)
213+
for fields in option
214+
}
215+
order_with_respect_to = options.get('order_with_respect_to')
216+
if order_with_respect_to == operation.old_name:
217+
options['order_with_respect_to'] = operation.new_name
184218
return [
185219
CreateModel(
186220
self.name,
187221
fields=[
188222
(operation.new_name if n == operation.old_name else n, v)
189223
for n, v in self.fields
190224
],
191-
options=self.options,
225+
options=options,
192226
bases=self.bases,
193227
managers=self.managers,
194228
),
@@ -568,6 +602,8 @@ def describe(self):
568602
class AlterOrderWithRespectTo(FieldRelatedOptionOperation):
569603
"""Represent a change with the order_with_respect_to option."""
570604

605+
option_name = 'order_with_respect_to'
606+
571607
def __init__(self, name, order_with_respect_to):
572608
self.order_with_respect_to = order_with_respect_to
573609
super().__init__(name)

tests/migrations/test_autodetector.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,8 +2075,10 @@ def test_add_model_order_with_respect_to(self):
20752075
changes = self.get_changes([], [self.book, self.author_with_book_order_wrt])
20762076
# Right number/type of migrations?
20772077
self.assertNumberMigrations(changes, 'testapp', 1)
2078-
self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel", "AlterOrderWithRespectTo"])
2079-
self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book")
2078+
self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"])
2079+
self.assertOperationAttributes(
2080+
changes, 'testapp', 0, 0, name="Author", options={'order_with_respect_to': 'book'}
2081+
)
20802082
self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields])
20812083

20822084
def test_alter_model_managers(self):

tests/migrations/test_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ def test_squashmigrations_optimizes(self):
13351335
out = io.StringIO()
13361336
with self.temporary_migration_module(module="migrations.test_migrations"):
13371337
call_command("squashmigrations", "migrations", "0002", interactive=False, verbosity=1, stdout=out)
1338-
self.assertIn("Optimized from 8 operations to 4 operations.", out.getvalue())
1338+
self.assertIn("Optimized from 8 operations to 2 operations.", out.getvalue())
13391339

13401340
def test_ticket_23799_squashmigrations_no_optimize(self):
13411341
"""

tests/migrations/test_optimizer.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,11 @@ def test_alter_field_delete_field(self):
594594
def _test_create_alter_foo_field(self, alter):
595595
"""
596596
CreateModel, AlterFooTogether/AlterOrderWithRespectTo followed by an
597-
add/alter/rename field should optimize to CreateModel and the Alter*
597+
add/alter/rename field should optimize to CreateModel with options.
598598
"""
599+
option_value = getattr(alter, alter.option_name)
600+
options = {alter.option_name: option_value}
601+
599602
# AddField
600603
self.assertOptimizesTo(
601604
[
@@ -611,13 +614,12 @@ def _test_create_alter_foo_field(self, alter):
611614
("a", models.IntegerField()),
612615
("b", models.IntegerField()),
613616
("c", models.IntegerField()),
614-
]),
615-
alter,
617+
], options=options),
616618
],
617619
)
618620

619621
# AlterField
620-
self.assertDoesNotOptimize(
622+
self.assertOptimizesTo(
621623
[
622624
migrations.CreateModel("Foo", [
623625
("a", models.IntegerField()),
@@ -626,6 +628,12 @@ def _test_create_alter_foo_field(self, alter):
626628
alter,
627629
migrations.AlterField("Foo", "b", models.CharField(max_length=255)),
628630
],
631+
[
632+
migrations.CreateModel("Foo", [
633+
("a", models.IntegerField()),
634+
("b", models.CharField(max_length=255)),
635+
], options=options),
636+
],
629637
)
630638

631639
self.assertOptimizesTo(
@@ -643,13 +651,20 @@ def _test_create_alter_foo_field(self, alter):
643651
("a", models.IntegerField()),
644652
("b", models.IntegerField()),
645653
("c", models.CharField(max_length=255)),
646-
]),
647-
alter,
654+
], options=options),
648655
],
649656
)
650657

651658
# RenameField
652-
self.assertDoesNotOptimize(
659+
if isinstance(option_value, str):
660+
renamed_options = {alter.option_name: 'c'}
661+
else:
662+
renamed_options = {
663+
alter.option_name: {
664+
tuple('c' if value == 'b' else value for value in item) for item in option_value
665+
}
666+
}
667+
self.assertOptimizesTo(
653668
[
654669
migrations.CreateModel("Foo", [
655670
("a", models.IntegerField()),
@@ -658,6 +673,12 @@ def _test_create_alter_foo_field(self, alter):
658673
alter,
659674
migrations.RenameField("Foo", "b", "c"),
660675
],
676+
[
677+
migrations.CreateModel("Foo", [
678+
("a", models.IntegerField()),
679+
("c", models.IntegerField()),
680+
], options=renamed_options),
681+
],
661682
)
662683

663684
self.assertOptimizesTo(
@@ -673,10 +694,8 @@ def _test_create_alter_foo_field(self, alter):
673694
[
674695
migrations.CreateModel("Foo", [
675696
("a", models.IntegerField()),
676-
("b", models.IntegerField()),
677-
]),
678-
alter,
679-
migrations.RenameField("Foo", "b", "c"),
697+
("c", models.IntegerField()),
698+
], options=renamed_options),
680699
],
681700
)
682701

@@ -695,13 +714,20 @@ def _test_create_alter_foo_field(self, alter):
695714
("a", models.IntegerField()),
696715
("b", models.IntegerField()),
697716
("d", models.IntegerField()),
698-
]),
699-
alter,
717+
], options=options),
700718
],
701719
)
702720

703721
# RemoveField
704-
self.assertDoesNotOptimize(
722+
if isinstance(option_value, str):
723+
removed_options = None
724+
else:
725+
removed_options = {
726+
alter.option_name: {
727+
tuple(value for value in item if value != 'b') for item in option_value
728+
}
729+
}
730+
self.assertOptimizesTo(
705731
[
706732
migrations.CreateModel("Foo", [
707733
("a", models.IntegerField()),
@@ -710,9 +736,14 @@ def _test_create_alter_foo_field(self, alter):
710736
alter,
711737
migrations.RemoveField("Foo", "b"),
712738
],
739+
[
740+
migrations.CreateModel("Foo", [
741+
("a", models.IntegerField()),
742+
], options=removed_options),
743+
]
713744
)
714745

715-
self.assertDoesNotOptimize(
746+
self.assertOptimizesTo(
716747
[
717748
migrations.CreateModel("Foo", [
718749
("a", models.IntegerField()),
@@ -722,6 +753,12 @@ def _test_create_alter_foo_field(self, alter):
722753
alter,
723754
migrations.RemoveField("Foo", "c"),
724755
],
756+
[
757+
migrations.CreateModel("Foo", [
758+
("a", models.IntegerField()),
759+
("b", models.IntegerField()),
760+
], options=options),
761+
],
725762
)
726763

727764
def test_create_alter_unique_field(self):

0 commit comments

Comments
 (0)