migration.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import re
  2. from django.db.migrations.utils import get_migration_name_timestamp
  3. from django.db.transaction import atomic
  4. from .exceptions import IrreversibleError
  5. class Migration:
  6. """
  7. The base class for all migrations.
  8. Migration files will import this from django.db.migrations.Migration
  9. and subclass it as a class called Migration. It will have one or more
  10. of the following attributes:
  11. - operations: A list of Operation instances, probably from
  12. django.db.migrations.operations
  13. - dependencies: A list of tuples of (app_path, migration_name)
  14. - run_before: A list of tuples of (app_path, migration_name)
  15. - replaces: A list of migration_names
  16. Note that all migrations come out of migrations and into the Loader or
  17. Graph as instances, having been initialized with their app label and name.
  18. """
  19. # Operations to apply during this migration, in order.
  20. operations = []
  21. # Other migrations that should be run before this migration.
  22. # Should be a list of (app, migration_name).
  23. dependencies = []
  24. # Other migrations that should be run after this one (i.e. have
  25. # this migration added to their dependencies). Useful to make third-party
  26. # apps' migrations run after your AUTH_USER replacement, for example.
  27. run_before = []
  28. # Migration names in this app that this migration replaces. If this is
  29. # non-empty, this migration will only be applied if all these migrations
  30. # are not applied.
  31. replaces = []
  32. # Is this an initial migration? Initial migrations are skipped on
  33. # --fake-initial if the table or fields already exist. If None, check if
  34. # the migration has any dependencies to determine if there are dependencies
  35. # to tell if db introspection needs to be done. If True, always perform
  36. # introspection. If False, never perform introspection.
  37. initial = None
  38. # Whether to wrap the whole migration in a transaction. Only has an effect
  39. # on database backends which support transactional DDL.
  40. atomic = True
  41. def __init__(self, name, app_label):
  42. self.name = name
  43. self.app_label = app_label
  44. # Copy dependencies & other attrs as we might mutate them at runtime
  45. self.operations = list(self.__class__.operations)
  46. self.dependencies = list(self.__class__.dependencies)
  47. self.run_before = list(self.__class__.run_before)
  48. self.replaces = list(self.__class__.replaces)
  49. def __eq__(self, other):
  50. return (
  51. isinstance(other, Migration)
  52. and self.name == other.name
  53. and self.app_label == other.app_label
  54. )
  55. def __repr__(self):
  56. return "<Migration %s.%s>" % (self.app_label, self.name)
  57. def __str__(self):
  58. return "%s.%s" % (self.app_label, self.name)
  59. def __hash__(self):
  60. return hash("%s.%s" % (self.app_label, self.name))
  61. def mutate_state(self, project_state, preserve=True):
  62. """
  63. Take a ProjectState and return a new one with the migration's
  64. operations applied to it. Preserve the original object state by
  65. default and return a mutated state from a copy.
  66. """
  67. new_state = project_state
  68. if preserve:
  69. new_state = project_state.clone()
  70. for operation in self.operations:
  71. operation.state_forwards(self.app_label, new_state)
  72. return new_state
  73. def apply(self, project_state, schema_editor, collect_sql=False):
  74. """
  75. Take a project_state representing all migrations prior to this one
  76. and a schema_editor for a live database and apply the migration
  77. in a forwards order.
  78. Return the resulting project state for efficient reuse by following
  79. Migrations.
  80. """
  81. for operation in self.operations:
  82. # If this operation cannot be represented as SQL, place a comment
  83. # there instead
  84. if collect_sql:
  85. schema_editor.collected_sql.append("--")
  86. schema_editor.collected_sql.append("-- %s" % operation.describe())
  87. schema_editor.collected_sql.append("--")
  88. if not operation.reduces_to_sql:
  89. schema_editor.collected_sql.append(
  90. "-- THIS OPERATION CANNOT BE WRITTEN AS SQL"
  91. )
  92. continue
  93. collected_sql_before = len(schema_editor.collected_sql)
  94. # Save the state before the operation has run
  95. old_state = project_state.clone()
  96. operation.state_forwards(self.app_label, project_state)
  97. # Run the operation
  98. atomic_operation = operation.atomic or (
  99. self.atomic and operation.atomic is not False
  100. )
  101. if not schema_editor.atomic_migration and atomic_operation:
  102. # Force a transaction on a non-transactional-DDL backend or an
  103. # atomic operation inside a non-atomic migration.
  104. with atomic(schema_editor.connection.alias):
  105. operation.database_forwards(
  106. self.app_label, schema_editor, old_state, project_state
  107. )
  108. else:
  109. # Normal behaviour
  110. operation.database_forwards(
  111. self.app_label, schema_editor, old_state, project_state
  112. )
  113. if collect_sql and collected_sql_before == len(schema_editor.collected_sql):
  114. schema_editor.collected_sql.append("-- (no-op)")
  115. return project_state
  116. def unapply(self, project_state, schema_editor, collect_sql=False):
  117. """
  118. Take a project_state representing all migrations prior to this one
  119. and a schema_editor for a live database and apply the migration
  120. in a reverse order.
  121. The backwards migration process consists of two phases:
  122. 1. The intermediate states from right before the first until right
  123. after the last operation inside this migration are preserved.
  124. 2. The operations are applied in reverse order using the states
  125. recorded in step 1.
  126. """
  127. # Construct all the intermediate states we need for a reverse migration
  128. to_run = []
  129. new_state = project_state
  130. # Phase 1
  131. for operation in self.operations:
  132. # If it's irreversible, error out
  133. if not operation.reversible:
  134. raise IrreversibleError(
  135. "Operation %s in %s is not reversible" % (operation, self)
  136. )
  137. # Preserve new state from previous run to not tamper the same state
  138. # over all operations
  139. new_state = new_state.clone()
  140. old_state = new_state.clone()
  141. operation.state_forwards(self.app_label, new_state)
  142. to_run.insert(0, (operation, old_state, new_state))
  143. # Phase 2
  144. for operation, to_state, from_state in to_run:
  145. if collect_sql:
  146. schema_editor.collected_sql.append("--")
  147. schema_editor.collected_sql.append("-- %s" % operation.describe())
  148. schema_editor.collected_sql.append("--")
  149. if not operation.reduces_to_sql:
  150. schema_editor.collected_sql.append(
  151. "-- THIS OPERATION CANNOT BE WRITTEN AS SQL"
  152. )
  153. continue
  154. collected_sql_before = len(schema_editor.collected_sql)
  155. atomic_operation = operation.atomic or (
  156. self.atomic and operation.atomic is not False
  157. )
  158. if not schema_editor.atomic_migration and atomic_operation:
  159. # Force a transaction on a non-transactional-DDL backend or an
  160. # atomic operation inside a non-atomic migration.
  161. with atomic(schema_editor.connection.alias):
  162. operation.database_backwards(
  163. self.app_label, schema_editor, from_state, to_state
  164. )
  165. else:
  166. # Normal behaviour
  167. operation.database_backwards(
  168. self.app_label, schema_editor, from_state, to_state
  169. )
  170. if collect_sql and collected_sql_before == len(schema_editor.collected_sql):
  171. schema_editor.collected_sql.append("-- (no-op)")
  172. return project_state
  173. def suggest_name(self):
  174. """
  175. Suggest a name for the operations this migration might represent. Names
  176. are not guaranteed to be unique, but put some effort into the fallback
  177. name to avoid VCS conflicts if possible.
  178. """
  179. if self.initial:
  180. return "initial"
  181. raw_fragments = [op.migration_name_fragment for op in self.operations]
  182. fragments = [re.sub(r"\W+", "_", name) for name in raw_fragments if name]
  183. if not fragments or len(fragments) != len(self.operations):
  184. return "auto_%s" % get_migration_name_timestamp()
  185. name = fragments[0]
  186. for fragment in fragments[1:]:
  187. new_name = f"{name}_{fragment}"
  188. if len(new_name) > 52:
  189. name = f"{name}_and_more"
  190. break
  191. name = new_name
  192. return name
  193. class SwappableTuple(tuple):
  194. """
  195. Subclass of tuple so Django can tell this was originally a swappable
  196. dependency when it reads the migration file.
  197. """
  198. def __new__(cls, value, setting):
  199. self = tuple.__new__(cls, value)
  200. self.setting = setting
  201. return self
  202. def swappable_dependency(value):
  203. """Turn a setting value into a dependency."""
  204. return SwappableTuple((value.split(".", 1)[0], "__first__"), value)