migration.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. from __future__ import unicode_literals
  2. from django.db.transaction import atomic
  3. class Migration(object):
  4. """
  5. The base class for all migrations.
  6. Migration files will import this from django.db.migrations.Migration
  7. and subclass it as a class called Migration. It will have one or more
  8. of the following attributes:
  9. - operations: A list of Operation instances, probably from django.db.migrations.operations
  10. - dependencies: A list of tuples of (app_path, migration_name)
  11. - run_before: A list of tuples of (app_path, migration_name)
  12. - replaces: A list of migration_names
  13. Note that all migrations come out of migrations and into the Loader or
  14. Graph as instances, having been initialized with their app label and name.
  15. """
  16. # Operations to apply during this migration, in order.
  17. operations = []
  18. # Other migrations that should be run before this migration.
  19. # Should be a list of (app, migration_name).
  20. dependencies = []
  21. # Other migrations that should be run after this one (i.e. have
  22. # this migration added to their dependencies). Useful to make third-party
  23. # apps' migrations run after your AUTH_USER replacement, for example.
  24. run_before = []
  25. # Migration names in this app that this migration replaces. If this is
  26. # non-empty, this migration will only be applied if all these migrations
  27. # are not applied.
  28. replaces = []
  29. # Error class which is raised when a migration is irreversible
  30. class IrreversibleError(RuntimeError):
  31. pass
  32. def __init__(self, name, app_label):
  33. self.name = name
  34. self.app_label = app_label
  35. # Copy dependencies & other attrs as we might mutate them at runtime
  36. self.operations = list(self.__class__.operations)
  37. self.dependencies = list(self.__class__.dependencies)
  38. self.run_before = list(self.__class__.run_before)
  39. self.replaces = list(self.__class__.replaces)
  40. def __eq__(self, other):
  41. if not isinstance(other, Migration):
  42. return False
  43. return (self.name == other.name) and (self.app_label == other.app_label)
  44. def __ne__(self, other):
  45. return not (self == other)
  46. def __repr__(self):
  47. return "<Migration %s.%s>" % (self.app_label, self.name)
  48. def __str__(self):
  49. return "%s.%s" % (self.app_label, self.name)
  50. def __hash__(self):
  51. return hash("%s.%s" % (self.app_label, self.name))
  52. def mutate_state(self, project_state):
  53. """
  54. Takes a ProjectState and returns a new one with the migration's
  55. operations applied to it.
  56. """
  57. new_state = project_state.clone()
  58. for operation in self.operations:
  59. operation.state_forwards(self.app_label, new_state)
  60. return new_state
  61. def apply(self, project_state, schema_editor, collect_sql=False):
  62. """
  63. Takes a project_state representing all migrations prior to this one
  64. and a schema_editor for a live database and applies the migration
  65. in a forwards order.
  66. Returns the resulting project state for efficient re-use by following
  67. Migrations.
  68. """
  69. for operation in self.operations:
  70. # If this operation cannot be represented as SQL, place a comment
  71. # there instead
  72. if collect_sql and not operation.reduces_to_sql:
  73. schema_editor.collected_sql.append("--")
  74. schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:")
  75. schema_editor.collected_sql.append("-- %s" % operation.describe())
  76. schema_editor.collected_sql.append("--")
  77. continue
  78. # Get the state after the operation has run
  79. new_state = project_state.clone()
  80. operation.state_forwards(self.app_label, new_state)
  81. # Run the operation
  82. if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:
  83. # We're forcing a transaction on a non-transactional-DDL backend
  84. with atomic(schema_editor.connection.alias):
  85. operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  86. else:
  87. # Normal behaviour
  88. operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  89. # Switch states
  90. project_state = new_state
  91. return project_state
  92. def unapply(self, project_state, schema_editor, collect_sql=False):
  93. """
  94. Takes a project_state representing all migrations prior to this one
  95. and a schema_editor for a live database and applies the migration
  96. in a reverse order.
  97. """
  98. # We need to pre-calculate the stack of project states
  99. to_run = []
  100. for operation in self.operations:
  101. # If this operation cannot be represented as SQL, place a comment
  102. # there instead
  103. if collect_sql and not operation.reduces_to_sql:
  104. schema_editor.collected_sql.append("--")
  105. schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:")
  106. schema_editor.collected_sql.append("-- %s" % operation.describe())
  107. schema_editor.collected_sql.append("--")
  108. continue
  109. # If it's irreversible, error out
  110. if not operation.reversible:
  111. raise Migration.IrreversibleError("Operation %s in %s is not reversible" % (operation, self))
  112. new_state = project_state.clone()
  113. operation.state_forwards(self.app_label, new_state)
  114. to_run.append((operation, project_state, new_state))
  115. project_state = new_state
  116. # Now run them in reverse
  117. to_run.reverse()
  118. for operation, to_state, from_state in to_run:
  119. if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:
  120. # We're forcing a transaction on a non-transactional-DDL backend
  121. with atomic(schema_editor.connection.alias):
  122. operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
  123. else:
  124. # Normal behaviour
  125. operation.database_backwards(self.app_label, schema_editor, from_state, to_state)
  126. return project_state
  127. class SwappableTuple(tuple):
  128. """
  129. Subclass of tuple so Django can tell this was originally a swappable
  130. dependency when it reads the migration file.
  131. """
  132. def __new__(cls, value, setting):
  133. self = tuple.__new__(cls, value)
  134. self.setting = setting
  135. return self
  136. def swappable_dependency(value):
  137. """
  138. Turns a setting value into a dependency.
  139. """
  140. return SwappableTuple((value.split(".", 1)[0], "__first__"), value)