Commit c9de1b4a authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Implement swappable model support for migrations

parent 5c7ac749
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
from .migration import Migration  # NOQA
from .migration import Migration, swappable_dependency  # NOQA
from .operations import *  # NOQA
+18 −3
Original line number Diff line number Diff line
@@ -105,7 +105,11 @@ class MigrationAutodetector(object):
                    )
                )
                for field_name, other_app_label, other_model_name in related_fields:
                    if app_label != other_app_label:
                    # If it depends on a swappable something, add a dynamic depend'cy
                    swappable_setting = new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0].swappable_setting
                    if swappable_setting is not None:
                        self.add_swappable_dependency(app_label, swappable_setting)
                    elif app_label != other_app_label:
                            self.add_dependency(app_label, other_app_label)
                del pending_add[app_label, model_name]
            # Ah well, we'll need to split one. Pick deterministically.
@@ -140,7 +144,11 @@ class MigrationAutodetector(object):
                ),
                new=True,
            )
            if app_label != other_app_label:
            # If it depends on a swappable something, add a dynamic depend'cy
            swappable_setting = new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0].swappable_setting
            if swappable_setting is not None:
                self.add_swappable_dependency(app_label, swappable_setting)
            elif app_label != other_app_label:
                self.add_dependency(app_label, other_app_label)
        # Removing models
        removed_models = set(old_model_keys) - set(new_model_keys)
@@ -276,6 +284,13 @@ class MigrationAutodetector(object):
            dependency = (other_app_label, "__first__")
        self.migrations[app_label][-1].dependencies.append(dependency)

    def add_swappable_dependency(self, app_label, setting_name):
        """
        Adds a dependency to the value of a swappable model setting.
        """
        dependency = ("__setting__", setting_name)
        self.migrations[app_label][-1].dependencies.append(dependency)

    def _arrange_for_graph(self, changes, graph):
        """
        Takes in a result from changes() and a MigrationGraph,
+1 −1
Original line number Diff line number Diff line
@@ -223,7 +223,7 @@ class MigrationLoader(object):
                        self.graph.add_node(parent, new_migration)
                        self.applied_migrations.add(parent)
                    elif parent[0] in self.migrated_apps:
                        parent = (parent[0], list(self.graph.root_nodes(parent[0]))[0])
                        parent = list(self.graph.root_nodes(parent[0]))[0]
                    else:
                        raise ValueError("Dependency on unknown app %s" % parent[0])
                self.graph.add_dependency(key, parent)
+7 −0
Original line number Diff line number Diff line
@@ -127,3 +127,10 @@ class Migration(object):
        to_run.reverse()
        for operation, to_state, from_state in to_run:
            operation.database_backwards(self.app_label, schema_editor, from_state, to_state)


def swappable_dependency(value):
    """
    Turns a setting value into a dependency.
    """
    return (value.split(".", 1)[0], "__first__")
+26 −1
Original line number Diff line number Diff line
@@ -13,6 +13,20 @@ from django.utils.functional import Promise
from django.utils import six


class SettingsReference(str):
    """
    Special subclass of string which actually references a current settings
    value. It's treated as the value in memory, but serializes out to a
    settings.NAME attribute reference.
    """

    def __new__(self, value, setting_name):
        return str.__new__(self, value)

    def __init__(self, value, setting_name):
        self.setting_name = setting_name


class MigrationWriter(object):
    """
    Takes a Migration instance and is able to produce the contents
@@ -27,7 +41,6 @@ class MigrationWriter(object):
        Returns a string of the file contents.
        """
        items = {
            "dependencies": repr(self.migration.dependencies),
            "replaces_str": "",
        }
        imports = set()
@@ -46,6 +59,15 @@ class MigrationWriter(object):
                arg_strings.append("%s = %s" % (kw, arg_string))
            operation_strings.append("migrations.%s(%s\n        )" % (name, "".join("\n            %s," % arg for arg in arg_strings)))
        items["operations"] = "[%s\n    ]" % "".join("\n        %s," % s for s in operation_strings)
        # Format dependencies and write out swappable dependencies right
        items["dependencies"] = "["
        for dependency in self.migration.dependencies:
            if dependency[0] == "__setting__":
                items["dependencies"] += "\n        migrations.swappable_dependency(settings.%s)," % dependency[1]
                imports.add("from django.conf import settings")
            else:
                items["dependencies"] += "\n        %s," % repr(dependency)
        items["dependencies"] += "\n    ]"
        # Format imports nicely
        imports.discard("from django.db import models")
        if not imports:
@@ -136,6 +158,9 @@ class MigrationWriter(object):
        # Datetimes
        elif isinstance(value, (datetime.datetime, datetime.date)):
            return repr(value), set(["import datetime"])
        # Settings references
        elif isinstance(value, SettingsReference):
            return "settings.%s" % value.setting_name, set(["from django.conf import settings"])
        # Simple types
        elif isinstance(value, six.integer_types + (float, six.binary_type, six.text_type, bool, type(None))):
            return repr(value), set()
Loading