Plain is headed towards 1.0! Subscribe for development updates →

   1import functools
   2import re
   3from graphlib import TopologicalSorter
   4from itertools import chain
   5
   6from plain import models
   7from plain.models.migrations import operations
   8from plain.models.migrations.migration import Migration
   9from plain.models.migrations.operations.models import AlterModelOptions
  10from plain.models.migrations.optimizer import MigrationOptimizer
  11from plain.models.migrations.questioner import MigrationQuestioner
  12from plain.models.migrations.utils import (
  13    COMPILED_REGEX_TYPE,
  14    RegexObject,
  15    resolve_relation,
  16)
  17from plain.runtime import settings
  18
  19
  20class MigrationAutodetector:
  21    """
  22    Take a pair of ProjectStates and compare them to see what the first would
  23    need doing to make it match the second (the second usually being the
  24    project's current state).
  25
  26    Note that this naturally operates on entire projects at a time,
  27    as it's likely that changes interact (for example, you can't
  28    add a ForeignKey without having a migration to add the table it
  29    depends on first). A user interface may offer single-app usage
  30    if it wishes, with the caveat that it may not always be possible.
  31    """
  32
  33    def __init__(self, from_state, to_state, questioner=None):
  34        self.from_state = from_state
  35        self.to_state = to_state
  36        self.questioner = questioner or MigrationQuestioner()
  37        self.existing_packages = {app for app, model in from_state.models}
  38
  39    def changes(
  40        self, graph, trim_to_packages=None, convert_packages=None, migration_name=None
  41    ):
  42        """
  43        Main entry point to produce a list of applicable changes.
  44        Take a graph to base names on and an optional set of packages
  45        to try and restrict to (restriction is not guaranteed)
  46        """
  47        changes = self._detect_changes(convert_packages, graph)
  48        changes = self.arrange_for_graph(changes, graph, migration_name)
  49        if trim_to_packages:
  50            changes = self._trim_to_packages(changes, trim_to_packages)
  51        return changes
  52
  53    def deep_deconstruct(self, obj):
  54        """
  55        Recursive deconstruction for a field and its arguments.
  56        Used for full comparison for rename/alter; sometimes a single-level
  57        deconstruction will not compare correctly.
  58        """
  59        if isinstance(obj, list):
  60            return [self.deep_deconstruct(value) for value in obj]
  61        elif isinstance(obj, tuple):
  62            return tuple(self.deep_deconstruct(value) for value in obj)
  63        elif isinstance(obj, dict):
  64            return {key: self.deep_deconstruct(value) for key, value in obj.items()}
  65        elif isinstance(obj, functools.partial):
  66            return (
  67                obj.func,
  68                self.deep_deconstruct(obj.args),
  69                self.deep_deconstruct(obj.keywords),
  70            )
  71        elif isinstance(obj, COMPILED_REGEX_TYPE):
  72            return RegexObject(obj)
  73        elif isinstance(obj, type):
  74            # If this is a type that implements 'deconstruct' as an instance method,
  75            # avoid treating this as being deconstructible itself - see #22951
  76            return obj
  77        elif hasattr(obj, "deconstruct"):
  78            deconstructed = obj.deconstruct()
  79            if isinstance(obj, models.Field):
  80                # we have a field which also returns a name
  81                deconstructed = deconstructed[1:]
  82            path, args, kwargs = deconstructed
  83            return (
  84                path,
  85                [self.deep_deconstruct(value) for value in args],
  86                {key: self.deep_deconstruct(value) for key, value in kwargs.items()},
  87            )
  88        else:
  89            return obj
  90
  91    def only_relation_agnostic_fields(self, fields):
  92        """
  93        Return a definition of the fields that ignores field names and
  94        what related fields actually relate to. Used for detecting renames (as
  95        the related fields change during renames).
  96        """
  97        fields_def = []
  98        for name, field in sorted(fields.items()):
  99            deconstruction = self.deep_deconstruct(field)
 100            if field.remote_field and field.remote_field.model:
 101                deconstruction[2].pop("to", None)
 102            fields_def.append(deconstruction)
 103        return fields_def
 104
 105    def _detect_changes(self, convert_packages=None, graph=None):
 106        """
 107        Return a dict of migration plans which will achieve the
 108        change from from_state to to_state. The dict has app labels
 109        as keys and a list of migrations as values.
 110
 111        The resulting migrations aren't specially named, but the names
 112        do matter for dependencies inside the set.
 113
 114        convert_packages is the list of packages to convert to use migrations
 115        (i.e. to make initial migrations for, in the usual case)
 116
 117        graph is an optional argument that, if provided, can help improve
 118        dependency generation and avoid potential circular dependencies.
 119        """
 120        # The first phase is generating all the operations for each app
 121        # and gathering them into a big per-app list.
 122        # Then go through that list, order it, and split into migrations to
 123        # resolve dependencies caused by M2Ms and FKs.
 124        self.generated_operations = {}
 125        self.altered_indexes = {}
 126        self.altered_constraints = {}
 127        self.renamed_fields = {}
 128
 129        # Prepare some old/new state and model lists, ignoring unmigrated packages.
 130        self.old_model_keys = set()
 131        self.old_unmanaged_keys = set()
 132        self.new_model_keys = set()
 133        self.new_unmanaged_keys = set()
 134        for (package_label, model_name), model_state in self.from_state.models.items():
 135            if not model_state.options.get("managed", True):
 136                self.old_unmanaged_keys.add((package_label, model_name))
 137            elif package_label not in self.from_state.real_packages:
 138                self.old_model_keys.add((package_label, model_name))
 139
 140        for (package_label, model_name), model_state in self.to_state.models.items():
 141            if not model_state.options.get("managed", True):
 142                self.new_unmanaged_keys.add((package_label, model_name))
 143            elif package_label not in self.from_state.real_packages or (
 144                convert_packages and package_label in convert_packages
 145            ):
 146                self.new_model_keys.add((package_label, model_name))
 147
 148        self.from_state.resolve_fields_and_relations()
 149        self.to_state.resolve_fields_and_relations()
 150
 151        # Renames have to come first
 152        self.generate_renamed_models()
 153
 154        # Prepare lists of fields and generate through model map
 155        self._prepare_field_lists()
 156        self._generate_through_model_map()
 157
 158        # Generate non-rename model operations
 159        self.generate_deleted_models()
 160        self.generate_created_models()
 161        self.generate_altered_options()
 162        self.generate_altered_managers()
 163        self.generate_altered_db_table_comment()
 164
 165        # Create the renamed fields and store them in self.renamed_fields.
 166        # They are used by create_altered_indexes(), generate_altered_fields(),
 167        # generate_removed_altered_index(), and
 168        # generate_altered_index().
 169        self.create_renamed_fields()
 170        # Create the altered indexes and store them in self.altered_indexes.
 171        # This avoids the same computation in generate_removed_indexes()
 172        # and generate_added_indexes().
 173        self.create_altered_indexes()
 174        self.create_altered_constraints()
 175        # Generate index removal operations before field is removed
 176        self.generate_removed_constraints()
 177        self.generate_removed_indexes()
 178        # Generate field renaming operations.
 179        self.generate_renamed_fields()
 180        self.generate_renamed_indexes()
 181        # Generate field operations.
 182        self.generate_removed_fields()
 183        self.generate_added_fields()
 184        self.generate_altered_fields()
 185        self.generate_altered_order_with_respect_to()
 186        self.generate_added_indexes()
 187        self.generate_added_constraints()
 188        self.generate_altered_db_table()
 189
 190        self._sort_migrations()
 191        self._build_migration_list(graph)
 192        self._optimize_migrations()
 193
 194        return self.migrations
 195
 196    def _prepare_field_lists(self):
 197        """
 198        Prepare field lists and a list of the fields that used through models
 199        in the old state so dependencies can be made from the through model
 200        deletion to the field that uses it.
 201        """
 202        self.kept_model_keys = self.old_model_keys & self.new_model_keys
 203        self.kept_unmanaged_keys = self.old_unmanaged_keys & self.new_unmanaged_keys
 204        self.through_users = {}
 205        self.old_field_keys = {
 206            (package_label, model_name, field_name)
 207            for package_label, model_name in self.kept_model_keys
 208            for field_name in self.from_state.models[
 209                package_label,
 210                self.renamed_models.get((package_label, model_name), model_name),
 211            ].fields
 212        }
 213        self.new_field_keys = {
 214            (package_label, model_name, field_name)
 215            for package_label, model_name in self.kept_model_keys
 216            for field_name in self.to_state.models[package_label, model_name].fields
 217        }
 218
 219    def _generate_through_model_map(self):
 220        """Through model map generation."""
 221        for package_label, model_name in sorted(self.old_model_keys):
 222            old_model_name = self.renamed_models.get(
 223                (package_label, model_name), model_name
 224            )
 225            old_model_state = self.from_state.models[package_label, old_model_name]
 226            for field_name, field in old_model_state.fields.items():
 227                if hasattr(field, "remote_field") and getattr(
 228                    field.remote_field, "through", None
 229                ):
 230                    through_key = resolve_relation(
 231                        field.remote_field.through, package_label, model_name
 232                    )
 233                    self.through_users[through_key] = (
 234                        package_label,
 235                        old_model_name,
 236                        field_name,
 237                    )
 238
 239    @staticmethod
 240    def _resolve_dependency(dependency):
 241        """
 242        Return the resolved dependency and a boolean denoting whether or not
 243        it was swappable.
 244        """
 245        if dependency[0] != "__setting__":
 246            return dependency, False
 247        resolved_package_label, resolved_object_name = getattr(
 248            settings, dependency[1]
 249        ).split(".")
 250        return (resolved_package_label, resolved_object_name.lower()) + dependency[
 251            2:
 252        ], True
 253
 254    def _build_migration_list(self, graph=None):
 255        """
 256        Chop the lists of operations up into migrations with dependencies on
 257        each other. Do this by going through an app's list of operations until
 258        one is found that has an outgoing dependency that isn't in another
 259        app's migration yet (hasn't been chopped off its list). Then chop off
 260        the operations before it into a migration and move onto the next app.
 261        If the loops completes without doing anything, there's a circular
 262        dependency (which _should_ be impossible as the operations are
 263        all split at this point so they can't depend and be depended on).
 264        """
 265        self.migrations = {}
 266        num_ops = sum(len(x) for x in self.generated_operations.values())
 267        chop_mode = False
 268        while num_ops:
 269            # On every iteration, we step through all the packages and see if there
 270            # is a completed set of operations.
 271            # If we find that a subset of the operations are complete we can
 272            # try to chop it off from the rest and continue, but we only
 273            # do this if we've already been through the list once before
 274            # without any chopping and nothing has changed.
 275            for package_label in sorted(self.generated_operations):
 276                chopped = []
 277                dependencies = set()
 278                for operation in list(self.generated_operations[package_label]):
 279                    deps_satisfied = True
 280                    operation_dependencies = set()
 281                    for dep in operation._auto_deps:
 282                        # Temporarily resolve the swappable dependency to
 283                        # prevent circular references. While keeping the
 284                        # dependency checks on the resolved model, add the
 285                        # swappable dependencies.
 286                        original_dep = dep
 287                        dep, is_swappable_dep = self._resolve_dependency(dep)
 288                        if dep[0] != package_label:
 289                            # External app dependency. See if it's not yet
 290                            # satisfied.
 291                            for other_operation in self.generated_operations.get(
 292                                dep[0], []
 293                            ):
 294                                if self.check_dependency(other_operation, dep):
 295                                    deps_satisfied = False
 296                                    break
 297                            if not deps_satisfied:
 298                                break
 299                            else:
 300                                if is_swappable_dep:
 301                                    operation_dependencies.add(
 302                                        (original_dep[0], original_dep[1])
 303                                    )
 304                                elif dep[0] in self.migrations:
 305                                    operation_dependencies.add(
 306                                        (dep[0], self.migrations[dep[0]][-1].name)
 307                                    )
 308                                else:
 309                                    # If we can't find the other app, we add a
 310                                    # first/last dependency, but only if we've
 311                                    # already been through once and checked
 312                                    # everything.
 313                                    if chop_mode:
 314                                        # If the app already exists, we add a
 315                                        # dependency on the last migration, as
 316                                        # we don't know which migration
 317                                        # contains the target field. If it's
 318                                        # not yet migrated or has no
 319                                        # migrations, we use __first__.
 320                                        if graph and graph.leaf_nodes(dep[0]):
 321                                            operation_dependencies.add(
 322                                                graph.leaf_nodes(dep[0])[0]
 323                                            )
 324                                        else:
 325                                            operation_dependencies.add(
 326                                                (dep[0], "__first__")
 327                                            )
 328                                    else:
 329                                        deps_satisfied = False
 330                    if deps_satisfied:
 331                        chopped.append(operation)
 332                        dependencies.update(operation_dependencies)
 333                        del self.generated_operations[package_label][0]
 334                    else:
 335                        break
 336                # Make a migration! Well, only if there's stuff to put in it
 337                if dependencies or chopped:
 338                    if not self.generated_operations[package_label] or chop_mode:
 339                        subclass = type(
 340                            "Migration",
 341                            (Migration,),
 342                            {"operations": [], "dependencies": []},
 343                        )
 344                        instance = subclass(
 345                            "auto_%i"
 346                            % (len(self.migrations.get(package_label, [])) + 1),
 347                            package_label,
 348                        )
 349                        instance.dependencies = list(dependencies)
 350                        instance.operations = chopped
 351                        instance.initial = package_label not in self.existing_packages
 352                        self.migrations.setdefault(package_label, []).append(instance)
 353                        chop_mode = False
 354                    else:
 355                        self.generated_operations[package_label] = (
 356                            chopped + self.generated_operations[package_label]
 357                        )
 358            new_num_ops = sum(len(x) for x in self.generated_operations.values())
 359            if new_num_ops == num_ops:
 360                if not chop_mode:
 361                    chop_mode = True
 362                else:
 363                    raise ValueError(
 364                        "Cannot resolve operation dependencies: %r"
 365                        % self.generated_operations
 366                    )
 367            num_ops = new_num_ops
 368
 369    def _sort_migrations(self):
 370        """
 371        Reorder to make things possible. Reordering may be needed so FKs work
 372        nicely inside the same app.
 373        """
 374        for package_label, ops in sorted(self.generated_operations.items()):
 375            ts = TopologicalSorter()
 376            for op in ops:
 377                ts.add(op)
 378                for dep in op._auto_deps:
 379                    # Resolve intra-app dependencies to handle circular
 380                    # references involving a swappable model.
 381                    dep = self._resolve_dependency(dep)[0]
 382                    if dep[0] != package_label:
 383                        continue
 384                    ts.add(op, *(x for x in ops if self.check_dependency(x, dep)))
 385            self.generated_operations[package_label] = list(ts.static_order())
 386
 387    def _optimize_migrations(self):
 388        # Add in internal dependencies among the migrations
 389        for package_label, migrations in self.migrations.items():
 390            for m1, m2 in zip(migrations, migrations[1:]):
 391                m2.dependencies.append((package_label, m1.name))
 392
 393        # De-dupe dependencies
 394        for migrations in self.migrations.values():
 395            for migration in migrations:
 396                migration.dependencies = list(set(migration.dependencies))
 397
 398        # Optimize migrations
 399        for package_label, migrations in self.migrations.items():
 400            for migration in migrations:
 401                migration.operations = MigrationOptimizer().optimize(
 402                    migration.operations, package_label
 403                )
 404
 405    def check_dependency(self, operation, dependency):
 406        """
 407        Return True if the given operation depends on the given dependency,
 408        False otherwise.
 409        """
 410        # Created model
 411        if dependency[2] is None and dependency[3] is True:
 412            return (
 413                isinstance(operation, operations.CreateModel)
 414                and operation.name_lower == dependency[1].lower()
 415            )
 416        # Created field
 417        elif dependency[2] is not None and dependency[3] is True:
 418            return (
 419                isinstance(operation, operations.CreateModel)
 420                and operation.name_lower == dependency[1].lower()
 421                and any(dependency[2] == x for x, y in operation.fields)
 422            ) or (
 423                isinstance(operation, operations.AddField)
 424                and operation.model_name_lower == dependency[1].lower()
 425                and operation.name_lower == dependency[2].lower()
 426            )
 427        # Removed field
 428        elif dependency[2] is not None and dependency[3] is False:
 429            return (
 430                isinstance(operation, operations.RemoveField)
 431                and operation.model_name_lower == dependency[1].lower()
 432                and operation.name_lower == dependency[2].lower()
 433            )
 434        # Removed model
 435        elif dependency[2] is None and dependency[3] is False:
 436            return (
 437                isinstance(operation, operations.DeleteModel)
 438                and operation.name_lower == dependency[1].lower()
 439            )
 440        # Field being altered
 441        elif dependency[2] is not None and dependency[3] == "alter":
 442            return (
 443                isinstance(operation, operations.AlterField)
 444                and operation.model_name_lower == dependency[1].lower()
 445                and operation.name_lower == dependency[2].lower()
 446            )
 447        # order_with_respect_to being unset for a field
 448        elif dependency[2] is not None and dependency[3] == "order_wrt_unset":
 449            return (
 450                isinstance(operation, operations.AlterOrderWithRespectTo)
 451                and operation.name_lower == dependency[1].lower()
 452                and (operation.order_with_respect_to or "").lower()
 453                != dependency[2].lower()
 454            )
 455        # Unknown dependency. Raise an error.
 456        else:
 457            raise ValueError(f"Can't handle dependency {dependency!r}")
 458
 459    def add_operation(
 460        self, package_label, operation, dependencies=None, beginning=False
 461    ):
 462        # Dependencies are
 463        # (package_label, model_name, field_name, create/delete as True/False)
 464        operation._auto_deps = dependencies or []
 465        if beginning:
 466            self.generated_operations.setdefault(package_label, []).insert(0, operation)
 467        else:
 468            self.generated_operations.setdefault(package_label, []).append(operation)
 469
 470    def swappable_first_key(self, item):
 471        """
 472        Place potential swappable models first in lists of created models (only
 473        real way to solve #22783).
 474        """
 475        try:
 476            model_state = self.to_state.models[item]
 477            base_names = {
 478                base if isinstance(base, str) else base.__name__
 479                for base in model_state.bases
 480            }
 481            string_version = f"{item[0]}.{item[1]}"
 482            if (
 483                model_state.options.get("swappable")
 484                or "BaseUser" in base_names
 485                or "AbstractBaseUser" in base_names
 486                or settings.AUTH_USER_MODEL.lower() == string_version.lower()
 487            ):
 488                return ("___" + item[0], "___" + item[1])
 489        except LookupError:
 490            pass
 491        return item
 492
 493    def generate_renamed_models(self):
 494        """
 495        Find any renamed models, generate the operations for them, and remove
 496        the old entry from the model lists. Must be run before other
 497        model-level generation.
 498        """
 499        self.renamed_models = {}
 500        self.renamed_models_rel = {}
 501        added_models = self.new_model_keys - self.old_model_keys
 502        for package_label, model_name in sorted(added_models):
 503            model_state = self.to_state.models[package_label, model_name]
 504            model_fields_def = self.only_relation_agnostic_fields(model_state.fields)
 505
 506            removed_models = self.old_model_keys - self.new_model_keys
 507            for rem_package_label, rem_model_name in removed_models:
 508                if rem_package_label == package_label:
 509                    rem_model_state = self.from_state.models[
 510                        rem_package_label, rem_model_name
 511                    ]
 512                    rem_model_fields_def = self.only_relation_agnostic_fields(
 513                        rem_model_state.fields
 514                    )
 515                    if model_fields_def == rem_model_fields_def:
 516                        if self.questioner.ask_rename_model(
 517                            rem_model_state, model_state
 518                        ):
 519                            dependencies = []
 520                            fields = list(model_state.fields.values()) + [
 521                                field.remote_field
 522                                for relations in self.to_state.relations[
 523                                    package_label, model_name
 524                                ].values()
 525                                for field in relations.values()
 526                            ]
 527                            for field in fields:
 528                                if field.is_relation:
 529                                    dependencies.extend(
 530                                        self._get_dependencies_for_foreign_key(
 531                                            package_label,
 532                                            model_name,
 533                                            field,
 534                                            self.to_state,
 535                                        )
 536                                    )
 537                            self.add_operation(
 538                                package_label,
 539                                operations.RenameModel(
 540                                    old_name=rem_model_state.name,
 541                                    new_name=model_state.name,
 542                                ),
 543                                dependencies=dependencies,
 544                            )
 545                            self.renamed_models[
 546                                package_label, model_name
 547                            ] = rem_model_name
 548                            renamed_models_rel_key = "{}.{}".format(
 549                                rem_model_state.package_label,
 550                                rem_model_state.name_lower,
 551                            )
 552                            self.renamed_models_rel[
 553                                renamed_models_rel_key
 554                            ] = f"{model_state.package_label}.{model_state.name_lower}"
 555                            self.old_model_keys.remove(
 556                                (rem_package_label, rem_model_name)
 557                            )
 558                            self.old_model_keys.add((package_label, model_name))
 559                            break
 560
 561    def generate_created_models(self):
 562        """
 563        Find all new models (both managed and unmanaged) and make create
 564        operations for them as well as separate operations to create any
 565        foreign key or M2M relationships (these are optimized later, if
 566        possible).
 567
 568        Defer any model options that refer to collections of fields that might
 569        be deferred.
 570        """
 571        old_keys = self.old_model_keys | self.old_unmanaged_keys
 572        added_models = self.new_model_keys - old_keys
 573        added_unmanaged_models = self.new_unmanaged_keys - old_keys
 574        all_added_models = chain(
 575            sorted(added_models, key=self.swappable_first_key, reverse=True),
 576            sorted(added_unmanaged_models, key=self.swappable_first_key, reverse=True),
 577        )
 578        for package_label, model_name in all_added_models:
 579            model_state = self.to_state.models[package_label, model_name]
 580            # Gather related fields
 581            related_fields = {}
 582            primary_key_rel = None
 583            for field_name, field in model_state.fields.items():
 584                if field.remote_field:
 585                    if field.remote_field.model:
 586                        if field.primary_key:
 587                            primary_key_rel = field.remote_field.model
 588                        elif not field.remote_field.parent_link:
 589                            related_fields[field_name] = field
 590                    if getattr(field.remote_field, "through", None):
 591                        related_fields[field_name] = field
 592
 593            # Are there indexes to defer?
 594            indexes = model_state.options.pop("indexes")
 595            constraints = model_state.options.pop("constraints")
 596            order_with_respect_to = model_state.options.pop(
 597                "order_with_respect_to", None
 598            )
 599            # Depend on the deletion of any possible proxy version of us
 600            dependencies = [
 601                (package_label, model_name, None, False),
 602            ]
 603            # Depend on all bases
 604            for base in model_state.bases:
 605                if isinstance(base, str) and "." in base:
 606                    base_package_label, base_name = base.split(".", 1)
 607                    dependencies.append((base_package_label, base_name, None, True))
 608                    # Depend on the removal of base fields if the new model has
 609                    # a field with the same name.
 610                    old_base_model_state = self.from_state.models.get(
 611                        (base_package_label, base_name)
 612                    )
 613                    new_base_model_state = self.to_state.models.get(
 614                        (base_package_label, base_name)
 615                    )
 616                    if old_base_model_state and new_base_model_state:
 617                        removed_base_fields = (
 618                            set(old_base_model_state.fields)
 619                            .difference(
 620                                new_base_model_state.fields,
 621                            )
 622                            .intersection(model_state.fields)
 623                        )
 624                        for removed_base_field in removed_base_fields:
 625                            dependencies.append(
 626                                (
 627                                    base_package_label,
 628                                    base_name,
 629                                    removed_base_field,
 630                                    False,
 631                                )
 632                            )
 633            # Depend on the other end of the primary key if it's a relation
 634            if primary_key_rel:
 635                dependencies.append(
 636                    resolve_relation(
 637                        primary_key_rel,
 638                        package_label,
 639                        model_name,
 640                    )
 641                    + (None, True)
 642                )
 643            # Generate creation operation
 644            self.add_operation(
 645                package_label,
 646                operations.CreateModel(
 647                    name=model_state.name,
 648                    fields=[
 649                        d
 650                        for d in model_state.fields.items()
 651                        if d[0] not in related_fields
 652                    ],
 653                    options=model_state.options,
 654                    bases=model_state.bases,
 655                    managers=model_state.managers,
 656                ),
 657                dependencies=dependencies,
 658                beginning=True,
 659            )
 660
 661            # Don't add operations which modify the database for unmanaged models
 662            if not model_state.options.get("managed", True):
 663                continue
 664
 665            # Generate operations for each related field
 666            for name, field in sorted(related_fields.items()):
 667                dependencies = self._get_dependencies_for_foreign_key(
 668                    package_label,
 669                    model_name,
 670                    field,
 671                    self.to_state,
 672                )
 673                # Depend on our own model being created
 674                dependencies.append((package_label, model_name, None, True))
 675                # Make operation
 676                self.add_operation(
 677                    package_label,
 678                    operations.AddField(
 679                        model_name=model_name,
 680                        name=name,
 681                        field=field,
 682                    ),
 683                    dependencies=list(set(dependencies)),
 684                )
 685            # Generate other opns
 686            if order_with_respect_to:
 687                self.add_operation(
 688                    package_label,
 689                    operations.AlterOrderWithRespectTo(
 690                        name=model_name,
 691                        order_with_respect_to=order_with_respect_to,
 692                    ),
 693                    dependencies=[
 694                        (package_label, model_name, order_with_respect_to, True),
 695                        (package_label, model_name, None, True),
 696                    ],
 697                )
 698            related_dependencies = [
 699                (package_label, model_name, name, True)
 700                for name in sorted(related_fields)
 701            ]
 702            related_dependencies.append((package_label, model_name, None, True))
 703            for index in indexes:
 704                self.add_operation(
 705                    package_label,
 706                    operations.AddIndex(
 707                        model_name=model_name,
 708                        index=index,
 709                    ),
 710                    dependencies=related_dependencies,
 711                )
 712            for constraint in constraints:
 713                self.add_operation(
 714                    package_label,
 715                    operations.AddConstraint(
 716                        model_name=model_name,
 717                        constraint=constraint,
 718                    ),
 719                    dependencies=related_dependencies,
 720                )
 721
 722    def generate_deleted_models(self):
 723        """
 724        Find all deleted models (managed and unmanaged) and make delete
 725        operations for them as well as separate operations to delete any
 726        foreign key or M2M relationships (these are optimized later, if
 727        possible).
 728
 729        Also bring forward removal of any model options that refer to
 730        collections of fields - the inverse of generate_created_models().
 731        """
 732        new_keys = self.new_model_keys | self.new_unmanaged_keys
 733        deleted_models = self.old_model_keys - new_keys
 734        deleted_unmanaged_models = self.old_unmanaged_keys - new_keys
 735        all_deleted_models = chain(
 736            sorted(deleted_models), sorted(deleted_unmanaged_models)
 737        )
 738        for package_label, model_name in all_deleted_models:
 739            model_state = self.from_state.models[package_label, model_name]
 740            # Gather related fields
 741            related_fields = {}
 742            for field_name, field in model_state.fields.items():
 743                if field.remote_field:
 744                    if field.remote_field.model:
 745                        related_fields[field_name] = field
 746                    if getattr(field.remote_field, "through", None):
 747                        related_fields[field_name] = field
 748
 749            # Then remove each related field
 750            for name in sorted(related_fields):
 751                self.add_operation(
 752                    package_label,
 753                    operations.RemoveField(
 754                        model_name=model_name,
 755                        name=name,
 756                    ),
 757                )
 758            # Finally, remove the model.
 759            # This depends on both the removal/alteration of all incoming fields
 760            # and the removal of all its own related fields, and if it's
 761            # a through model the field that references it.
 762            dependencies = []
 763            relations = self.from_state.relations
 764            for (
 765                related_object_package_label,
 766                object_name,
 767            ), relation_related_fields in relations[package_label, model_name].items():
 768                for field_name, field in relation_related_fields.items():
 769                    dependencies.append(
 770                        (related_object_package_label, object_name, field_name, False),
 771                    )
 772                    if not field.many_to_many:
 773                        dependencies.append(
 774                            (
 775                                related_object_package_label,
 776                                object_name,
 777                                field_name,
 778                                "alter",
 779                            ),
 780                        )
 781
 782            for name in sorted(related_fields):
 783                dependencies.append((package_label, model_name, name, False))
 784            # We're referenced in another field's through=
 785            through_user = self.through_users.get(
 786                (package_label, model_state.name_lower)
 787            )
 788            if through_user:
 789                dependencies.append(
 790                    (through_user[0], through_user[1], through_user[2], False)
 791                )
 792            # Finally, make the operation, deduping any dependencies
 793            self.add_operation(
 794                package_label,
 795                operations.DeleteModel(
 796                    name=model_state.name,
 797                ),
 798                dependencies=list(set(dependencies)),
 799            )
 800
 801    def create_renamed_fields(self):
 802        """Work out renamed fields."""
 803        self.renamed_operations = []
 804        old_field_keys = self.old_field_keys.copy()
 805        for package_label, model_name, field_name in sorted(
 806            self.new_field_keys - old_field_keys
 807        ):
 808            old_model_name = self.renamed_models.get(
 809                (package_label, model_name), model_name
 810            )
 811            old_model_state = self.from_state.models[package_label, old_model_name]
 812            new_model_state = self.to_state.models[package_label, model_name]
 813            field = new_model_state.get_field(field_name)
 814            # Scan to see if this is actually a rename!
 815            field_dec = self.deep_deconstruct(field)
 816            for rem_package_label, rem_model_name, rem_field_name in sorted(
 817                old_field_keys - self.new_field_keys
 818            ):
 819                if rem_package_label == package_label and rem_model_name == model_name:
 820                    old_field = old_model_state.get_field(rem_field_name)
 821                    old_field_dec = self.deep_deconstruct(old_field)
 822                    if (
 823                        field.remote_field
 824                        and field.remote_field.model
 825                        and "to" in old_field_dec[2]
 826                    ):
 827                        old_rel_to = old_field_dec[2]["to"]
 828                        if old_rel_to in self.renamed_models_rel:
 829                            old_field_dec[2]["to"] = self.renamed_models_rel[old_rel_to]
 830                    old_field.set_attributes_from_name(rem_field_name)
 831                    old_db_column = old_field.get_attname_column()[1]
 832                    if old_field_dec == field_dec or (
 833                        # Was the field renamed and db_column equal to the
 834                        # old field's column added?
 835                        old_field_dec[0:2] == field_dec[0:2]
 836                        and dict(old_field_dec[2], db_column=old_db_column)
 837                        == field_dec[2]
 838                    ):
 839                        if self.questioner.ask_rename(
 840                            model_name, rem_field_name, field_name, field
 841                        ):
 842                            self.renamed_operations.append(
 843                                (
 844                                    rem_package_label,
 845                                    rem_model_name,
 846                                    old_field.db_column,
 847                                    rem_field_name,
 848                                    package_label,
 849                                    model_name,
 850                                    field,
 851                                    field_name,
 852                                )
 853                            )
 854                            old_field_keys.remove(
 855                                (rem_package_label, rem_model_name, rem_field_name)
 856                            )
 857                            old_field_keys.add((package_label, model_name, field_name))
 858                            self.renamed_fields[
 859                                package_label, model_name, field_name
 860                            ] = rem_field_name
 861                            break
 862
 863    def generate_renamed_fields(self):
 864        """Generate RenameField operations."""
 865        for (
 866            rem_package_label,
 867            rem_model_name,
 868            rem_db_column,
 869            rem_field_name,
 870            package_label,
 871            model_name,
 872            field,
 873            field_name,
 874        ) in self.renamed_operations:
 875            # A db_column mismatch requires a prior noop AlterField for the
 876            # subsequent RenameField to be a noop on attempts at preserving the
 877            # old name.
 878            if rem_db_column != field.db_column:
 879                altered_field = field.clone()
 880                altered_field.name = rem_field_name
 881                self.add_operation(
 882                    package_label,
 883                    operations.AlterField(
 884                        model_name=model_name,
 885                        name=rem_field_name,
 886                        field=altered_field,
 887                    ),
 888                )
 889            self.add_operation(
 890                package_label,
 891                operations.RenameField(
 892                    model_name=model_name,
 893                    old_name=rem_field_name,
 894                    new_name=field_name,
 895                ),
 896            )
 897            self.old_field_keys.remove(
 898                (rem_package_label, rem_model_name, rem_field_name)
 899            )
 900            self.old_field_keys.add((package_label, model_name, field_name))
 901
 902    def generate_added_fields(self):
 903        """Make AddField operations."""
 904        for package_label, model_name, field_name in sorted(
 905            self.new_field_keys - self.old_field_keys
 906        ):
 907            self._generate_added_field(package_label, model_name, field_name)
 908
 909    def _generate_added_field(self, package_label, model_name, field_name):
 910        field = self.to_state.models[package_label, model_name].get_field(field_name)
 911        # Adding a field always depends at least on its removal.
 912        dependencies = [(package_label, model_name, field_name, False)]
 913        # Fields that are foreignkeys/m2ms depend on stuff.
 914        if field.remote_field and field.remote_field.model:
 915            dependencies.extend(
 916                self._get_dependencies_for_foreign_key(
 917                    package_label,
 918                    model_name,
 919                    field,
 920                    self.to_state,
 921                )
 922            )
 923        # You can't just add NOT NULL fields with no default or fields
 924        # which don't allow empty strings as default.
 925        time_fields = (models.DateField, models.DateTimeField, models.TimeField)
 926        preserve_default = (
 927            field.null
 928            or field.has_default()
 929            or field.many_to_many
 930            or (field.blank and field.empty_strings_allowed)
 931            or (isinstance(field, time_fields) and field.auto_now)
 932        )
 933        if not preserve_default:
 934            field = field.clone()
 935            if isinstance(field, time_fields) and field.auto_now_add:
 936                field.default = self.questioner.ask_auto_now_add_addition(
 937                    field_name, model_name
 938                )
 939            else:
 940                field.default = self.questioner.ask_not_null_addition(
 941                    field_name, model_name
 942                )
 943        if (
 944            field.unique
 945            and field.default is not models.NOT_PROVIDED
 946            and callable(field.default)
 947        ):
 948            self.questioner.ask_unique_callable_default_addition(field_name, model_name)
 949        self.add_operation(
 950            package_label,
 951            operations.AddField(
 952                model_name=model_name,
 953                name=field_name,
 954                field=field,
 955                preserve_default=preserve_default,
 956            ),
 957            dependencies=dependencies,
 958        )
 959
 960    def generate_removed_fields(self):
 961        """Make RemoveField operations."""
 962        for package_label, model_name, field_name in sorted(
 963            self.old_field_keys - self.new_field_keys
 964        ):
 965            self._generate_removed_field(package_label, model_name, field_name)
 966
 967    def _generate_removed_field(self, package_label, model_name, field_name):
 968        self.add_operation(
 969            package_label,
 970            operations.RemoveField(
 971                model_name=model_name,
 972                name=field_name,
 973            ),
 974            # We might need to depend on the removal of an
 975            # order_with_respect_to or index operation;
 976            # this is safely ignored if there isn't one
 977            dependencies=[
 978                (package_label, model_name, field_name, "order_wrt_unset"),
 979            ],
 980        )
 981
 982    def generate_altered_fields(self):
 983        """
 984        Make AlterField operations, or possibly RemovedField/AddField if alter
 985        isn't possible.
 986        """
 987        for package_label, model_name, field_name in sorted(
 988            self.old_field_keys & self.new_field_keys
 989        ):
 990            # Did the field change?
 991            old_model_name = self.renamed_models.get(
 992                (package_label, model_name), model_name
 993            )
 994            old_field_name = self.renamed_fields.get(
 995                (package_label, model_name, field_name), field_name
 996            )
 997            old_field = self.from_state.models[package_label, old_model_name].get_field(
 998                old_field_name
 999            )
1000            new_field = self.to_state.models[package_label, model_name].get_field(
1001                field_name
1002            )
1003            dependencies = []
1004            # Implement any model renames on relations; these are handled by RenameModel
1005            # so we need to exclude them from the comparison
1006            if hasattr(new_field, "remote_field") and getattr(
1007                new_field.remote_field, "model", None
1008            ):
1009                rename_key = resolve_relation(
1010                    new_field.remote_field.model, package_label, model_name
1011                )
1012                if rename_key in self.renamed_models:
1013                    new_field.remote_field.model = old_field.remote_field.model
1014                # Handle ForeignKey which can only have a single to_field.
1015                remote_field_name = getattr(new_field.remote_field, "field_name", None)
1016                if remote_field_name:
1017                    to_field_rename_key = rename_key + (remote_field_name,)
1018                    if to_field_rename_key in self.renamed_fields:
1019                        # Repoint both model and field name because to_field
1020                        # inclusion in ForeignKey.deconstruct() is based on
1021                        # both.
1022                        new_field.remote_field.model = old_field.remote_field.model
1023                        new_field.remote_field.field_name = (
1024                            old_field.remote_field.field_name
1025                        )
1026                # Handle ForeignObjects which can have multiple from_fields/to_fields.
1027                from_fields = getattr(new_field, "from_fields", None)
1028                if from_fields:
1029                    from_rename_key = (package_label, model_name)
1030                    new_field.from_fields = tuple(
1031                        [
1032                            self.renamed_fields.get(
1033                                from_rename_key + (from_field,), from_field
1034                            )
1035                            for from_field in from_fields
1036                        ]
1037                    )
1038                    new_field.to_fields = tuple(
1039                        [
1040                            self.renamed_fields.get(rename_key + (to_field,), to_field)
1041                            for to_field in new_field.to_fields
1042                        ]
1043                    )
1044                dependencies.extend(
1045                    self._get_dependencies_for_foreign_key(
1046                        package_label,
1047                        model_name,
1048                        new_field,
1049                        self.to_state,
1050                    )
1051                )
1052            if hasattr(new_field, "remote_field") and getattr(
1053                new_field.remote_field, "through", None
1054            ):
1055                rename_key = resolve_relation(
1056                    new_field.remote_field.through, package_label, model_name
1057                )
1058                if rename_key in self.renamed_models:
1059                    new_field.remote_field.through = old_field.remote_field.through
1060            old_field_dec = self.deep_deconstruct(old_field)
1061            new_field_dec = self.deep_deconstruct(new_field)
1062            # If the field was confirmed to be renamed it means that only
1063            # db_column was allowed to change which generate_renamed_fields()
1064            # already accounts for by adding an AlterField operation.
1065            if old_field_dec != new_field_dec and old_field_name == field_name:
1066                both_m2m = old_field.many_to_many and new_field.many_to_many
1067                neither_m2m = not old_field.many_to_many and not new_field.many_to_many
1068                if both_m2m or neither_m2m:
1069                    # Either both fields are m2m or neither is
1070                    preserve_default = True
1071                    if (
1072                        old_field.null
1073                        and not new_field.null
1074                        and not new_field.has_default()
1075                        and not new_field.many_to_many
1076                    ):
1077                        field = new_field.clone()
1078                        new_default = self.questioner.ask_not_null_alteration(
1079                            field_name, model_name
1080                        )
1081                        if new_default is not models.NOT_PROVIDED:
1082                            field.default = new_default
1083                            preserve_default = False
1084                    else:
1085                        field = new_field
1086                    self.add_operation(
1087                        package_label,
1088                        operations.AlterField(
1089                            model_name=model_name,
1090                            name=field_name,
1091                            field=field,
1092                            preserve_default=preserve_default,
1093                        ),
1094                        dependencies=dependencies,
1095                    )
1096                else:
1097                    # We cannot alter between m2m and concrete fields
1098                    self._generate_removed_field(package_label, model_name, field_name)
1099                    self._generate_added_field(package_label, model_name, field_name)
1100
1101    def create_altered_indexes(self):
1102        option_name = operations.AddIndex.option_name
1103
1104        for package_label, model_name in sorted(self.kept_model_keys):
1105            old_model_name = self.renamed_models.get(
1106                (package_label, model_name), model_name
1107            )
1108            old_model_state = self.from_state.models[package_label, old_model_name]
1109            new_model_state = self.to_state.models[package_label, model_name]
1110
1111            old_indexes = old_model_state.options[option_name]
1112            new_indexes = new_model_state.options[option_name]
1113            added_indexes = [idx for idx in new_indexes if idx not in old_indexes]
1114            removed_indexes = [idx for idx in old_indexes if idx not in new_indexes]
1115            renamed_indexes = []
1116            # Find renamed indexes.
1117            remove_from_added = []
1118            remove_from_removed = []
1119            for new_index in added_indexes:
1120                new_index_dec = new_index.deconstruct()
1121                new_index_name = new_index_dec[2].pop("name")
1122                for old_index in removed_indexes:
1123                    old_index_dec = old_index.deconstruct()
1124                    old_index_name = old_index_dec[2].pop("name")
1125                    # Indexes are the same except for the names.
1126                    if (
1127                        new_index_dec == old_index_dec
1128                        and new_index_name != old_index_name
1129                    ):
1130                        renamed_indexes.append((old_index_name, new_index_name, None))
1131                        remove_from_added.append(new_index)
1132                        remove_from_removed.append(old_index)
1133
1134            # Remove renamed indexes from the lists of added and removed
1135            # indexes.
1136            added_indexes = [
1137                idx for idx in added_indexes if idx not in remove_from_added
1138            ]
1139            removed_indexes = [
1140                idx for idx in removed_indexes if idx not in remove_from_removed
1141            ]
1142
1143            self.altered_indexes.update(
1144                {
1145                    (package_label, model_name): {
1146                        "added_indexes": added_indexes,
1147                        "removed_indexes": removed_indexes,
1148                        "renamed_indexes": renamed_indexes,
1149                    }
1150                }
1151            )
1152
1153    def generate_added_indexes(self):
1154        for (package_label, model_name), alt_indexes in self.altered_indexes.items():
1155            dependencies = self._get_dependencies_for_model(package_label, model_name)
1156            for index in alt_indexes["added_indexes"]:
1157                self.add_operation(
1158                    package_label,
1159                    operations.AddIndex(
1160                        model_name=model_name,
1161                        index=index,
1162                    ),
1163                    dependencies=dependencies,
1164                )
1165
1166    def generate_removed_indexes(self):
1167        for (package_label, model_name), alt_indexes in self.altered_indexes.items():
1168            for index in alt_indexes["removed_indexes"]:
1169                self.add_operation(
1170                    package_label,
1171                    operations.RemoveIndex(
1172                        model_name=model_name,
1173                        name=index.name,
1174                    ),
1175                )
1176
1177    def generate_renamed_indexes(self):
1178        for (package_label, model_name), alt_indexes in self.altered_indexes.items():
1179            for old_index_name, new_index_name, old_fields in alt_indexes[
1180                "renamed_indexes"
1181            ]:
1182                self.add_operation(
1183                    package_label,
1184                    operations.RenameIndex(
1185                        model_name=model_name,
1186                        new_name=new_index_name,
1187                        old_name=old_index_name,
1188                        old_fields=old_fields,
1189                    ),
1190                )
1191
1192    def create_altered_constraints(self):
1193        option_name = operations.AddConstraint.option_name
1194        for package_label, model_name in sorted(self.kept_model_keys):
1195            old_model_name = self.renamed_models.get(
1196                (package_label, model_name), model_name
1197            )
1198            old_model_state = self.from_state.models[package_label, old_model_name]
1199            new_model_state = self.to_state.models[package_label, model_name]
1200
1201            old_constraints = old_model_state.options[option_name]
1202            new_constraints = new_model_state.options[option_name]
1203            add_constraints = [c for c in new_constraints if c not in old_constraints]
1204            rem_constraints = [c for c in old_constraints if c not in new_constraints]
1205
1206            self.altered_constraints.update(
1207                {
1208                    (package_label, model_name): {
1209                        "added_constraints": add_constraints,
1210                        "removed_constraints": rem_constraints,
1211                    }
1212                }
1213            )
1214
1215    def generate_added_constraints(self):
1216        for (
1217            package_label,
1218            model_name,
1219        ), alt_constraints in self.altered_constraints.items():
1220            dependencies = self._get_dependencies_for_model(package_label, model_name)
1221            for constraint in alt_constraints["added_constraints"]:
1222                self.add_operation(
1223                    package_label,
1224                    operations.AddConstraint(
1225                        model_name=model_name,
1226                        constraint=constraint,
1227                    ),
1228                    dependencies=dependencies,
1229                )
1230
1231    def generate_removed_constraints(self):
1232        for (
1233            package_label,
1234            model_name,
1235        ), alt_constraints in self.altered_constraints.items():
1236            for constraint in alt_constraints["removed_constraints"]:
1237                self.add_operation(
1238                    package_label,
1239                    operations.RemoveConstraint(
1240                        model_name=model_name,
1241                        name=constraint.name,
1242                    ),
1243                )
1244
1245    @staticmethod
1246    def _get_dependencies_for_foreign_key(
1247        package_label, model_name, field, project_state
1248    ):
1249        remote_field_model = None
1250        if hasattr(field.remote_field, "model"):
1251            remote_field_model = field.remote_field.model
1252        else:
1253            relations = project_state.relations[package_label, model_name]
1254            for (remote_package_label, remote_model_name), fields in relations.items():
1255                if any(
1256                    field == related_field.remote_field
1257                    for related_field in fields.values()
1258                ):
1259                    remote_field_model = f"{remote_package_label}.{remote_model_name}"
1260                    break
1261        # Account for FKs to swappable models
1262        swappable_setting = getattr(field, "swappable_setting", None)
1263        if swappable_setting is not None:
1264            dep_package_label = "__setting__"
1265            dep_object_name = swappable_setting
1266        else:
1267            dep_package_label, dep_object_name = resolve_relation(
1268                remote_field_model,
1269                package_label,
1270                model_name,
1271            )
1272        dependencies = [(dep_package_label, dep_object_name, None, True)]
1273        if getattr(field.remote_field, "through", None):
1274            through_package_label, through_object_name = resolve_relation(
1275                field.remote_field.through,
1276                package_label,
1277                model_name,
1278            )
1279            dependencies.append(
1280                (through_package_label, through_object_name, None, True)
1281            )
1282        return dependencies
1283
1284    def _get_dependencies_for_model(self, package_label, model_name):
1285        """Return foreign key dependencies of the given model."""
1286        dependencies = []
1287        model_state = self.to_state.models[package_label, model_name]
1288        for field in model_state.fields.values():
1289            if field.is_relation:
1290                dependencies.extend(
1291                    self._get_dependencies_for_foreign_key(
1292                        package_label,
1293                        model_name,
1294                        field,
1295                        self.to_state,
1296                    )
1297                )
1298        return dependencies
1299
1300    def _get_altered_foo_together_operations(self, option_name):
1301        for package_label, model_name in sorted(self.kept_model_keys):
1302            old_model_name = self.renamed_models.get(
1303                (package_label, model_name), model_name
1304            )
1305            old_model_state = self.from_state.models[package_label, old_model_name]
1306            new_model_state = self.to_state.models[package_label, model_name]
1307
1308            # We run the old version through the field renames to account for those
1309            old_value = old_model_state.options.get(option_name)
1310            old_value = (
1311                {
1312                    tuple(
1313                        self.renamed_fields.get((package_label, model_name, n), n)
1314                        for n in unique
1315                    )
1316                    for unique in old_value
1317                }
1318                if old_value
1319                else set()
1320            )
1321
1322            new_value = new_model_state.options.get(option_name)
1323            new_value = set(new_value) if new_value else set()
1324
1325            if old_value != new_value:
1326                dependencies = []
1327                for foo_togethers in new_value:
1328                    for field_name in foo_togethers:
1329                        field = new_model_state.get_field(field_name)
1330                        if field.remote_field and field.remote_field.model:
1331                            dependencies.extend(
1332                                self._get_dependencies_for_foreign_key(
1333                                    package_label,
1334                                    model_name,
1335                                    field,
1336                                    self.to_state,
1337                                )
1338                            )
1339                yield (
1340                    old_value,
1341                    new_value,
1342                    package_label,
1343                    model_name,
1344                    dependencies,
1345                )
1346
1347    def _generate_removed_altered_foo_together(self, operation):
1348        for (
1349            old_value,
1350            new_value,
1351            package_label,
1352            model_name,
1353            dependencies,
1354        ) in self._get_altered_foo_together_operations(operation.option_name):
1355            removal_value = new_value.intersection(old_value)
1356            if removal_value or old_value:
1357                self.add_operation(
1358                    package_label,
1359                    operation(
1360                        name=model_name, **{operation.option_name: removal_value}
1361                    ),
1362                    dependencies=dependencies,
1363                )
1364
1365    def generate_altered_db_table(self):
1366        models_to_check = self.kept_model_keys.union(self.kept_unmanaged_keys)
1367        for package_label, model_name in sorted(models_to_check):
1368            old_model_name = self.renamed_models.get(
1369                (package_label, model_name), model_name
1370            )
1371            old_model_state = self.from_state.models[package_label, old_model_name]
1372            new_model_state = self.to_state.models[package_label, model_name]
1373            old_db_table_name = old_model_state.options.get("db_table")
1374            new_db_table_name = new_model_state.options.get("db_table")
1375            if old_db_table_name != new_db_table_name:
1376                self.add_operation(
1377                    package_label,
1378                    operations.AlterModelTable(
1379                        name=model_name,
1380                        table=new_db_table_name,
1381                    ),
1382                )
1383
1384    def generate_altered_db_table_comment(self):
1385        models_to_check = self.kept_model_keys.union(self.kept_unmanaged_keys)
1386        for package_label, model_name in sorted(models_to_check):
1387            old_model_name = self.renamed_models.get(
1388                (package_label, model_name), model_name
1389            )
1390            old_model_state = self.from_state.models[package_label, old_model_name]
1391            new_model_state = self.to_state.models[package_label, model_name]
1392
1393            old_db_table_comment = old_model_state.options.get("db_table_comment")
1394            new_db_table_comment = new_model_state.options.get("db_table_comment")
1395            if old_db_table_comment != new_db_table_comment:
1396                self.add_operation(
1397                    package_label,
1398                    operations.AlterModelTableComment(
1399                        name=model_name,
1400                        table_comment=new_db_table_comment,
1401                    ),
1402                )
1403
1404    def generate_altered_options(self):
1405        """
1406        Work out if any non-schema-affecting options have changed and make an
1407        operation to represent them in state changes (in case Python code in
1408        migrations needs them).
1409        """
1410        models_to_check = self.kept_model_keys.union(
1411            self.kept_unmanaged_keys,
1412            # unmanaged converted to managed
1413            self.old_unmanaged_keys & self.new_model_keys,
1414            # managed converted to unmanaged
1415            self.old_model_keys & self.new_unmanaged_keys,
1416        )
1417
1418        for package_label, model_name in sorted(models_to_check):
1419            old_model_name = self.renamed_models.get(
1420                (package_label, model_name), model_name
1421            )
1422            old_model_state = self.from_state.models[package_label, old_model_name]
1423            new_model_state = self.to_state.models[package_label, model_name]
1424            old_options = {
1425                key: value
1426                for key, value in old_model_state.options.items()
1427                if key in AlterModelOptions.ALTER_OPTION_KEYS
1428            }
1429            new_options = {
1430                key: value
1431                for key, value in new_model_state.options.items()
1432                if key in AlterModelOptions.ALTER_OPTION_KEYS
1433            }
1434            if old_options != new_options:
1435                self.add_operation(
1436                    package_label,
1437                    operations.AlterModelOptions(
1438                        name=model_name,
1439                        options=new_options,
1440                    ),
1441                )
1442
1443    def generate_altered_order_with_respect_to(self):
1444        for package_label, model_name in sorted(self.kept_model_keys):
1445            old_model_name = self.renamed_models.get(
1446                (package_label, model_name), model_name
1447            )
1448            old_model_state = self.from_state.models[package_label, old_model_name]
1449            new_model_state = self.to_state.models[package_label, model_name]
1450            if old_model_state.options.get(
1451                "order_with_respect_to"
1452            ) != new_model_state.options.get("order_with_respect_to"):
1453                # Make sure it comes second if we're adding
1454                # (removal dependency is part of RemoveField)
1455                dependencies = []
1456                if new_model_state.options.get("order_with_respect_to"):
1457                    dependencies.append(
1458                        (
1459                            package_label,
1460                            model_name,
1461                            new_model_state.options["order_with_respect_to"],
1462                            True,
1463                        )
1464                    )
1465                # Actually generate the operation
1466                self.add_operation(
1467                    package_label,
1468                    operations.AlterOrderWithRespectTo(
1469                        name=model_name,
1470                        order_with_respect_to=new_model_state.options.get(
1471                            "order_with_respect_to"
1472                        ),
1473                    ),
1474                    dependencies=dependencies,
1475                )
1476
1477    def generate_altered_managers(self):
1478        for package_label, model_name in sorted(self.kept_model_keys):
1479            old_model_name = self.renamed_models.get(
1480                (package_label, model_name), model_name
1481            )
1482            old_model_state = self.from_state.models[package_label, old_model_name]
1483            new_model_state = self.to_state.models[package_label, model_name]
1484            if old_model_state.managers != new_model_state.managers:
1485                self.add_operation(
1486                    package_label,
1487                    operations.AlterModelManagers(
1488                        name=model_name,
1489                        managers=new_model_state.managers,
1490                    ),
1491                )
1492
1493    def arrange_for_graph(self, changes, graph, migration_name=None):
1494        """
1495        Take a result from changes() and a MigrationGraph, and fix the names
1496        and dependencies of the changes so they extend the graph from the leaf
1497        nodes for each app.
1498        """
1499        leaves = graph.leaf_nodes()
1500        name_map = {}
1501        for package_label, migrations in list(changes.items()):
1502            if not migrations:
1503                continue
1504            # Find the app label's current leaf node
1505            app_leaf = None
1506            for leaf in leaves:
1507                if leaf[0] == package_label:
1508                    app_leaf = leaf
1509                    break
1510            # Do they want an initial migration for this app?
1511            if app_leaf is None and not self.questioner.ask_initial(package_label):
1512                # They don't.
1513                for migration in migrations:
1514                    name_map[(package_label, migration.name)] = (
1515                        package_label,
1516                        "__first__",
1517                    )
1518                del changes[package_label]
1519                continue
1520            # Work out the next number in the sequence
1521            if app_leaf is None:
1522                next_number = 1
1523            else:
1524                next_number = (self.parse_number(app_leaf[1]) or 0) + 1
1525            # Name each migration
1526            for i, migration in enumerate(migrations):
1527                if i == 0 and app_leaf:
1528                    migration.dependencies.append(app_leaf)
1529                new_name_parts = ["%04i" % next_number]
1530                if migration_name:
1531                    new_name_parts.append(migration_name)
1532                elif i == 0 and not app_leaf:
1533                    new_name_parts.append("initial")
1534                else:
1535                    new_name_parts.append(migration.suggest_name()[:100])
1536                new_name = "_".join(new_name_parts)
1537                name_map[(package_label, migration.name)] = (package_label, new_name)
1538                next_number += 1
1539                migration.name = new_name
1540        # Now fix dependencies
1541        for migrations in changes.values():
1542            for migration in migrations:
1543                migration.dependencies = [
1544                    name_map.get(d, d) for d in migration.dependencies
1545                ]
1546        return changes
1547
1548    def _trim_to_packages(self, changes, package_labels):
1549        """
1550        Take changes from arrange_for_graph() and set of app labels, and return
1551        a modified set of changes which trims out as many migrations that are
1552        not in package_labels as possible. Note that some other migrations may
1553        still be present as they may be required dependencies.
1554        """
1555        # Gather other app dependencies in a first pass
1556        app_dependencies = {}
1557        for package_label, migrations in changes.items():
1558            for migration in migrations:
1559                for dep_package_label, name in migration.dependencies:
1560                    app_dependencies.setdefault(package_label, set()).add(
1561                        dep_package_label
1562                    )
1563        required_packages = set(package_labels)
1564        # Keep resolving till there's no change
1565        old_required_packages = None
1566        while old_required_packages != required_packages:
1567            old_required_packages = set(required_packages)
1568            required_packages.update(
1569                *[
1570                    app_dependencies.get(package_label, ())
1571                    for package_label in required_packages
1572                ]
1573            )
1574        # Remove all migrations that aren't needed
1575        for package_label in list(changes):
1576            if package_label not in required_packages:
1577                del changes[package_label]
1578        return changes
1579
1580    @classmethod
1581    def parse_number(cls, name):
1582        """
1583        Given a migration name, try to extract a number from the beginning of
1584        it. For a squashed migration such as '0001_squashed_0004…', return the
1585        second number. If no number is found, return None.
1586        """
1587        if squashed_match := re.search(r".*_squashed_(\d+)", name):
1588            return int(squashed_match[1])
1589        match = re.match(r"^\d+", name)
1590        if match:
1591            return int(match[0])
1592        return None