1from plain.models.db import router
2
3
4class Operation:
5 """
6 Base class for migration operations.
7
8 It's responsible for both mutating the in-memory model state
9 (see db/migrations/state.py) to represent what it performs, as well
10 as actually performing it against a live database.
11
12 Note that some operations won't modify memory state at all (e.g. data
13 copying operations), and some will need their modifications to be
14 optionally specified by the user (e.g. custom Python code snippets)
15
16 Due to the way this class deals with deconstruction, it should be
17 considered immutable.
18 """
19
20 # If this migration can be run in reverse.
21 # Some operations are impossible to reverse, like deleting data.
22 reversible = True
23
24 # Can this migration be represented as SQL? (things like RunPython cannot)
25 reduces_to_sql = True
26
27 # Should this operation be forced as atomic even on backends with no
28 # DDL transaction support (i.e., does it have no DDL, like RunPython)
29 atomic = False
30
31 # Should this operation be considered safe to elide and optimize across?
32 elidable = False
33
34 serialization_expand_args = []
35
36 def __new__(cls, *args, **kwargs):
37 # We capture the arguments to make returning them trivial
38 self = object.__new__(cls)
39 self._constructor_args = (args, kwargs)
40 return self
41
42 def deconstruct(self):
43 """
44 Return a 3-tuple of class import path (or just name if it lives
45 under plain.models.migrations), positional arguments, and keyword
46 arguments.
47 """
48 return (
49 self.__class__.__name__,
50 self._constructor_args[0],
51 self._constructor_args[1],
52 )
53
54 def state_forwards(self, package_label, state):
55 """
56 Take the state from the previous migration, and mutate it
57 so that it matches what this migration would perform.
58 """
59 raise NotImplementedError(
60 "subclasses of Operation must provide a state_forwards() method"
61 )
62
63 def database_forwards(self, package_label, schema_editor, from_state, to_state):
64 """
65 Perform the mutation on the database schema in the normal
66 (forwards) direction.
67 """
68 raise NotImplementedError(
69 "subclasses of Operation must provide a database_forwards() method"
70 )
71
72 def database_backwards(self, package_label, schema_editor, from_state, to_state):
73 """
74 Perform the mutation on the database schema in the reverse
75 direction - e.g. if this were CreateModel, it would in fact
76 drop the model's table.
77 """
78 raise NotImplementedError(
79 "subclasses of Operation must provide a database_backwards() method"
80 )
81
82 def describe(self):
83 """
84 Output a brief summary of what the action does.
85 """
86 return f"{self.__class__.__name__}: {self._constructor_args}"
87
88 @property
89 def migration_name_fragment(self):
90 """
91 A filename part suitable for automatically naming a migration
92 containing this operation, or None if not applicable.
93 """
94 return None
95
96 def references_model(self, name, package_label):
97 """
98 Return True if there is a chance this operation references the given
99 model name (as a string), with an app label for accuracy.
100
101 Used for optimization. If in doubt, return True;
102 returning a false positive will merely make the optimizer a little
103 less efficient, while returning a false negative may result in an
104 unusable optimized migration.
105 """
106 return True
107
108 def references_field(self, model_name, name, package_label):
109 """
110 Return True if there is a chance this operation references the given
111 field name, with an app label for accuracy.
112
113 Used for optimization. If in doubt, return True.
114 """
115 return self.references_model(model_name, package_label)
116
117 def allow_migrate_model(self, connection_alias, model):
118 """
119 Return whether or not a model may be migrated.
120
121 This is a thin wrapper around router.allow_migrate_model() that
122 preemptively rejects any swapped out or unmanaged model.
123 """
124 if not model._meta.can_migrate(connection_alias):
125 return False
126
127 return router.allow_migrate_model(connection_alias, model)
128
129 def reduce(self, operation, package_label):
130 """
131 Return either a list of operations the actual operation should be
132 replaced with or a boolean that indicates whether or not the specified
133 operation can be optimized across.
134 """
135 if self.elidable:
136 return [operation]
137 elif operation.elidable:
138 return [self]
139 return False
140
141 def __repr__(self):
142 return "<{} {}{}>".format(
143 self.__class__.__name__,
144 ", ".join(map(repr, self._constructor_args[0])),
145 ",".join(" {}={!r}".format(*x) for x in self._constructor_args[1].items()),
146 )