1
from __future__ import unicode_literals
3
from django.db import migrations
4
from django.utils import six
7
class MigrationOptimizer(object):
9
Powers the optimization process, where you provide a list of Operations
10
and you are returned a list of equal or shorter length - operations
11
are merged into one if possible.
13
For example, a CreateModel and an AddField can be optimized into a
14
new CreateModel, and CreateModel and DeleteModel can be optimized into
18
def optimize(self, operations, app_label=None):
20
Main optimization entry point. Pass in a list of Operation instances,
21
get out a new list of Operation instances.
23
Unfortunately, due to the scope of the optimization (two combinable
24
operations might be separated by several hundred others), this can't be
25
done as a peephole optimization with checks/output implemented on
26
the Operations themselves; instead, the optimizer looks at each
27
individual operation and scans forwards in the list to see if there
28
are any matches, stopping at boundaries - operations which can't
29
be optimized over (RunSQL, operations on the same field/model, etc.)
31
The inner loop is run until the starting list is the same as the result
32
list, and then the result is returned. This means that operation
33
optimization must be stable and always return an equal or shorter list.
35
The app_label argument is optional, but if you pass it you'll get more
36
efficient optimization.
38
# Internal tracking variable for test assertions about # of loops
41
result = self.optimize_inner(operations, app_label)
43
if result == operations:
47
def optimize_inner(self, operations, app_label=None):
49
Inner optimization loop.
52
for i, operation in enumerate(operations):
53
# Compare it to each operation after it
54
for j, other in enumerate(operations[i + 1:]):
55
result = self.reduce(operation, other, operations[i + 1:i + j + 1])
56
if result is not None:
57
# Optimize! Add result, then remaining others, then return
58
new_operations.extend(result)
59
new_operations.extend(operations[i + 1:i + 1 + j])
60
new_operations.extend(operations[i + j + 2:])
62
if not self.can_optimize_through(operation, other, app_label):
63
new_operations.append(operation)
66
new_operations.append(operation)
71
def reduce(self, operation, other, in_between=None):
73
Either returns a list of zero, one or two operations,
74
or None, meaning this pair cannot be optimized.
78
migrations.CreateModel,
79
migrations.DeleteModel,
80
self.reduce_model_create_delete,
83
migrations.AlterModelTable,
84
migrations.DeleteModel,
85
self.reduce_model_alter_delete,
88
migrations.AlterUniqueTogether,
89
migrations.DeleteModel,
90
self.reduce_model_alter_delete,
93
migrations.AlterIndexTogether,
94
migrations.DeleteModel,
95
self.reduce_model_alter_delete,
98
migrations.CreateModel,
99
migrations.RenameModel,
100
self.reduce_model_create_rename,
103
migrations.RenameModel,
104
migrations.RenameModel,
105
self.reduce_model_rename_self,
108
migrations.CreateModel,
110
self.reduce_create_model_add_field,
113
migrations.CreateModel,
114
migrations.AlterField,
115
self.reduce_create_model_alter_field,
118
migrations.CreateModel,
119
migrations.RemoveField,
120
self.reduce_create_model_remove_field,
124
migrations.AlterField,
125
self.reduce_add_field_alter_field,
129
migrations.RemoveField,
130
self.reduce_add_field_delete_field,
133
migrations.AlterField,
134
migrations.RemoveField,
135
self.reduce_alter_field_delete_field,
139
migrations.RenameField,
140
self.reduce_add_field_rename_field,
143
migrations.AlterField,
144
migrations.RenameField,
145
self.reduce_alter_field_rename_field,
148
migrations.CreateModel,
149
migrations.RenameField,
150
self.reduce_create_model_rename_field,
153
migrations.RenameField,
154
migrations.RenameField,
155
self.reduce_rename_field_self,
158
for ia, ib, om in submethods:
159
if isinstance(operation, ia) and isinstance(other, ib):
160
return om(operation, other, in_between or [])
163
def model_to_key(self, model):
165
Takes either a model class or a "appname.ModelName" string
166
and returns (appname, modelname)
168
if isinstance(model, six.string_types):
169
return model.split(".", 1)
172
model._meta.app_label,
173
model._meta.object_name,
176
def reduce_model_create_delete(self, operation, other, in_between):
178
Folds a CreateModel and a DeleteModel into nothing.
180
if (operation.name.lower() == other.name.lower() and
181
not operation.options.get("proxy", False)):
184
def reduce_model_alter_delete(self, operation, other, in_between):
186
Folds an AlterModelSomething and a DeleteModel into just delete.
188
if operation.name.lower() == other.name.lower():
191
def reduce_model_create_rename(self, operation, other, in_between):
193
Folds a model rename into its create
195
if operation.name.lower() == other.old_name.lower():
197
migrations.CreateModel(
199
fields=operation.fields,
200
options=operation.options,
201
bases=operation.bases,
205
def reduce_model_rename_self(self, operation, other, in_between):
207
Folds a model rename into another one
209
if operation.new_name.lower() == other.old_name.lower():
211
migrations.RenameModel(
217
def reduce_create_model_add_field(self, operation, other, in_between):
218
if operation.name.lower() == other.model_name.lower():
219
# Don't allow optimisations of FKs through models they reference
220
if hasattr(other.field, "rel") and other.field.rel:
221
for between in in_between:
222
# Check that it doesn't point to the model
223
app_label, object_name = self.model_to_key(other.field.rel.to)
224
if between.references_model(object_name, app_label):
226
# Check that it's not through the model
227
if getattr(other.field.rel, "through", None):
228
app_label, object_name = self.model_to_key(other.field.rel.through)
229
if between.references_model(object_name, app_label):
233
migrations.CreateModel(
235
fields=operation.fields + [(other.name, other.field)],
236
options=operation.options,
237
bases=operation.bases,
241
def reduce_create_model_alter_field(self, operation, other, in_between):
242
if operation.name.lower() == other.model_name.lower():
244
migrations.CreateModel(
247
(n, other.field if n == other.name else v)
248
for n, v in operation.fields
250
options=operation.options,
251
bases=operation.bases,
255
def reduce_create_model_rename_field(self, operation, other, in_between):
256
if operation.name.lower() == other.model_name.lower():
258
migrations.CreateModel(
261
(other.new_name if n == other.old_name else n, v)
262
for n, v in operation.fields
264
options=operation.options,
265
bases=operation.bases,
269
def reduce_create_model_remove_field(self, operation, other, in_between):
270
if operation.name.lower() == other.model_name.lower():
272
migrations.CreateModel(
276
for n, v in operation.fields
277
if n.lower() != other.name.lower()
279
options=operation.options,
280
bases=operation.bases,
284
def reduce_add_field_alter_field(self, operation, other, in_between):
285
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.name.lower():
288
model_name=operation.model_name,
294
def reduce_add_field_delete_field(self, operation, other, in_between):
295
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.name.lower():
298
def reduce_alter_field_delete_field(self, operation, other, in_between):
299
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.name.lower():
302
def reduce_add_field_rename_field(self, operation, other, in_between):
303
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.old_name.lower():
306
model_name=operation.model_name,
308
field=operation.field,
312
def reduce_alter_field_rename_field(self, operation, other, in_between):
313
if operation.model_name.lower() == other.model_name.lower() and operation.name.lower() == other.old_name.lower():
316
migrations.AlterField(
317
model_name=operation.model_name,
319
field=operation.field,
323
def reduce_rename_field_self(self, operation, other, in_between):
324
if operation.model_name.lower() == other.model_name.lower() and operation.new_name.lower() == other.old_name.lower():
326
migrations.RenameField(
327
operation.model_name,
333
#### THROUGH CHECKS ####
335
def can_optimize_through(self, operation, other, app_label=None):
337
Returns True if it's possible to optimize 'operation' with something
338
the other side of 'other'. This is possible if, for example, they
339
affect different models.
341
MODEL_LEVEL_OPERATIONS = (
342
migrations.CreateModel,
343
migrations.AlterModelTable,
344
migrations.AlterUniqueTogether,
345
migrations.AlterIndexTogether,
347
FIELD_LEVEL_OPERATIONS = (
349
migrations.AlterField,
351
# If it's a model level operation, let it through if there's
352
# nothing that looks like a reference to us in 'other'.
353
if isinstance(operation, MODEL_LEVEL_OPERATIONS):
354
if not other.references_model(operation.name, app_label):
356
# If it's field level, only let it through things that don't reference
357
# the field (which includes not referencing the model)
358
if isinstance(operation, FIELD_LEVEL_OPERATIONS):
359
if not other.references_field(operation.model_name, operation.name, app_label):