示例#1
0
 def test_apply(self):
     """
     Tests marking migrations as applied/unapplied.
     """
     recorder = MigrationRecorder(connection)
     self.assertEqual(
         {(x, y)
          for (x, y) in recorder.applied_migrations() if x == "myapp"},
         set(),
     )
     recorder.record_applied("myapp", "0432_ponies")
     self.assertEqual(
         {(x, y)
          for (x, y) in recorder.applied_migrations() if x == "myapp"},
         {("myapp", "0432_ponies")},
     )
     # That should not affect records of another database
     recorder_other = MigrationRecorder(connections['other'])
     self.assertEqual(
         {(x, y)
          for (x, y) in recorder_other.applied_migrations()
          if x == "myapp"},
         set(),
     )
     recorder.record_unapplied("myapp", "0432_ponies")
     self.assertEqual(
         {(x, y)
          for (x, y) in recorder.applied_migrations() if x == "myapp"},
         set(),
     )
示例#2
0
 def check_consistent_history(self, connection):
     """
     Raise InconsistentMigrationHistory if any applied migrations have
     unapplied dependencies.
     """
     recorder = MigrationRecorder(connection)
     applied = recorder.applied_migrations()
     for migration in applied:
         # If the migration is unknown, skip it.
         if migration not in self.graph.nodes:
             continue
         for parent in self.graph.node_map[migration].parents:
             if parent not in applied:
                 # Skip unapplied squashed migrations that have all of their
                 # `replaces` applied.
                 if parent in self.replacements:
                     if all(m in applied
                            for m in self.replacements[parent].replaces):
                         continue
                 raise InconsistentMigrationHistory(
                     "Migration {}.{} is applied before its dependency "
                     "{}.{} on database '{}'.".format(
                         migration[0],
                         migration[1],
                         parent[0],
                         parent[1],
                         connection.alias,
                     ))
示例#3
0
    def test_migrate_marks_replacement_applied_even_if_it_did_nothing(self):
        """
        A new squash migration will be marked as applied even if all its
        replaced migrations were previously already applied (#24628).
        """
        recorder = MigrationRecorder(connection)
        # Record all replaced migrations as applied
        recorder.record_applied("migrations", "0001_initial")
        recorder.record_applied("migrations", "0002_second")
        executor = MigrationExecutor(connection)
        executor.migrate([("migrations", "0001_squashed_0002")])

        # Because 0001 and 0002 are both applied, even though this migrate run
        # didn't apply anything new, their squashed replacement should be
        # marked as applied.
        self.assertIn(
            ("migrations", "0001_squashed_0002"),
            recorder.applied_migrations(),
        )
示例#4
0
    def test_apply_all_replaced_marks_replacement_as_applied(self):
        """
        Applying all replaced migrations marks replacement as applied (#24628).
        """
        recorder = MigrationRecorder(connection)
        # Place the database in a state where the replaced migrations are
        # partially applied: 0001 is applied, 0002 is not.
        recorder.record_applied("migrations", "0001_initial")
        executor = MigrationExecutor(connection)
        # Use fake because we don't actually have the first migration
        # applied, so the second will fail. And there's no need to actually
        # create/modify tables here, we're just testing the
        # MigrationRecord, which works the same with or without fake.
        executor.migrate([("migrations", "0002_second")], fake=True)

        # Because we've now applied 0001 and 0002 both, their squashed
        # replacement should be marked as applied.
        self.assertIn(
            ("migrations", "0001_squashed_0002"),
            recorder.applied_migrations(),
        )
示例#5
0
 def build_graph(self):
     """
     Build a migration dependency graph using both the disk and database.
     You'll need to rebuild the graph if you apply migrations. This isn't
     usually a problem as generally migration stuff runs in a one-shot process.
     """
     # Load disk data
     self.load_disk()
     # Load database data
     if self.connection is None:
         self.applied_migrations = set()
     else:
         recorder = MigrationRecorder(self.connection)
         self.applied_migrations = recorder.applied_migrations()
     # To start, populate the migration graph with nodes for ALL migrations
     # and their dependencies. Also make note of replacing migrations at this step.
     self.graph = MigrationGraph()
     self.replacements = {}
     for key, migration in self.disk_migrations.items():
         self.graph.add_node(key, migration)
         # Internal (aka same-app) dependencies.
         self.add_internal_dependencies(key, migration)
         # Replacing migrations.
         if migration.replaces:
             self.replacements[key] = migration
     # Add external dependencies now that the internal ones have been resolved.
     for key, migration in self.disk_migrations.items():
         self.add_external_dependencies(key, migration)
     # Carry out replacements where possible.
     for key, migration in self.replacements.items():
         # Get applied status of each of this migration's replacement targets.
         applied_statuses = [(target in self.applied_migrations)
                             for target in migration.replaces]
         # Ensure the replacing migration is only marked as applied if all of
         # its replacement targets are.
         if all(applied_statuses):
             self.applied_migrations.add(key)
         else:
             self.applied_migrations.discard(key)
         # A replacing migration can be used if either all or none of its
         # replacement targets have been applied.
         if all(applied_statuses) or (not any(applied_statuses)):
             self.graph.remove_replaced_nodes(key, migration.replaces)
         else:
             # This replacing migration cannot be used because it is partially applied.
             # Remove it from the graph and remap dependencies to it (#25945).
             self.graph.remove_replacement_node(key, migration.replaces)
     # Ensure the graph is consistent.
     try:
         self.graph.validate_consistency()
     except NodeNotFoundError as exc:
         # Check if the missing node could have been replaced by any squash
         # migration but wasn't because the squash migration was partially
         # applied before. In that case raise a more understandable exception
         # (#23556).
         # Get reverse replacements.
         reverse_replacements = {}
         for key, migration in self.replacements.items():
             for replaced in migration.replaces:
                 reverse_replacements.setdefault(replaced, set()).add(key)
         # Try to reraise exception with more detail.
         if exc.node in reverse_replacements:
             candidates = reverse_replacements.get(exc.node, set())
             is_replaced = any(candidate in self.graph.nodes
                               for candidate in candidates)
             if not is_replaced:
                 tries = ', '.join('%s.%s' % c for c in candidates)
                 raise NodeNotFoundError(
                     "Migration {0} depends on nonexistent node ('{1}', '{2}'). "
                     "Django tried to replace migration {1}.{2} with any of [{3}] "
                     "but wasn't able to because some of the replaced migrations "
                     "are already applied.".format(exc.origin, exc.node[0],
                                                   exc.node[1], tries),
                     exc.node) from exc
         raise exc