Plain is headed towards 1.0! Subscribe for development updates →

  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        )