Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/migrations/operations/models.py: 34%
464 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
1from plain import models
2from plain.models.migrations.operations.base import Operation
3from plain.models.migrations.state import ModelState
4from plain.models.migrations.utils import field_references, resolve_relation
5from plain.utils.functional import cached_property
7from .fields import AddField, AlterField, FieldOperation, RemoveField, RenameField
10def _check_for_duplicates(arg_name, objs):
11 used_vals = set()
12 for val in objs:
13 if val in used_vals:
14 raise ValueError(
15 f"Found duplicate value {val} in CreateModel {arg_name} argument."
16 )
17 used_vals.add(val)
20class ModelOperation(Operation):
21 def __init__(self, name):
22 self.name = name
24 @cached_property
25 def name_lower(self):
26 return self.name.lower()
28 def references_model(self, name, package_label):
29 return name.lower() == self.name_lower
31 def reduce(self, operation, package_label):
32 return super().reduce(operation, package_label) or self.can_reduce_through(
33 operation, package_label
34 )
36 def can_reduce_through(self, operation, package_label):
37 return not operation.references_model(self.name, package_label)
40class CreateModel(ModelOperation):
41 """Create a model's table."""
43 serialization_expand_args = ["fields", "options", "managers"]
45 def __init__(self, name, fields, options=None, bases=None, managers=None):
46 self.fields = fields
47 self.options = options or {}
48 self.bases = bases or (models.Model,)
49 self.managers = managers or []
50 super().__init__(name)
51 # Sanity-check that there are no duplicated field names, bases, or
52 # manager names
53 _check_for_duplicates("fields", (name for name, _ in self.fields))
54 _check_for_duplicates(
55 "bases",
56 (
57 base._meta.label_lower
58 if hasattr(base, "_meta")
59 else base.lower()
60 if isinstance(base, str)
61 else base
62 for base in self.bases
63 ),
64 )
65 _check_for_duplicates("managers", (name for name, _ in self.managers))
67 def deconstruct(self):
68 kwargs = {
69 "name": self.name,
70 "fields": self.fields,
71 }
72 if self.options:
73 kwargs["options"] = self.options
74 if self.bases and self.bases != (models.Model,):
75 kwargs["bases"] = self.bases
76 if self.managers and self.managers != [("objects", models.Manager())]:
77 kwargs["managers"] = self.managers
78 return (self.__class__.__qualname__, [], kwargs)
80 def state_forwards(self, package_label, state):
81 state.add_model(
82 ModelState(
83 package_label,
84 self.name,
85 list(self.fields),
86 dict(self.options),
87 tuple(self.bases),
88 list(self.managers),
89 )
90 )
92 def database_forwards(self, package_label, schema_editor, from_state, to_state):
93 model = to_state.packages.get_model(package_label, self.name)
94 if self.allow_migrate_model(schema_editor.connection.alias, model):
95 schema_editor.create_model(model)
97 def database_backwards(self, package_label, schema_editor, from_state, to_state):
98 model = from_state.packages.get_model(package_label, self.name)
99 if self.allow_migrate_model(schema_editor.connection.alias, model):
100 schema_editor.delete_model(model)
102 def describe(self):
103 return f"Create model {self.name}"
105 @property
106 def migration_name_fragment(self):
107 return self.name_lower
109 def references_model(self, name, package_label):
110 name_lower = name.lower()
111 if name_lower == self.name_lower:
112 return True
114 # Check we didn't inherit from the model
115 reference_model_tuple = (package_label, name_lower)
116 for base in self.bases:
117 if (
118 base is not models.Model
119 and isinstance(base, models.base.ModelBase | str)
120 and resolve_relation(base, package_label) == reference_model_tuple
121 ):
122 return True
124 # Check we have no FKs/M2Ms with it
125 for _name, field in self.fields:
126 if field_references(
127 (package_label, self.name_lower), field, reference_model_tuple
128 ):
129 return True
130 return False
132 def reduce(self, operation, package_label):
133 if (
134 isinstance(operation, DeleteModel)
135 and self.name_lower == operation.name_lower
136 ):
137 return []
138 elif (
139 isinstance(operation, RenameModel)
140 and self.name_lower == operation.old_name_lower
141 ):
142 return [
143 CreateModel(
144 operation.new_name,
145 fields=self.fields,
146 options=self.options,
147 bases=self.bases,
148 managers=self.managers,
149 ),
150 ]
151 elif (
152 isinstance(operation, AlterModelOptions)
153 and self.name_lower == operation.name_lower
154 ):
155 options = {**self.options, **operation.options}
156 for key in operation.ALTER_OPTION_KEYS:
157 if key not in operation.options:
158 options.pop(key, None)
159 return [
160 CreateModel(
161 self.name,
162 fields=self.fields,
163 options=options,
164 bases=self.bases,
165 managers=self.managers,
166 ),
167 ]
168 elif (
169 isinstance(operation, AlterModelManagers)
170 and self.name_lower == operation.name_lower
171 ):
172 return [
173 CreateModel(
174 self.name,
175 fields=self.fields,
176 options=self.options,
177 bases=self.bases,
178 managers=operation.managers,
179 ),
180 ]
181 elif (
182 isinstance(operation, AlterOrderWithRespectTo)
183 and self.name_lower == operation.name_lower
184 ):
185 return [
186 CreateModel(
187 self.name,
188 fields=self.fields,
189 options={
190 **self.options,
191 "order_with_respect_to": operation.order_with_respect_to,
192 },
193 bases=self.bases,
194 managers=self.managers,
195 ),
196 ]
197 elif (
198 isinstance(operation, FieldOperation)
199 and self.name_lower == operation.model_name_lower
200 ):
201 if isinstance(operation, AddField):
202 return [
203 CreateModel(
204 self.name,
205 fields=self.fields + [(operation.name, operation.field)],
206 options=self.options,
207 bases=self.bases,
208 managers=self.managers,
209 ),
210 ]
211 elif isinstance(operation, AlterField):
212 return [
213 CreateModel(
214 self.name,
215 fields=[
216 (n, operation.field if n == operation.name else v)
217 for n, v in self.fields
218 ],
219 options=self.options,
220 bases=self.bases,
221 managers=self.managers,
222 ),
223 ]
224 elif isinstance(operation, RemoveField):
225 options = self.options.copy()
227 order_with_respect_to = options.get("order_with_respect_to")
228 if order_with_respect_to == operation.name_lower:
229 del options["order_with_respect_to"]
230 return [
231 CreateModel(
232 self.name,
233 fields=[
234 (n, v)
235 for n, v in self.fields
236 if n.lower() != operation.name_lower
237 ],
238 options=options,
239 bases=self.bases,
240 managers=self.managers,
241 ),
242 ]
243 elif isinstance(operation, RenameField):
244 options = self.options.copy()
246 order_with_respect_to = options.get("order_with_respect_to")
247 if order_with_respect_to == operation.old_name:
248 options["order_with_respect_to"] = operation.new_name
249 return [
250 CreateModel(
251 self.name,
252 fields=[
253 (operation.new_name if n == operation.old_name else n, v)
254 for n, v in self.fields
255 ],
256 options=options,
257 bases=self.bases,
258 managers=self.managers,
259 ),
260 ]
261 return super().reduce(operation, package_label)
264class DeleteModel(ModelOperation):
265 """Drop a model's table."""
267 def deconstruct(self):
268 kwargs = {
269 "name": self.name,
270 }
271 return (self.__class__.__qualname__, [], kwargs)
273 def state_forwards(self, package_label, state):
274 state.remove_model(package_label, self.name_lower)
276 def database_forwards(self, package_label, schema_editor, from_state, to_state):
277 model = from_state.packages.get_model(package_label, self.name)
278 if self.allow_migrate_model(schema_editor.connection.alias, model):
279 schema_editor.delete_model(model)
281 def database_backwards(self, package_label, schema_editor, from_state, to_state):
282 model = to_state.packages.get_model(package_label, self.name)
283 if self.allow_migrate_model(schema_editor.connection.alias, model):
284 schema_editor.create_model(model)
286 def references_model(self, name, package_label):
287 # The deleted model could be referencing the specified model through
288 # related fields.
289 return True
291 def describe(self):
292 return f"Delete model {self.name}"
294 @property
295 def migration_name_fragment(self):
296 return f"delete_{self.name_lower}"
299class RenameModel(ModelOperation):
300 """Rename a model."""
302 def __init__(self, old_name, new_name):
303 self.old_name = old_name
304 self.new_name = new_name
305 super().__init__(old_name)
307 @cached_property
308 def old_name_lower(self):
309 return self.old_name.lower()
311 @cached_property
312 def new_name_lower(self):
313 return self.new_name.lower()
315 def deconstruct(self):
316 kwargs = {
317 "old_name": self.old_name,
318 "new_name": self.new_name,
319 }
320 return (self.__class__.__qualname__, [], kwargs)
322 def state_forwards(self, package_label, state):
323 state.rename_model(package_label, self.old_name, self.new_name)
325 def database_forwards(self, package_label, schema_editor, from_state, to_state):
326 new_model = to_state.packages.get_model(package_label, self.new_name)
327 if self.allow_migrate_model(schema_editor.connection.alias, new_model):
328 old_model = from_state.packages.get_model(package_label, self.old_name)
329 # Move the main table
330 schema_editor.alter_db_table(
331 new_model,
332 old_model._meta.db_table,
333 new_model._meta.db_table,
334 )
335 # Alter the fields pointing to us
336 for related_object in old_model._meta.related_objects:
337 if related_object.related_model == old_model:
338 model = new_model
339 related_key = (package_label, self.new_name_lower)
340 else:
341 model = related_object.related_model
342 related_key = (
343 related_object.related_model._meta.package_label,
344 related_object.related_model._meta.model_name,
345 )
346 to_field = to_state.packages.get_model(*related_key)._meta.get_field(
347 related_object.field.name
348 )
349 schema_editor.alter_field(
350 model,
351 related_object.field,
352 to_field,
353 )
354 # Rename M2M fields whose name is based on this model's name.
355 fields = zip(
356 old_model._meta.local_many_to_many, new_model._meta.local_many_to_many
357 )
358 for old_field, new_field in fields:
359 # Skip self-referential fields as these are renamed above.
360 if (
361 new_field.model == new_field.related_model
362 or not new_field.remote_field.through._meta.auto_created
363 ):
364 continue
365 # Rename columns and the M2M table.
366 schema_editor._alter_many_to_many(
367 new_model,
368 old_field,
369 new_field,
370 strict=False,
371 )
373 def database_backwards(self, package_label, schema_editor, from_state, to_state):
374 self.new_name_lower, self.old_name_lower = (
375 self.old_name_lower,
376 self.new_name_lower,
377 )
378 self.new_name, self.old_name = self.old_name, self.new_name
380 self.database_forwards(package_label, schema_editor, from_state, to_state)
382 self.new_name_lower, self.old_name_lower = (
383 self.old_name_lower,
384 self.new_name_lower,
385 )
386 self.new_name, self.old_name = self.old_name, self.new_name
388 def references_model(self, name, package_label):
389 return (
390 name.lower() == self.old_name_lower or name.lower() == self.new_name_lower
391 )
393 def describe(self):
394 return f"Rename model {self.old_name} to {self.new_name}"
396 @property
397 def migration_name_fragment(self):
398 return f"rename_{self.old_name_lower}_{self.new_name_lower}"
400 def reduce(self, operation, package_label):
401 if (
402 isinstance(operation, RenameModel)
403 and self.new_name_lower == operation.old_name_lower
404 ):
405 return [
406 RenameModel(
407 self.old_name,
408 operation.new_name,
409 ),
410 ]
411 # Skip `ModelOperation.reduce` as we want to run `references_model`
412 # against self.new_name.
413 return super(ModelOperation, self).reduce(
414 operation, package_label
415 ) or not operation.references_model(self.new_name, package_label)
418class ModelOptionOperation(ModelOperation):
419 def reduce(self, operation, package_label):
420 if (
421 isinstance(operation, self.__class__ | DeleteModel)
422 and self.name_lower == operation.name_lower
423 ):
424 return [operation]
425 return super().reduce(operation, package_label)
428class AlterModelTable(ModelOptionOperation):
429 """Rename a model's table."""
431 def __init__(self, name, table):
432 self.table = table
433 super().__init__(name)
435 def deconstruct(self):
436 kwargs = {
437 "name": self.name,
438 "table": self.table,
439 }
440 return (self.__class__.__qualname__, [], kwargs)
442 def state_forwards(self, package_label, state):
443 state.alter_model_options(
444 package_label, self.name_lower, {"db_table": self.table}
445 )
447 def database_forwards(self, package_label, schema_editor, from_state, to_state):
448 new_model = to_state.packages.get_model(package_label, self.name)
449 if self.allow_migrate_model(schema_editor.connection.alias, new_model):
450 old_model = from_state.packages.get_model(package_label, self.name)
451 schema_editor.alter_db_table(
452 new_model,
453 old_model._meta.db_table,
454 new_model._meta.db_table,
455 )
456 # Rename M2M fields whose name is based on this model's db_table
457 for old_field, new_field in zip(
458 old_model._meta.local_many_to_many, new_model._meta.local_many_to_many
459 ):
460 if new_field.remote_field.through._meta.auto_created:
461 schema_editor.alter_db_table(
462 new_field.remote_field.through,
463 old_field.remote_field.through._meta.db_table,
464 new_field.remote_field.through._meta.db_table,
465 )
467 def database_backwards(self, package_label, schema_editor, from_state, to_state):
468 return self.database_forwards(
469 package_label, schema_editor, from_state, to_state
470 )
472 def describe(self):
473 return "Rename table for {} to {}".format(
474 self.name,
475 self.table if self.table is not None else "(default)",
476 )
478 @property
479 def migration_name_fragment(self):
480 return f"alter_{self.name_lower}_table"
483class AlterModelTableComment(ModelOptionOperation):
484 def __init__(self, name, table_comment):
485 self.table_comment = table_comment
486 super().__init__(name)
488 def deconstruct(self):
489 kwargs = {
490 "name": self.name,
491 "table_comment": self.table_comment,
492 }
493 return (self.__class__.__qualname__, [], kwargs)
495 def state_forwards(self, package_label, state):
496 state.alter_model_options(
497 package_label, self.name_lower, {"db_table_comment": self.table_comment}
498 )
500 def database_forwards(self, package_label, schema_editor, from_state, to_state):
501 new_model = to_state.packages.get_model(package_label, self.name)
502 if self.allow_migrate_model(schema_editor.connection.alias, new_model):
503 old_model = from_state.packages.get_model(package_label, self.name)
504 schema_editor.alter_db_table_comment(
505 new_model,
506 old_model._meta.db_table_comment,
507 new_model._meta.db_table_comment,
508 )
510 def database_backwards(self, package_label, schema_editor, from_state, to_state):
511 return self.database_forwards(
512 package_label, schema_editor, from_state, to_state
513 )
515 def describe(self):
516 return f"Alter {self.name} table comment"
518 @property
519 def migration_name_fragment(self):
520 return f"alter_{self.name_lower}_table_comment"
523class AlterOrderWithRespectTo(ModelOptionOperation):
524 """Represent a change with the order_with_respect_to option."""
526 option_name = "order_with_respect_to"
528 def __init__(self, name, order_with_respect_to):
529 self.order_with_respect_to = order_with_respect_to
530 super().__init__(name)
532 def deconstruct(self):
533 kwargs = {
534 "name": self.name,
535 "order_with_respect_to": self.order_with_respect_to,
536 }
537 return (self.__class__.__qualname__, [], kwargs)
539 def state_forwards(self, package_label, state):
540 state.alter_model_options(
541 package_label,
542 self.name_lower,
543 {self.option_name: self.order_with_respect_to},
544 )
546 def database_forwards(self, package_label, schema_editor, from_state, to_state):
547 to_model = to_state.packages.get_model(package_label, self.name)
548 if self.allow_migrate_model(schema_editor.connection.alias, to_model):
549 from_model = from_state.packages.get_model(package_label, self.name)
550 # Remove a field if we need to
551 if (
552 from_model._meta.order_with_respect_to
553 and not to_model._meta.order_with_respect_to
554 ):
555 schema_editor.remove_field(
556 from_model, from_model._meta.get_field("_order")
557 )
558 # Add a field if we need to (altering the column is untouched as
559 # it's likely a rename)
560 elif (
561 to_model._meta.order_with_respect_to
562 and not from_model._meta.order_with_respect_to
563 ):
564 field = to_model._meta.get_field("_order")
565 if not field.has_default():
566 field.default = 0
567 schema_editor.add_field(
568 from_model,
569 field,
570 )
572 def database_backwards(self, package_label, schema_editor, from_state, to_state):
573 self.database_forwards(package_label, schema_editor, from_state, to_state)
575 def references_field(self, model_name, name, package_label):
576 return self.references_model(model_name, package_label) and (
577 self.order_with_respect_to is None or name == self.order_with_respect_to
578 )
580 def describe(self):
581 return (
582 f"Set order_with_respect_to on {self.name} to {self.order_with_respect_to}"
583 )
585 @property
586 def migration_name_fragment(self):
587 return f"alter_{self.name_lower}_order_with_respect_to"
590class AlterModelOptions(ModelOptionOperation):
591 """
592 Set new model options that don't directly affect the database schema
593 (like ordering). Python code in migrations
594 may still need them.
595 """
597 # Model options we want to compare and preserve in an AlterModelOptions op
598 ALTER_OPTION_KEYS = [
599 "base_manager_name",
600 "default_manager_name",
601 "default_related_name",
602 "get_latest_by",
603 "managed",
604 "ordering",
605 "select_on_save",
606 ]
608 def __init__(self, name, options):
609 self.options = options
610 super().__init__(name)
612 def deconstruct(self):
613 kwargs = {
614 "name": self.name,
615 "options": self.options,
616 }
617 return (self.__class__.__qualname__, [], kwargs)
619 def state_forwards(self, package_label, state):
620 state.alter_model_options(
621 package_label,
622 self.name_lower,
623 self.options,
624 self.ALTER_OPTION_KEYS,
625 )
627 def database_forwards(self, package_label, schema_editor, from_state, to_state):
628 pass
630 def database_backwards(self, package_label, schema_editor, from_state, to_state):
631 pass
633 def describe(self):
634 return f"Change Meta options on {self.name}"
636 @property
637 def migration_name_fragment(self):
638 return f"alter_{self.name_lower}_options"
641class AlterModelManagers(ModelOptionOperation):
642 """Alter the model's managers."""
644 serialization_expand_args = ["managers"]
646 def __init__(self, name, managers):
647 self.managers = managers
648 super().__init__(name)
650 def deconstruct(self):
651 return (self.__class__.__qualname__, [self.name, self.managers], {})
653 def state_forwards(self, package_label, state):
654 state.alter_model_managers(package_label, self.name_lower, self.managers)
656 def database_forwards(self, package_label, schema_editor, from_state, to_state):
657 pass
659 def database_backwards(self, package_label, schema_editor, from_state, to_state):
660 pass
662 def describe(self):
663 return f"Change managers on {self.name}"
665 @property
666 def migration_name_fragment(self):
667 return f"alter_{self.name_lower}_managers"
670class IndexOperation(Operation):
671 option_name = "indexes"
673 @cached_property
674 def model_name_lower(self):
675 return self.model_name.lower()
678class AddIndex(IndexOperation):
679 """Add an index on a model."""
681 def __init__(self, model_name, index):
682 self.model_name = model_name
683 if not index.name:
684 raise ValueError(
685 "Indexes passed to AddIndex operations require a name "
686 f"argument. {index!r} doesn't have one."
687 )
688 self.index = index
690 def state_forwards(self, package_label, state):
691 state.add_index(package_label, self.model_name_lower, self.index)
693 def database_forwards(self, package_label, schema_editor, from_state, to_state):
694 model = to_state.packages.get_model(package_label, self.model_name)
695 if self.allow_migrate_model(schema_editor.connection.alias, model):
696 schema_editor.add_index(model, self.index)
698 def database_backwards(self, package_label, schema_editor, from_state, to_state):
699 model = from_state.packages.get_model(package_label, self.model_name)
700 if self.allow_migrate_model(schema_editor.connection.alias, model):
701 schema_editor.remove_index(model, self.index)
703 def deconstruct(self):
704 kwargs = {
705 "model_name": self.model_name,
706 "index": self.index,
707 }
708 return (
709 self.__class__.__qualname__,
710 [],
711 kwargs,
712 )
714 def describe(self):
715 if self.index.expressions:
716 return "Create index {} on {} on model {}".format(
717 self.index.name,
718 ", ".join([str(expression) for expression in self.index.expressions]),
719 self.model_name,
720 )
721 return "Create index {} on field(s) {} of model {}".format(
722 self.index.name,
723 ", ".join(self.index.fields),
724 self.model_name,
725 )
727 @property
728 def migration_name_fragment(self):
729 return f"{self.model_name_lower}_{self.index.name.lower()}"
732class RemoveIndex(IndexOperation):
733 """Remove an index from a model."""
735 def __init__(self, model_name, name):
736 self.model_name = model_name
737 self.name = name
739 def state_forwards(self, package_label, state):
740 state.remove_index(package_label, self.model_name_lower, self.name)
742 def database_forwards(self, package_label, schema_editor, from_state, to_state):
743 model = from_state.packages.get_model(package_label, self.model_name)
744 if self.allow_migrate_model(schema_editor.connection.alias, model):
745 from_model_state = from_state.models[package_label, self.model_name_lower]
746 index = from_model_state.get_index_by_name(self.name)
747 schema_editor.remove_index(model, index)
749 def database_backwards(self, package_label, schema_editor, from_state, to_state):
750 model = to_state.packages.get_model(package_label, self.model_name)
751 if self.allow_migrate_model(schema_editor.connection.alias, model):
752 to_model_state = to_state.models[package_label, self.model_name_lower]
753 index = to_model_state.get_index_by_name(self.name)
754 schema_editor.add_index(model, index)
756 def deconstruct(self):
757 kwargs = {
758 "model_name": self.model_name,
759 "name": self.name,
760 }
761 return (
762 self.__class__.__qualname__,
763 [],
764 kwargs,
765 )
767 def describe(self):
768 return f"Remove index {self.name} from {self.model_name}"
770 @property
771 def migration_name_fragment(self):
772 return f"remove_{self.model_name_lower}_{self.name.lower()}"
775class RenameIndex(IndexOperation):
776 """Rename an index."""
778 def __init__(self, model_name, new_name, old_name=None, old_fields=None):
779 if not old_name and not old_fields:
780 raise ValueError(
781 "RenameIndex requires one of old_name and old_fields arguments to be "
782 "set."
783 )
784 if old_name and old_fields:
785 raise ValueError(
786 "RenameIndex.old_name and old_fields are mutually exclusive."
787 )
788 self.model_name = model_name
789 self.new_name = new_name
790 self.old_name = old_name
791 self.old_fields = old_fields
793 @cached_property
794 def old_name_lower(self):
795 return self.old_name.lower()
797 @cached_property
798 def new_name_lower(self):
799 return self.new_name.lower()
801 def deconstruct(self):
802 kwargs = {
803 "model_name": self.model_name,
804 "new_name": self.new_name,
805 }
806 if self.old_name:
807 kwargs["old_name"] = self.old_name
808 if self.old_fields:
809 kwargs["old_fields"] = self.old_fields
810 return (self.__class__.__qualname__, [], kwargs)
812 def state_forwards(self, package_label, state):
813 if self.old_fields:
814 state.add_index(
815 package_label,
816 self.model_name_lower,
817 models.Index(fields=self.old_fields, name=self.new_name),
818 )
819 else:
820 state.rename_index(
821 package_label, self.model_name_lower, self.old_name, self.new_name
822 )
824 def database_forwards(self, package_label, schema_editor, from_state, to_state):
825 model = to_state.packages.get_model(package_label, self.model_name)
826 if not self.allow_migrate_model(schema_editor.connection.alias, model):
827 return
829 if self.old_fields:
830 from_model = from_state.packages.get_model(package_label, self.model_name)
831 columns = [
832 from_model._meta.get_field(field).column for field in self.old_fields
833 ]
834 matching_index_name = schema_editor._constraint_names(
835 from_model, column_names=columns, index=True
836 )
837 if len(matching_index_name) != 1:
838 raise ValueError(
839 "Found wrong number ({}) of indexes for {}({}).".format(
840 len(matching_index_name),
841 from_model._meta.db_table,
842 ", ".join(columns),
843 )
844 )
845 old_index = models.Index(
846 fields=self.old_fields,
847 name=matching_index_name[0],
848 )
849 else:
850 from_model_state = from_state.models[package_label, self.model_name_lower]
851 old_index = from_model_state.get_index_by_name(self.old_name)
852 # Don't alter when the index name is not changed.
853 if old_index.name == self.new_name:
854 return
856 to_model_state = to_state.models[package_label, self.model_name_lower]
857 new_index = to_model_state.get_index_by_name(self.new_name)
858 schema_editor.rename_index(model, old_index, new_index)
860 def database_backwards(self, package_label, schema_editor, from_state, to_state):
861 if self.old_fields:
862 # Backward operation with unnamed index is a no-op.
863 return
865 self.new_name_lower, self.old_name_lower = (
866 self.old_name_lower,
867 self.new_name_lower,
868 )
869 self.new_name, self.old_name = self.old_name, self.new_name
871 self.database_forwards(package_label, schema_editor, from_state, to_state)
873 self.new_name_lower, self.old_name_lower = (
874 self.old_name_lower,
875 self.new_name_lower,
876 )
877 self.new_name, self.old_name = self.old_name, self.new_name
879 def describe(self):
880 if self.old_name:
881 return (
882 f"Rename index {self.old_name} on {self.model_name} to {self.new_name}"
883 )
884 return (
885 f"Rename unnamed index for {self.old_fields} on {self.model_name} to "
886 f"{self.new_name}"
887 )
889 @property
890 def migration_name_fragment(self):
891 if self.old_name:
892 return f"rename_{self.old_name_lower}_{self.new_name_lower}"
893 return "rename_{}_{}_{}".format(
894 self.model_name_lower,
895 "_".join(self.old_fields),
896 self.new_name_lower,
897 )
899 def reduce(self, operation, package_label):
900 if (
901 isinstance(operation, RenameIndex)
902 and self.model_name_lower == operation.model_name_lower
903 and operation.old_name
904 and self.new_name_lower == operation.old_name_lower
905 ):
906 return [
907 RenameIndex(
908 self.model_name,
909 new_name=operation.new_name,
910 old_name=self.old_name,
911 old_fields=self.old_fields,
912 )
913 ]
914 return super().reduce(operation, package_label)
917class AddConstraint(IndexOperation):
918 option_name = "constraints"
920 def __init__(self, model_name, constraint):
921 self.model_name = model_name
922 self.constraint = constraint
924 def state_forwards(self, package_label, state):
925 state.add_constraint(package_label, self.model_name_lower, self.constraint)
927 def database_forwards(self, package_label, schema_editor, from_state, to_state):
928 model = to_state.packages.get_model(package_label, self.model_name)
929 if self.allow_migrate_model(schema_editor.connection.alias, model):
930 schema_editor.add_constraint(model, self.constraint)
932 def database_backwards(self, package_label, schema_editor, from_state, to_state):
933 model = to_state.packages.get_model(package_label, self.model_name)
934 if self.allow_migrate_model(schema_editor.connection.alias, model):
935 schema_editor.remove_constraint(model, self.constraint)
937 def deconstruct(self):
938 return (
939 self.__class__.__name__,
940 [],
941 {
942 "model_name": self.model_name,
943 "constraint": self.constraint,
944 },
945 )
947 def describe(self):
948 return f"Create constraint {self.constraint.name} on model {self.model_name}"
950 @property
951 def migration_name_fragment(self):
952 return f"{self.model_name_lower}_{self.constraint.name.lower()}"
955class RemoveConstraint(IndexOperation):
956 option_name = "constraints"
958 def __init__(self, model_name, name):
959 self.model_name = model_name
960 self.name = name
962 def state_forwards(self, package_label, state):
963 state.remove_constraint(package_label, self.model_name_lower, self.name)
965 def database_forwards(self, package_label, schema_editor, from_state, to_state):
966 model = to_state.packages.get_model(package_label, self.model_name)
967 if self.allow_migrate_model(schema_editor.connection.alias, model):
968 from_model_state = from_state.models[package_label, self.model_name_lower]
969 constraint = from_model_state.get_constraint_by_name(self.name)
970 schema_editor.remove_constraint(model, constraint)
972 def database_backwards(self, package_label, schema_editor, from_state, to_state):
973 model = to_state.packages.get_model(package_label, self.model_name)
974 if self.allow_migrate_model(schema_editor.connection.alias, model):
975 to_model_state = to_state.models[package_label, self.model_name_lower]
976 constraint = to_model_state.get_constraint_by_name(self.name)
977 schema_editor.add_constraint(model, constraint)
979 def deconstruct(self):
980 return (
981 self.__class__.__name__,
982 [],
983 {
984 "model_name": self.model_name,
985 "name": self.name,
986 },
987 )
989 def describe(self):
990 return f"Remove constraint {self.name} from model {self.model_name}"
992 @property
993 def migration_name_fragment(self):
994 return f"remove_{self.model_name_lower}_{self.name.lower()}"