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.
 
 
 
 

339 lines
16 KiB

  1. from __future__ import unicode_literals
  2. import os
  3. import sys
  4. from importlib import import_module
  5. from django.apps import apps
  6. from django.conf import settings
  7. from django.db.migrations.graph import MigrationGraph
  8. from django.db.migrations.recorder import MigrationRecorder
  9. from django.utils import six
  10. from .exceptions import AmbiguityError, BadMigrationError, NodeNotFoundError
  11. MIGRATIONS_MODULE_NAME = 'migrations'
  12. class MigrationLoader(object):
  13. """
  14. Loads migration files from disk, and their status from the database.
  15. Migration files are expected to live in the "migrations" directory of
  16. an app. Their names are entirely unimportant from a code perspective,
  17. but will probably follow the 1234_name.py convention.
  18. On initialization, this class will scan those directories, and open and
  19. read the python files, looking for a class called Migration, which should
  20. inherit from django.db.migrations.Migration. See
  21. django.db.migrations.migration for what that looks like.
  22. Some migrations will be marked as "replacing" another set of migrations.
  23. These are loaded into a separate set of migrations away from the main ones.
  24. If all the migrations they replace are either unapplied or missing from
  25. disk, then they are injected into the main set, replacing the named migrations.
  26. Any dependency pointers to the replaced migrations are re-pointed to the
  27. new migration.
  28. This does mean that this class MUST also talk to the database as well as
  29. to disk, but this is probably fine. We're already not just operating
  30. in memory.
  31. """
  32. def __init__(self, connection, load=True, ignore_no_migrations=False):
  33. self.connection = connection
  34. self.disk_migrations = None
  35. self.applied_migrations = None
  36. self.ignore_no_migrations = ignore_no_migrations
  37. if load:
  38. self.build_graph()
  39. @classmethod
  40. def migrations_module(cls, app_label):
  41. if app_label in settings.MIGRATION_MODULES:
  42. return settings.MIGRATION_MODULES[app_label]
  43. else:
  44. app_package_name = apps.get_app_config(app_label).name
  45. return '%s.%s' % (app_package_name, MIGRATIONS_MODULE_NAME)
  46. def load_disk(self):
  47. """
  48. Loads the migrations from all INSTALLED_APPS from disk.
  49. """
  50. self.disk_migrations = {}
  51. self.unmigrated_apps = set()
  52. self.migrated_apps = set()
  53. for app_config in apps.get_app_configs():
  54. # Get the migrations module directory
  55. module_name = self.migrations_module(app_config.label)
  56. if module_name is None:
  57. self.unmigrated_apps.add(app_config.label)
  58. continue
  59. was_loaded = module_name in sys.modules
  60. try:
  61. module = import_module(module_name)
  62. except ImportError as e:
  63. # I hate doing this, but I don't want to squash other import errors.
  64. # Might be better to try a directory check directly.
  65. if "No module named" in str(e) and MIGRATIONS_MODULE_NAME in str(e):
  66. self.unmigrated_apps.add(app_config.label)
  67. continue
  68. raise
  69. else:
  70. # PY3 will happily import empty dirs as namespaces.
  71. if not hasattr(module, '__file__'):
  72. self.unmigrated_apps.add(app_config.label)
  73. continue
  74. # Module is not a package (e.g. migrations.py).
  75. if not hasattr(module, '__path__'):
  76. self.unmigrated_apps.add(app_config.label)
  77. continue
  78. # Force a reload if it's already loaded (tests need this)
  79. if was_loaded:
  80. six.moves.reload_module(module)
  81. self.migrated_apps.add(app_config.label)
  82. directory = os.path.dirname(module.__file__)
  83. # Scan for .py files
  84. migration_names = set()
  85. for name in os.listdir(directory):
  86. if name.endswith(".py"):
  87. import_name = name.rsplit(".", 1)[0]
  88. if import_name[0] not in "_.~":
  89. migration_names.add(import_name)
  90. # Load them
  91. for migration_name in migration_names:
  92. migration_module = import_module("%s.%s" % (module_name, migration_name))
  93. if not hasattr(migration_module, "Migration"):
  94. raise BadMigrationError(
  95. "Migration %s in app %s has no Migration class" % (migration_name, app_config.label)
  96. )
  97. self.disk_migrations[app_config.label, migration_name] = migration_module.Migration(
  98. migration_name,
  99. app_config.label,
  100. )
  101. def get_migration(self, app_label, name_prefix):
  102. "Gets the migration exactly named, or raises `graph.NodeNotFoundError`"
  103. return self.graph.nodes[app_label, name_prefix]
  104. def get_migration_by_prefix(self, app_label, name_prefix):
  105. "Returns the migration(s) which match the given app label and name _prefix_"
  106. # Do the search
  107. results = []
  108. for l, n in self.disk_migrations:
  109. if l == app_label and n.startswith(name_prefix):
  110. results.append((l, n))
  111. if len(results) > 1:
  112. raise AmbiguityError(
  113. "There is more than one migration for '%s' with the prefix '%s'" % (app_label, name_prefix)
  114. )
  115. elif len(results) == 0:
  116. raise KeyError("There no migrations for '%s' with the prefix '%s'" % (app_label, name_prefix))
  117. else:
  118. return self.disk_migrations[results[0]]
  119. def check_key(self, key, current_app):
  120. if (key[1] != "__first__" and key[1] != "__latest__") or key in self.graph:
  121. return key
  122. # Special-case __first__, which means "the first migration" for
  123. # migrated apps, and is ignored for unmigrated apps. It allows
  124. # makemigrations to declare dependencies on apps before they even have
  125. # migrations.
  126. if key[0] == current_app:
  127. # Ignore __first__ references to the same app (#22325)
  128. return
  129. if key[0] in self.unmigrated_apps:
  130. # This app isn't migrated, but something depends on it.
  131. # The models will get auto-added into the state, though
  132. # so we're fine.
  133. return
  134. if key[0] in self.migrated_apps:
  135. try:
  136. if key[1] == "__first__":
  137. return list(self.graph.root_nodes(key[0]))[0]
  138. else: # "__latest__"
  139. return list(self.graph.leaf_nodes(key[0]))[0]
  140. except IndexError:
  141. if self.ignore_no_migrations:
  142. return None
  143. else:
  144. raise ValueError("Dependency on app with no migrations: %s" % key[0])
  145. raise ValueError("Dependency on unknown app: %s" % key[0])
  146. def build_graph(self):
  147. """
  148. Builds a migration dependency graph using both the disk and database.
  149. You'll need to rebuild the graph if you apply migrations. This isn't
  150. usually a problem as generally migration stuff runs in a one-shot process.
  151. """
  152. # Load disk data
  153. self.load_disk()
  154. # Load database data
  155. if self.connection is None:
  156. self.applied_migrations = set()
  157. else:
  158. recorder = MigrationRecorder(self.connection)
  159. self.applied_migrations = recorder.applied_migrations()
  160. # Do a first pass to separate out replacing and non-replacing migrations
  161. normal = {}
  162. replacing = {}
  163. for key, migration in self.disk_migrations.items():
  164. if migration.replaces:
  165. replacing[key] = migration
  166. else:
  167. normal[key] = migration
  168. # Calculate reverse dependencies - i.e., for each migration, what depends on it?
  169. # This is just for dependency re-pointing when applying replacements,
  170. # so we ignore run_before here.
  171. reverse_dependencies = {}
  172. for key, migration in normal.items():
  173. for parent in migration.dependencies:
  174. reverse_dependencies.setdefault(parent, set()).add(key)
  175. # Remember the possible replacements to generate more meaningful error
  176. # messages
  177. reverse_replacements = {}
  178. for key, migration in replacing.items():
  179. for replaced in migration.replaces:
  180. reverse_replacements.setdefault(replaced, set()).add(key)
  181. # Carry out replacements if we can - that is, if all replaced migrations
  182. # are either unapplied or missing.
  183. for key, migration in replacing.items():
  184. # Ensure this replacement migration is not in applied_migrations
  185. self.applied_migrations.discard(key)
  186. # Do the check. We can replace if all our replace targets are
  187. # applied, or if all of them are unapplied.
  188. applied_statuses = [(target in self.applied_migrations) for target in migration.replaces]
  189. can_replace = all(applied_statuses) or (not any(applied_statuses))
  190. if not can_replace:
  191. continue
  192. # Alright, time to replace. Step through the replaced migrations
  193. # and remove, repointing dependencies if needs be.
  194. for replaced in migration.replaces:
  195. if replaced in normal:
  196. # We don't care if the replaced migration doesn't exist;
  197. # the usage pattern here is to delete things after a while.
  198. del normal[replaced]
  199. for child_key in reverse_dependencies.get(replaced, set()):
  200. if child_key in migration.replaces:
  201. continue
  202. # List of migrations whose dependency on `replaced` needs
  203. # to be updated to a dependency on `key`.
  204. to_update = []
  205. # Child key may itself be replaced, in which case it might
  206. # not be in `normal` anymore (depending on whether we've
  207. # processed its replacement yet). If it's present, we go
  208. # ahead and update it; it may be deleted later on if it is
  209. # replaced, but there's no harm in updating it regardless.
  210. if child_key in normal:
  211. to_update.append(normal[child_key])
  212. # If the child key is replaced, we update its replacement's
  213. # dependencies too, if necessary. (We don't know if this
  214. # replacement will actually take effect or not, but either
  215. # way it's OK to update the replacing migration).
  216. if child_key in reverse_replacements:
  217. for replaces_child_key in reverse_replacements[child_key]:
  218. if replaced in replacing[replaces_child_key].dependencies:
  219. to_update.append(replacing[replaces_child_key])
  220. # Actually perform the dependency update on all migrations
  221. # that require it.
  222. for migration_needing_update in to_update:
  223. migration_needing_update.dependencies.remove(replaced)
  224. migration_needing_update.dependencies.append(key)
  225. normal[key] = migration
  226. # Mark the replacement as applied if all its replaced ones are
  227. if all(applied_statuses):
  228. self.applied_migrations.add(key)
  229. # Store the replacement migrations for later checks
  230. self.replacements = replacing
  231. # Finally, make a graph and load everything into it
  232. self.graph = MigrationGraph()
  233. for key, migration in normal.items():
  234. self.graph.add_node(key, migration)
  235. def _reraise_missing_dependency(migration, missing, exc):
  236. """
  237. Checks if ``missing`` could have been replaced by any squash
  238. migration but wasn't because the the squash migration was partially
  239. applied before. In that case raise a more understandable exception.
  240. #23556
  241. """
  242. if missing in reverse_replacements:
  243. candidates = reverse_replacements.get(missing, set())
  244. is_replaced = any(candidate in self.graph.nodes for candidate in candidates)
  245. if not is_replaced:
  246. tries = ', '.join('%s.%s' % c for c in candidates)
  247. exc_value = NodeNotFoundError(
  248. "Migration {0} depends on nonexistent node ('{1}', '{2}'). "
  249. "Django tried to replace migration {1}.{2} with any of [{3}] "
  250. "but wasn't able to because some of the replaced migrations "
  251. "are already applied.".format(
  252. migration, missing[0], missing[1], tries
  253. ),
  254. missing)
  255. exc_value.__cause__ = exc
  256. six.reraise(NodeNotFoundError, exc_value, sys.exc_info()[2])
  257. raise exc
  258. # Add all internal dependencies first to ensure __first__ dependencies
  259. # find the correct root node.
  260. for key, migration in normal.items():
  261. for parent in migration.dependencies:
  262. if parent[0] != key[0] or parent[1] == '__first__':
  263. # Ignore __first__ references to the same app (#22325)
  264. continue
  265. try:
  266. self.graph.add_dependency(migration, key, parent)
  267. except NodeNotFoundError as e:
  268. # Since we added "key" to the nodes before this implies
  269. # "parent" is not in there. To make the raised exception
  270. # more understandable we check if parent could have been
  271. # replaced but hasn't (eg partially applied squashed
  272. # migration)
  273. _reraise_missing_dependency(migration, parent, e)
  274. for key, migration in normal.items():
  275. for parent in migration.dependencies:
  276. if parent[0] == key[0]:
  277. # Internal dependencies already added.
  278. continue
  279. parent = self.check_key(parent, key[0])
  280. if parent is not None:
  281. try:
  282. self.graph.add_dependency(migration, key, parent)
  283. except NodeNotFoundError as e:
  284. # Since we added "key" to the nodes before this implies
  285. # "parent" is not in there.
  286. _reraise_missing_dependency(migration, parent, e)
  287. for child in migration.run_before:
  288. child = self.check_key(child, key[0])
  289. if child is not None:
  290. try:
  291. self.graph.add_dependency(migration, child, key)
  292. except NodeNotFoundError as e:
  293. # Since we added "key" to the nodes before this implies
  294. # "child" is not in there.
  295. _reraise_missing_dependency(migration, child, e)
  296. def detect_conflicts(self):
  297. """
  298. Looks through the loaded graph and detects any conflicts - apps
  299. with more than one leaf migration. Returns a dict of the app labels
  300. that conflict with the migration names that conflict.
  301. """
  302. seen_apps = {}
  303. conflicting_apps = set()
  304. for app_label, migration_name in self.graph.leaf_nodes():
  305. if app_label in seen_apps:
  306. conflicting_apps.add(app_label)
  307. seen_apps.setdefault(app_label, set()).add(migration_name)
  308. return {app_label: seen_apps[app_label] for app_label in conflicting_apps}
  309. def project_state(self, nodes=None, at_end=True):
  310. """
  311. Returns a ProjectState object representing the most recent state
  312. that the migrations we loaded represent.
  313. See graph.make_state for the meaning of "nodes" and "at_end"
  314. """
  315. return self.graph.make_state(nodes=nodes, at_end=at_end, real_apps=list(self.unmigrated_apps))