You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

193 lines
7.8 KiB

  1. from __future__ import unicode_literals
  2. from django.db.transaction import atomic
  3. from django.utils.encoding import python_2_unicode_compatible
  4. from .exceptions import IrreversibleError
  5. @python_2_unicode_compatible
  6. class Migration(object):
  7. """
  8. The base class for all migrations.
  9. Migration files will import this from django.db.migrations.Migration
  10. and subclass it as a class called Migration. It will have one or more
  11. of the following attributes:
  12. - operations: A list of Operation instances, probably from 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. def __init__(self, name, app_label):
  39. self.name = name
  40. self.app_label = app_label
  41. # Copy dependencies & other attrs as we might mutate them at runtime
  42. self.operations = list(self.__class__.operations)
  43. self.dependencies = list(self.__class__.dependencies)
  44. self.run_before = list(self.__class__.run_before)
  45. self.replaces = list(self.__class__.replaces)
  46. def __eq__(self, other):
  47. if not isinstance(other, Migration):
  48. return False
  49. return (self.name == other.name) and (self.app_label == other.app_label)
  50. def __ne__(self, other):
  51. return not (self == other)
  52. def __repr__(self):
  53. return "<Migration %s.%s>" % (self.app_label, self.name)
  54. def __str__(self):
  55. return "%s.%s" % (self.app_label, self.name)
  56. def __hash__(self):
  57. return hash("%s.%s" % (self.app_label, self.name))
  58. def mutate_state(self, project_state, preserve=True):
  59. """
  60. Takes a ProjectState and returns a new one with the migration's
  61. operations applied to it. Preserves the original object state by
  62. default and will return a mutated state from a copy.
  63. """
  64. new_state = project_state
  65. if preserve:
  66. new_state = project_state.clone()
  67. for operation in self.operations:
  68. operation.state_forwards(self.app_label, new_state)
  69. return new_state
  70. def apply(self, project_state, schema_editor, collect_sql=False):
  71. """
  72. Takes a project_state representing all migrations prior to this one
  73. and a schema_editor for a live database and applies the migration
  74. in a forwards order.
  75. Returns the resulting project state for efficient re-use by following
  76. Migrations.
  77. """
  78. for operation in self.operations:
  79. # If this operation cannot be represented as SQL, place a comment
  80. # there instead
  81. if collect_sql:
  82. schema_editor.collected_sql.append("--")
  83. if not operation.reduces_to_sql:
  84. schema_editor.collected_sql.append(
  85. "-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:"
  86. )
  87. schema_editor.collected_sql.append("-- %s" % operation.describe())
  88. schema_editor.collected_sql.append("--")
  89. if not operation.reduces_to_sql:
  90. continue
  91. # Save the state before the operation has run
  92. old_state = project_state.clone()
  93. operation.state_forwards(self.app_label, project_state)
  94. # Run the operation
  95. if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:
  96. # We're forcing a transaction on a non-transactional-DDL backend
  97. with atomic(schema_editor.connection.alias):
  98. operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  99. else:
  100. # Normal behaviour
  101. operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  102. return project_state
  103. def unapply(self, project_state, schema_editor, collect_sql=False):
  104. """
  105. Takes a project_state representing all migrations prior to this one
  106. and a schema_editor for a live database and applies the migration
  107. in a reverse order.
  108. The backwards migration process consists of two phases:
  109. 1. The intermediate states from right before the first until right
  110. after the last operation inside this migration are preserved.
  111. 2. The operations are applied in reverse order using the states
  112. recorded in step 1.
  113. """
  114. # Construct all the intermediate states we need for a reverse migration
  115. to_run = []
  116. new_state = project_state
  117. # Phase 1
  118. for operation in self.operations:
  119. # If it's irreversible, error out
  120. if not operation.reversible:
  121. raise IrreversibleError("Operation %s in %s is not reversible" % (operation, self))
  122. # Preserve new state from previous run to not tamper the same state
  123. # over all operations
  124. new_state = new_state.clone()
  125. old_state = new_state.clone()
  126. operation.state_forwards(self.app_label, new_state)
  127. to_run.insert(0, (operation, old_state, new_state))
  128. # Phase 2
  129. for operation, to_state, from_state in to_run:
  130. if collect_sql:
  131. schema_editor.collected_sql.append("--")
  132. if not operation.reduces_to_sql:
  133. schema_editor.collected_sql.append(
  134. "-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:"
  135. )
  136. schema_editor.collected_sql.append("-- %s" % operation.describe())
  137. schema_editor.collected_sql.append("--")
  138. if not operation.reduces_to_sql:
  139. continue
  140. if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:
  141. # We're forcing a transaction on a non-transactional-DDL backend
  142. with atomic(schema_editor.connection.alias):
  143. operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
  144. else:
  145. # Normal behaviour
  146. operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
  147. return project_state
  148. class SwappableTuple(tuple):
  149. """
  150. Subclass of tuple so Django can tell this was originally a swappable
  151. dependency when it reads the migration file.
  152. """
  153. def __new__(cls, value, setting):
  154. self = tuple.__new__(cls, value)
  155. self.setting = setting
  156. return self
  157. def swappable_dependency(value):
  158. """
  159. Turns a setting value into a dependency.
  160. """
  161. return SwappableTuple((value.split(".", 1)[0], "__first__"), value)