def get_app(app_label, emptyOK=False):
    """Return the app with the given label.

    This returns the app from the app registry on Django >= 1.7, and from
    the old-style cache on Django < 1.7.

        app_label (str):
            The label for the app containing the models.

        emptyOK (bool, optional):
            Impacts the return value if the app has no models in it.

    Returns:
        module:
        The app module, if available.

        If the app module is available, but the models module is not and
        ``emptyOK`` is set, this will return ``None``. Otherwise, if modules
        are not available, this will raise
        :py:exc:`~django.core.exceptions.ImproperlyConfigured`.

    Raises:
        django.core.exceptions.ImproperlyConfigured:
            The app module was not found, or it was found but a models module
            was not and ``emptyOK`` was ``False``.
    """
    if apps:
        # Django >= 1.7
        try:
            models_module = apps.get_app_config(app_label).models_module
        except LookupError as e:
            # Convert this to an ImproperlyConfigured.
            raise ImproperlyConfigured(*e.args)

        if models_module is None and not emptyOK:
            # This is the exact error that Django 1.6 provided.
            raise ImproperlyConfigured(
                'App with label %s is missing a models.py module.'
                % app_label)

        return models_module
    else:
        # Django < 1.7
        return cache.get_app(app_label, emptyOK) class DatabaseState(object):
    """Tracks some useful state in the database.

    This primarily tracks indexes associated with tables, allowing them to be
    scanned from the database, explicitly added, removed, or cleared.
    """

    def __init__(self, db_name, scan=True):
        """Initialize the state.

        Args:
            db_name (unicode):
                The name of the database.

            scan (bool, optional):
                Whether to automatically scan state from the database during
                initialization. By default, information is scanned.
        """
        connection = connections[db_name]

        self.db_name = db_name
        self._tables = {}
        self._norm_table_name = \
            lambda name: convert_table_name(connection, name)

        if scan:
            self.rescan_tables()

    def clone(self):
        """Clone the database state.

        Returns:
            DatabaseState:
            The cloned copy of the state.
        """
        cloned_sig = DatabaseState(db_name=self.db_name, scan=False)
        cloned_sig._tables = deepcopy(self._tables)

        return cloned_sig

    def add_table(self, table_name):
        """Add a table to track.

        This will add an empty entry for the table to the state.

        Args:
            table_name (unicode):
                The name of the table.
        """
        self._tables[self._norm_table_name(table_name)] = {
            'indexes': {},
        }

    def has_table(self, table_name):
        """Return whether a table is being tracked.

        This does not necessarily mean that the table exists in the database.
        Rather, state for the table is being tracked.

        Args:
            table_name (unicode):
                The name of the table to look up.

        Returns:
            bool:
            ``True`` if the table is being tracked. ``False`` if it is not.
        """
        return self._norm_table_name(table_name) in self._tables

    def has_model(self, model):
        """Return whether a database model is installed in the database.

        Args:
            model (type):
                The model class.

        Returns:
            bool:
            ``True`` if the model has an accompanying table in the database.
            ``False`` if it does not.
        """
        meta = model._meta

        return (self.has_table(meta.db_table) or
                (meta.auto_created and
                 self.has_table(meta.auto_created._meta.db_table)))

    def add_index(self, table_name, index_name, columns, unique=False):
        """Add a table's index to the database state.

        This index can be used for later lookup during the evolution process.
        It won't otherwise be preserved, though the resulting indexes are
        expected to match the result in the database.

        This requires the table to be tracked first.

        Args:
            table_name (unicode):
                The name of the table.

            index_name (unicode):
                The name of the index.

            columns (list of unicode):
                A list of column names the index is comprised of.

            unique (bool, optional):
                Whether this is a unique index.

        Raises:
            django_evolution.errors.DatabaseStateError:
                There was an issue adding this index. Details are in the
                exception's message.
        """
        assert index_name

        table_name = self._norm_table_name(table_name)

        try:
            indexes = self._tables[table_name]['indexes']
        except KeyError:
            raise DatabaseStateError(
                'Unable to add index "%s" to table "%s". The table is not '
                'being tracked in the database state.'
                % (index_name, table_name))

        existing_index = self.get_index(table_name=table_name,
                                        index_name=index_name)

        if existing_index:
            raise DatabaseStateError(
                'Unable to add index "%s" to table "%s". This index already '
                'exists.'
                % (index_name, table_name))

        indexes[index_name] = IndexState(name=index_name,
                                         columns=columns,
                                         unique=unique)

    def remove_index(self, table_name, index_name, unique=False):
        """Remove an index from the database state.

        This index will no longer be found during lookups when generating
        evolution SQL, even if it exists in the database.

        This requires the table to be tracked first and for the index to
        both exist and match the ``unique`` flag.

        Args:
            table_name (unicode):
                The name of the table.

            index_name (unicode):
                The name of the index.

            unique (bool, optional):
                Whether this is a unique index.

        Raises:
            django_evolution.errors.DatabaseStateError:
                There was an issue removing this index. Details are in the
                exception's message.
        """
        table_name = self._norm_table_name(table_name)

        try:
            indexes = self._tables[table_name]['indexes']
        except KeyError:
            raise DatabaseStateError(
                'Unable to remove index "%s" from table "%s". The table is '
                'not being tracked in the database state.'
                % (index_name, table_name))

        try:
            existing_index = indexes[index_name]
        except KeyError:
            raise DatabaseStateError(
                'Unable to remove index "%s" from table "%s". The index '
                'could not be found.'
                % (index_name, table_name))

        if unique != existing_index.unique:
            raise DatabaseStateError(
                'Unable to remove index "%s" from table "%s". The specified '
                'index type (unique=%r) does not match the existing type '
                '(unique=%r).'
                % (index_name, table_name, unique, existing_index.unique))

        del indexes[index_name]

    def get_index(self, table_name, index_name):
        """Return the index state for a given name.

        Args:
            table_name (unicode):
                The name of the table.

            index_name (unicode):
                The name of the index.

        Returns:
            IndexState:
            The state for the index, if found. ``None`` if the index could not
            be found.
        """
        table_name = self._norm_table_name(table_name)

        try:
            return self._tables[table_name]['indexes'][index_name]
        except KeyError:
            return None

    def find_index(self, table_name, columns, unique=False):
        """Find and return an index matching the given columns and flags.

        Args:
            table_name (unicode):
                The name of the table.

            columns (list of unicode):
                The list of columns the index is comprised of.

            unique (bool, optional):
                Whether this is a unique index.

        Returns:
            IndexState:
            The state for the index, if found. ``None`` if an index matching
            the criteria could not be found.
        """
        table_name = self._norm_table_name(table_name)

        for index_state in self.iter_indexes(table_name):
            if (index_state.columns == columns and
                index_state.unique == unique):
                return index_state

        return None

    def clear_indexes(self, table_name):
        """Clear all recorded indexes for a table.

        Args:
            table_name (unicode):
                The name of the table.
        """
        table_name = self._norm_table_name(table_name)

        try:
            self._tables[table_name]['indexes'].clear()
        except KeyError:
            pass

    def iter_indexes(self, table_name):
        """Iterate through all indexes for a table.

        Args:
            table_name (unicode):
                The name of the table.

        Yields:
            IndexState:
            An index in the table.
        """
        table_name = self._norm_table_name(table_name)

        try:
            indexes = self._tables[table_name]['indexes']
        except KeyError:
            return

        for index_state in six.itervalues(indexes):
            yield index_state

    def rescan_tables(self):
        """Rescan the list of tables from the database.

        This will look up all tables found in the database, along with
        information (such as indexes) on those tables.

        Existing information on the tables will be flushed.
        """
        evolver = EvolutionOperationsMulti(self.db_name).get_evolver()
        connection = evolver.connection
        introspection = connection.introspection
        cursor = connection.cursor()

        for table_name in introspection.get_table_list(cursor):
            # NOTE: The table names are already normalized, so there's no
            #       need to normalize them again.
            if hasattr(table_name, 'name'):
                # In Django >= 1.7, we get back TableInfo namedtuples,
                # which have 'name' and 'type' keys. We don't care about
                # anything but 'name'.
                table_name = table_name.name

            if self.has_table(table_name):
                self.clear_indexes(table_name)
            else:
                self.add_table(table_name)

            indexes = evolver.get_indexes_for_table(table_name)

            for index_name, index_info in six.iteritems(indexes):
                self.add_index(table_name=table_name,
                               index_name=index_name,
                               columns=index_info['columns'],
                               unique=index_info['unique']) class MigrationConflictsError(BaseMigrationError):
    """There are conflicts between migrations."""

    def __init__(self, conflicts):
        """Initialize the error.

        Args:
            conflicts (dict):
                A dictionary of conflicts, provided by the migrations system.
        """
        # Note that we're using the same error message that Django's migrate
        # command uses.
        super(MigrationConflictsError, self).__init__(
            "Conflicting migrations detected; multiple leaf nodes "
            "in the migration graph: (%s).\n"
            "To fix them run 'python manage.py makemigrations "
            "--merge'"
            % '; '.join(
                '%s in %s' % (', '.join(sorted(conflict_names)), app_label)
                for app_label, conflict_names in six.iteritems(conflicts)
            )) class MigrationHistoryError(BaseMigrationError):
    """An error with the stored history of migrations.

    This is raised if any applied migrations have unapplied dependencies.
    """ class AppSignature(BaseSignature):
    """Signature information for an application.

    Application signatures store information on a Django application and all
    models registered under that application.
    """

    @classmethod
    def from_app(cls, app, database):
        """Create an application signature from an application.

        This will store data on the application and create a
        :py:class:`ModelSignature` for each of the application's models.

        Args:
            app (module):
                The application module to create the signature from.

            database (unicode):
                The name of the database.

        Returns:
            AppSignature:
            The application signature based on the application.
        """
        app_label = get_app_label(app)
        app_upgrade_info = get_app_upgrade_info(app,
                                                simulate_applied=True,
                                                database=database)

        app_sig = cls(
            app_id=app_label,
            legacy_app_label=get_legacy_app_label(app),
            upgrade_method=app_upgrade_info.get('upgrade_method'),
            applied_migrations=app_upgrade_info.get('applied_migrations'))

        for model in get_models(app):
            if db_router_allows_schema_upgrade(database, app_label, model):
                app_sig.add_model(model)

        return app_sig

    @classmethod
    def deserialize(cls, app_id, app_sig_dict, sig_version,
                    database=DEFAULT_DB_ALIAS):
        """Deserialize a serialized application signature.

        Args:
            app_id (unicode):
                The application ID.

            app_sig_dict (dict):
                The dictionary containing application signature data.

            sig_version (int):
                The version of the serialized signature data.

            database (unicode, optional):
                The name of the database.

        Returns:
            AppSignature:
            The resulting signature instance.

        Raises:
            django_evolution.errors.InvalidSignatureVersion:
                The signature version provided isn't supported.
        """
        validate_sig_version(sig_version)

        legacy_app_label = None
        upgrade_method = None
        applied_migrations = None

        if sig_version == 2:
            model_sigs_dict = app_sig_dict['models']
            legacy_app_label = app_sig_dict['legacy_app_label']
            upgrade_method = app_sig_dict.get('upgrade_method')
            applied_migrations = app_sig_dict.get('applied_migrations')
        elif sig_version == 1:
            model_sigs_dict = app_sig_dict
            legacy_app_label = app_id

            # Try to figure out the upgrade method for this app, factoring in
            # just the presence of evolutions/migrations directories (*not*
            # scanning for any mutations that change the upgrade method).
            #
            # This might not find an upgrade method, which is okay. Other
            # heuristics during diffing will try to deal with unknown upgrade
            # methods, and when all else fails, explicit mutations can be
            # added to set the record straight.
            try:
                upgrade_info = get_app_upgrade_info(get_app(app_id),
                                                    scan_evolutions=False,
                                                    database=database)
                upgrade_method = upgrade_info.get('upgrade_method')
                applied_migrations = upgrade_info.get('applied_migrations')
            except ImproperlyConfigured:
                # An app with the ID couldn't be found. This is likely either
                # an issue with an app label change, a deleted app, or an
                # app that is dynamically added later.
                pass

        app_sig = cls(app_id=app_id,
                      legacy_app_label=legacy_app_label,
                      upgrade_method=upgrade_method,
                      applied_migrations=applied_migrations)
        app_sig._loaded_sig_version = sig_version

        for model_name, model_sig_dict in six.iteritems(model_sigs_dict):
            app_sig.add_model_sig(
                ModelSignature.deserialize(model_name=model_name,
                                           model_sig_dict=model_sig_dict,
                                           sig_version=sig_version,
                                           database=database))

        return app_sig

    def __init__(self, app_id, legacy_app_label=None, upgrade_method=None,
                 applied_migrations=None):
        """Initialize the signature.

        Args:
            app_id (unicode):
                The ID of the application. This will be the application label.
                On modern versions of Django, this may differ from the
                legacy app label.

            legacy_app_label (unicode, optional):
                The legacy label for the application. This is based on the
                module name.

            upgrade_method (unicode, optional):
                The upgrade method used for this application. This must be
                a value from
                :py:class:`~django_evolution.evolve.UpgradeMethod`, or
                ``None``.

            applied_migrations (set of unicode, optional):
                The migration names that are applied as of this signature.
        """
        self.app_id = app_id
        self.legacy_app_label = legacy_app_label or app_id
        self.upgrade_method = upgrade_method
        self.applied_migrations = applied_migrations

        self._loaded_sig_version = None
        self._model_sigs = OrderedDict()

    @property
    def model_sigs(self):
        """The model signatures stored on the application signature."""
        return six.itervalues(self._model_sigs)

    @property
    def applied_migrations(self):
        """The set of migration names applied to the app.

        Type:
            set of unicode
        """
        return self._applied_migrations

    @applied_migrations.setter
    def applied_migrations(self, value):
        """Set the migration names applied to the app.

        Args:
            value (set or list or
                   django_evolution.utils.migrations.MigratonList):
                The new migration names. This may be an explicit set/list
                of migration names, or it can be a MigrationList, of which
                only the migration names relevant to this app will be stored.
        """
        if isinstance(value, MigrationList):
            value = [
                info['name']
                for info in value
                if info['app_label'] == self.app_id
            ]

        if value is not None:
            value = set(value)

        self._applied_migrations = value

    def is_empty(self):
        """Return whether the application signature is empty.

        An empty application signature contains no models.

        Returns:
            bool:
            ``True`` if the signature is empty. ``False`` if it still has
            models in it.
        """
        return not bool(self._model_sigs)

    def add_model(self, model):
        """Add a model to the application signature.

        This will construct a :py:class:`ModelSignature` and add it to this
        application signature.

        Args:
            model (django.db.models.Model):
                The model to create the signature from.
        """
        self.add_model_sig(ModelSignature.from_model(model))

    def add_model_sig(self, model_sig):
        """Add a model signature to the application signature.

        Args:
            model_sig (ModelSignature):
                The model signature to add.
        """
        self._model_sigs[model_sig.model_name] = model_sig

    def remove_model_sig(self, model_name):
        """Remove a model signature from the application signature.

        Args:
            model_name (unicode):
                The name of the model.

        Raises:
            django_evolution.errors.MissingSignatureError:
                The model name does not represent a known model signature.
        """
        try:
            del self._model_sigs[model_name]
        except KeyError:
            raise MissingSignatureError(
                _('A model signature for "%s" could not be found.')
                % model_name)

    def clear_model_sigs(self):
        """Clear all model signatures from the application signature."""
        self._model_sigs.clear()

    def get_model_sig(self, model_name, required=False):
        """Return a model signature for the given model name.

        Args:
            model_name (unicode):
                The name of the model.

            required (bool, optional):
                Whether the model signature must be present. If ``True`` and
                the signature is missing, this will raise an exception.

        Returns:
            ModelSignature:
            The model signature, if found. If no model signature matches
            the model name, ``None`` will be returned.

        Raises:
            django_evolution.errors.MissingSignatureError:
                The model signature was not found, and ``required`` was
                ``True``.
        """
        model_sig = self._model_sigs.get(model_name)

        if model_sig is None and required:
            raise MissingSignatureError(
                _('Unable to find a model signature for "%s.%s". '
                  'syncdb/migrate might need to be run first.')
                % (self.app_id, model_name))

        return model_sig

    def diff(self, old_app_sig):
        """Diff against an older application signature.

        This will return a dictionary containing the differences between
        two application signatures.

        Args:
            old_app_sig (AppSignature):
                The old app signature to diff against.

        Returns:
            collections.OrderedDict:
            A dictionary in the following form::

                {
                    'changed': {
                        '<model_name>': <ModelSignature diff>,
                        ...
                    },
                    'deleted': [ <list of deleted model names> ],
                    'meta_changed': {
                        '<prop_name>': {
                            'old': <old value>,
                            'new': <new value>,
                        },
                        ...
                    }
                }

            Any key lacking a value will be ommitted from the diff.

        Raises:
            TypeError:
                The old signature provided was not an :py:class:`AppSignature`.
        """
        if not isinstance(old_app_sig, AppSignature):
            raise TypeError('Must provide an AppSignature to diff against, '
                            'not a %s.' % type(old_app_sig))

        deleted_models = []
        changed_models = OrderedDict()
        meta_changed = OrderedDict()

        # Process the models in the application, looking for changes to
        # fields and meta attributes.
        for old_model_sig in old_app_sig.model_sigs:
            model_name = old_model_sig.model_name
            new_model_sig = self.get_model_sig(model_name)

            if new_model_sig:
                model_changes = new_model_sig.diff(old_model_sig)

                if model_changes:
                    # There are changes for this model. Store that in the
                    # diff.
                    changed_models[model_name] = model_changes
            else:
                # The model has been deleted.
                deleted_models.append(model_name)

        # Check for changes to basic metadata for the app.
        for key in ('app_id', 'legacy_app_label'):
            old_value = getattr(old_app_sig, key)
            new_value = getattr(self, key)

            if old_value != new_value:
                meta_changed[key] = {
                    'old': old_value,
                    'new': new_value,
                }

        # Check if the upgrade method has changed. We have to do this a bit
        # carefully, as the old value might be None, due to:
        #
        # 1. Coming from a version 1 signature (meaning that we only care if
        #    there are actual changes to the app and we're also transitioning
        #    to Migrations)
        #
        # 2. Coming from a version 2 signature (including a database scan)
        #    and the old signature doesn't list an upgrade method for the
        #    app (meaning it likely didn't use either evolutions or
        #    migrations).
        old_upgrade_method = old_app_sig.upgrade_method
        new_upgrade_method = self.upgrade_method
        old_sig_version = old_app_sig._loaded_sig_version

        if (old_upgrade_method != new_upgrade_method and
            ((old_sig_version is None and
              old_upgrade_method is not None) or
             (old_sig_version == 1 and
              (changed_models or deleted_models) and
              old_upgrade_method is None and
              new_upgrade_method != UpgradeMethod.EVOLUTIONS))):
            # The upgrade method has changed. If we're moving to migrations,
            # discard any other changes to the model. We're working with the
            # assumption that the migrations will account for any changes.
            #
            # The assumption may technically be wrong (there may be
            # evolutions to apply before migrations takes over), but we can't
            # easily separate out the changes made by each method. However,
            # since we've recorded a change to this app, the evolver will
            # still apply any remaining evolutions, so we're covered.
            meta_changed['upgrade_method'] = {
                'old': old_upgrade_method,
                'new': new_upgrade_method,
            }

        if new_upgrade_method == UpgradeMethod.MIGRATIONS:
            # If we're using migrations, we don't want to show any other
            # changes to the models. Those are handled by migrations, and
            # aren't something we want to include in the diff, since they
            # can't be resolved by evolutions.
            changed_models.clear()
            deleted_models = []

        # Build the dictionary of changes for the app.
        return OrderedDict(
            (key, value)
            for key, value in (('changed', changed_models),
                               ('deleted', deleted_models),
                               ('meta_changed', meta_changed))
            if value
        )

    def clone(self):
        """Clone the signature.

        Returns:
            AppSignature:
            The cloned signature.
        """
        cloned_sig = AppSignature(
            app_id=self.app_id,
            legacy_app_label=self.legacy_app_label,
            upgrade_method=self.upgrade_method,
            applied_migrations=deepcopy(self.applied_migrations))

        for model_sig in self.model_sigs:
            cloned_sig.add_model_sig(model_sig.clone())

        return cloned_sig

    def serialize(self, sig_version=LATEST_SIGNATURE_VERSION):
        """Serialize application data to a signature dictionary.

        Args:
            sig_version (int, optional):
                The signature version to serialize as. This always defaults
                to the latest.

        Returns:
            dict:
            The serialized data.

        Raises:
            django_evolution.errors.InvalidSignatureVersion:
                The signature version provided isn't supported.
        """
        validate_sig_version(sig_version)

        app_sig_dict = OrderedDict()

        if sig_version == 2:
            app_sig_dict['legacy_app_label'] = self.legacy_app_label

            if self.upgrade_method:
                app_sig_dict['upgrade_method'] = self.upgrade_method

                if self.upgrade_method == UpgradeMethod.MIGRATIONS:
                    app_sig_dict['applied_migrations'] = \
                        sorted(self.applied_migrations or [])

            # Add an ordered dictionary of models to the signature.
            model_sigs_dict = OrderedDict()
            app_sig_dict['models'] = model_sigs_dict
        elif sig_version == 1:
            model_sigs_dict = app_sig_dict

        for model_name, model_sig in six.iteritems(self._model_sigs):
            model_sigs_dict[model_name] = model_sig.serialize(sig_version)

        return app_sig_dict

    def __eq__(self, other):
        """Return whether two application signatures are equal.

        Args:
            other (AppSignature):
                The other application signature.

        Returns:
            bool:
            ``True`` if the application signatures are equal. ``False`` if
            they are not.
        """
        return (other is not None and
                self.app_id == other.app_id and
                self.legacy_app_label == other.legacy_app_label and
                self.upgrade_method == other.upgrade_method and
                self.applied_migrations == other.applied_migrations and
                dict.__eq__(self._model_sigs, other._model_sigs))

    def __repr__(self):
        """Return a string representation of the signature.

        Returns:
            unicode:
            A string representation of the signature.
        """
        return ('<AppSignature(app_id=%r, legacy_app_label=%r,'
                ' upgrade_method=%r, models=%r)>'
                % (self.app_id, self.legacy_app_label, self.upgrade_method,
                   list(six.iterkeys(self._model_sigs)))) class MigrationsTestsMixin(object):
    """Mixin for test suites that work with migrations.

    This will ensure that no test migrations are marked as applied before
    the tests run.
    """

    needs_evolution_models = True

    def setUp(self):
        super(MigrationsTestsMixin, self).setUp()

        if supports_migrations:
            connection = connections[DEFAULT_DB_ALIAS]

            for app_label in ('tests', 'migrations_app', 'migrations_app2'):
                unrecord_applied_migrations(connection=connection,
                                            app_label=app_label) class TestCase(DjangoTestCase):
    """Base class for all Django Evolution test cases."""

    ws_re = re.compile(r'\s+')

    needs_evolution_models = False

    # Allow for diffs for large dictionary structures, to help debug
    # signature failures.
    maxDiff = 10000

    # The list of databases we may test against, required by Django 2.2+.
    databases = ['default', 'db_multi']

    default_database_name = 'default'
    sql_mapping_key = None

    builtin_test_app_labels = [
        'evolution_deps_app',
        'evolutions_app',
        'evolutions_app2',
        'migrations_app',
        'migrations_app2',
        'tests',
    ]

    # Override some internal functions on Django's TestCase to avoid setting
    # up transactions. We won't bother documenting these, since we're just
    # trying to turn off logic.
    if django.VERSION[:2] >= (1, 8):
        # Django >= 1.8
        @classmethod
        def _enter_atomics(cls):
            return

        @classmethod
        def _rollback_atomics(cls, atomics):
            return
    else:
        # Django <= 1.7
        def _fixture_setup(self):
            return

        def _fixture_teardown(self):
            return

    def setUp(self):
        super(TestCase, self).setUp()

        # If these are in the database, we need to delete them so we don't
        # carry signatures between tests.
        try:
            Evolution.objects.all().delete()
            Version.objects.all().delete()
        except Exception:
            pass

        # If the test case indicates it needs the Evolution and Version
        # models pre-populated, then do that now.
        if self.needs_evolution_models:
            self.ensure_evolution_models()

    def tearDown(self):
        super(TestCase, self).tearDown()

        self.ensure_deleted_apps()
        unregister_test_models()

    def shortDescription(self):
        """Returns the description of the current test.

        This changes the default behavior to replace all newlines with spaces,
        allowing a test description to span lines. It should still be kept
        short, though.
        """
        doc = self._testMethodDoc

        if doc is not None:
            doc = doc.split('\n\n', 1)[0]
            doc = self.ws_re.sub(' ', doc).strip()

        return doc

    def ensure_evolution_models(self):
        """Create the Evolution and Version models if missing."""
        Evolver()
        assert Version.objects.exists()

    def ensure_deleted_apps(self, app_labels=None):
        """Ensure an app's models and evolutions aren't in the database.

        Args:
            app_labels (list of unicode, optional):
                An explicit list of app labels to delete. This defaults
                to the built-in list of test app labels.
        """
        if app_labels is None:
            app_labels = self.builtin_test_app_labels

        # Do this one-by-one, since we might have some but not all of the
        # apps in the database.
        sql = []

        for app_label in reversed(app_labels):
            try:
                app = get_app(app_label)
            except Exception:
                # This wasn't registered, so we can skip it.
                continue

            sql += sql_delete(app)

        if sql:
            execute_test_sql(sql)

        Evolution.objects.filter(app_label__in=app_labels).delete()

    def ensure_evolved_apps(self, apps):
        """Ensure an app's models and evolutions are applied to the database.

        Args:
            apps (list of module):
                The app modules to evolve.
        """
        evolver = Evolver()

        for app in apps:
            evolver.queue_evolve_app(app)

        evolver.evolve()

    def record_evolutions(self, version, evolutions,
                          database=DEFAULT_DB_ALIAS):
        """Record evolutions in the database.

        This is a convenience around creating and saving `Evolution` models.

        Args:
            version (django_evolution.models.Version):
                The version to associate the evolutions with.

            evolutions (list of tuple):
                The list of evolutions to store. Each item is a tuple in
                ``(app_label, label)`` form.

            database (unicode, optional):
                The name of the database to save these on.
        """
        Evolution.objects.using(database).bulk_create([
            Evolution(version=version,
                      app_label=app_label,
                      label=label)
            for app_label, label in evolutions
        ])

    def record_applied_migrations(self, migration_targets,
                                  database=DEFAULT_DB_ALIAS):
        """Record applied migrations in the database.

        This is a convenience around creating a migration list and then
        recording it in the database.

        Args:
            migration_targets (list of tuple):
                The list of migration targets to store. Each item is a tuple
                in ``(app_label, label)`` form.

            database (unicode, optional):
                The name of the database to save these on.
        """
        assert supports_migrations

        migration_list = MigrationList()
        migration_list.add_migration_targets(migration_targets)

        record_applied_migrations(connection=connections[database],
                                  migrations=migration_list)

    def get_sql_mapping(self, name, sql_mappings_key=None, db_name=None):
        """Return the SQL for the given mapping name and database.

        Args:
            name (unicode):
                The registered name in the list of SQL mappings for this test
                suite and database.

            sql_mappings_key (unicode, optional):
                The key identifying the SQL mappings. This defaults to
                :py:attr:`sql_mapping_key`.

            db_name (unicode, optional):
                The name of the database to return SQL mappings for.

        Returns:
            list of unicode:
            The resulting list of SQL statements.

        Raises:
            ValueError:
                The provided name is not valid.
        """
        sql_mappings_key = sql_mappings_key or self.sql_mapping_key
        assert sql_mappings_key

        db_name = db_name or self.default_database_name
        assert db_name

        sql_mappings = get_sql_mappings(mapping_key=sql_mappings_key,
                                        db_name=db_name)

        try:
            sql = sql_mappings[name]
        except KeyError:
            raise ValueError('"%s" is not a valid SQL mapping name.'
                             % name)

        if isinstance(sql, six.text_type):
            sql = sql.splitlines()

        return sql

    def assertEvolutionsEqual(self, evolutions, expected_evolutions):
        """Assert that a list of evolutions models match expectations.

        Args:
            evolutions (list of django_evolution.models.Evolution):
                The list of evolution models.

            expected_evolutions (list of tuple):
                A list of evolution information to compare against the models.
                Each item is a tuple in ``(app_label, label)`` form.

        Raises:
            AssertionError:
                The lists do not match.
        """
        self.assertEqual(
            [
                (evolution.app_label, evolution.label)
                for evolution in evolutions
            ],
            expected_evolutions)

    def assertAppliedEvolutions(self, expected_evolutions, version=None,
                                database=DEFAULT_DB_ALIAS):
        """Assert that applied evolutions match expectations.

        Args:
            expected_evolutions (list of tuple):
                A list of evolution information to compare against the models.
                Each item is a tuple in ``(app_label, label)`` form.

            version (django_evolution.models.Version, optional):
                The stored project version that the evolutions must be
                associated with. If not provided, all evolutions in the
                database will be used.

            database (unicode, optional):
                The name of the database to query evolutions against. This
                is only used if ``version`` is not provided.

        Raises:
            AssertionError:
                The lists do not match.
        """
        if version is None:
            queryset = Evolution.objects.using(database)
        else:
            queryset = version.evolutions.all()

        applied_evolutions = set(queryset.values_list('app_label', 'label'))

        for app_label, name in expected_evolutions:
            self.assertIn((app_label, name), applied_evolutions)

    def assertAppliedMigrations(self, expected_migration_targets,
                                database=DEFAULT_DB_ALIAS):
        """Assert that applied migrations match expectations.

        Args:
            expected_migration_targets (list of tuple):
                A list of migration targets to compare against the applied
                migrations. Each item is a tuple in ``(app_label, name)``
                form.

            database (unicode, optional):
                The name of the database to query appliedm migrations against.

        Raises:
            AssertionError:
                The lists do not match.
        """
        applied_migrations = MigrationList.from_database(connections[database])

        for app_label, name in expected_migration_targets:
            self.assertTrue(applied_migrations.has_migration_info(
                app_label=app_label,
                name=name))

    def assertSQLMappingEqual(self, sql, sql_mapping_name,
                              sql_mappings_key=None, database=None):
        """Assert generated SQL against database-specific mapped test SQL.

        This will output the provided generated SQL and the expectation test
        SQL mapped by the given key and optional database name, for debugging,
        and will then compare the contents of both.

        The expected SQL may contain regexes, which are used for comparing
        against generated SQL that may depend on some dynamic value pulled from
        the database). If found, the pattern in the regex will be applied to
        the corresponding generated SQL to determine if there's a match. Other
        lines will be compared directly.

        If any part of the SQL does not match, a diff will be shown in the
        test output.

        Args:
            sql (list of unicode):
                The list of generated SQL statements.

            sql_mapping_name (unicode):
                The mapping name in the database-specific test data to compare
                against.

            sql_mappings_key (unicode, optional):
                The key identifying the SQL mappings. This defaults to
                :py:attr:`sql_mapping_key`.

            database (unicode, optional):
                An explicit database name to use for resolving
                ``sql_mapping_name``.

        Raises:
            AssertionError:
                The generated and expected SQL did not match.
        """
        if database is None:
            database = DEFAULT_DB_ALIAS

        # Normalize the generated and expected SQL so that we are
        # guaranteed to have a list with one item per line.
        try:
            generated_sql = '\n'.join(sql).splitlines()
        except TypeError:
            # This probably has an entry that isn't normalized to a flat list
            # of strings. Do that now.
            from django_evolution.utils.sql import SQLExecutor

            with SQLExecutor(database) as sql_executor:
                sql = sql_executor.run_sql(sql,
                                           capture=True,
                                           execute=False)

            generated_sql = '\n'.join(sql).splitlines()

        expected_sql = self.get_sql_mapping(name=sql_mapping_name,
                                            sql_mappings_key=sql_mappings_key,
                                            db_name=database)

        # Output the statements one-by-one, to help with diagnosing
        # differences.

        print()
        print("** Comparing SQL against '%s'" % sql_mapping_name)
        print('** Generated:')
        print()

        for line in generated_sql:
            print('    %s' % line)

        print()
        print('** Expected:')
        print()

        has_regex = False

        for line in expected_sql:
            if (not isinstance(line, six.text_type) and
                hasattr(line, 'pattern')):
                line = '/%s/' % line.pattern
                has_regex = True

            print('    %s' % line)

        print()

        if has_regex:
            # We can't compare directly at first, so let's see if things
            # are otherwise a match and then, if we spot anything wrong,
            # we'll just do an assertListEqual to get detailed output.
            match = (len(generated_sql) == len(expected_sql))

            if match:
                for gen_line, expected_line in zip(generated_sql,
                                                   expected_sql):
                    if ((isinstance(expected_line, six.text_type) and
                         gen_line != expected_line) or
                        (hasattr(line, 'pattern') and
                         not line.match(gen_line))):
                        match = False
                        break

            if not match:
                # Now show that detailed output.
                self.assertListEqual(generated_sql, expected_sql)
        else:
            # Compare as lists, so that we can better spot inconsistencies.
            self.assertListEqual(generated_sql, expected_sql) def _build_requires_support_decorator(flag, skip_message):
    def _decorator(func):
        def _wrapper(*args, **kwargs): class BaseTestModel(models.Model):
    """Base class for test-created models.

    This is intended to be used for all models that are created during unit
    test runs. It sets the appropriate app to ensure that models aren't
    grouped under the ``django_evolution`` app.
    """

    class Meta:
        abstract = True
        app_label = 'tests' def register_models(database_state, models, register_indexes=False,
                    new_app_label='tests', db_name='default', app=evo_test):
    """Register models for testing purposes.

    Args:
        database_state (django_evolution.db.state.DatabaseState):
            The database state to populate with model information.

        models (list of django.db.models.Model):
            The models to register.

        register_indexes (bool, optional):
            Whether indexes should be registered for any models. Defaults to
            ``False``.

        new_app_label (str, optional):
            The label for the test app. Defaults to "tests".

        db_name (str, optional):
            The name of the database connection. Defaults to "default".

        app (module, optional):
            The application module for the test models.

    Returns:
        collections.OrderedDict:
        A dictionary of registered models. The keys are model names, and
        the values are the models.
    """
    app_cache = OrderedDict()
    evolver = EvolutionOperationsMulti(db_name, database_state).get_evolver()

    db_connection = connections[db_name or DEFAULT_DB_ALIAS]
    max_name_length = db_connection.ops.max_name_length()

    for new_object_name, model in reversed(models):
        # Grab some state from the model's meta instance. Some of this will
        # be original state that we'll keep around to help us unregister old
        # values and compute new ones.
        meta = model._meta

        orig_app_label = meta.app_label
        orig_db_table = meta.db_table
        orig_object_name = meta.object_name
        orig_model_name = get_model_name(model)

        # Find out if the table name being used is a custom table name, or
        # one generated by Django.
        new_model_name = new_object_name.lower()
        new_db_table = orig_db_table

        generated_db_table = truncate_name(
            '%s_%s' % (orig_app_label, orig_model_name),
            max_name_length)

        if orig_db_table == generated_db_table:
            # It was a generated one, so replace it with a version containing
            # the new model and app names.
            new_db_table = truncate_name('%s_%s' % (new_app_label,
                                                    new_model_name),
                                         max_name_length)
            meta.db_table = new_db_table

        # Set the new app/model names back on the meta instance.
        meta.app_label = new_app_label
        meta.object_name = new_object_name
        set_model_name(model, new_model_name)

        # Add an entry for the table in the database state, if it's not
        # already there.
        if not database_state.has_table(new_db_table):
            database_state.add_table(new_db_table)

        if register_indexes:
            # Now that we definitely have an entry, store the indexes for
            # all the fields in the database state, so that other operations
            # can look up the index names.
            for field in meta.local_fields:
                if field.db_index or field.unique:
                    new_index_name = create_index_name(
                        db_connection,
                        new_db_table,
                        field_names=[field.name],
                        col_names=[field.column],
                        unique=field.unique)

                    database_state.add_index(
                        index_name=new_index_name,
                        table_name=new_db_table,
                        columns=[field.column],
                        unique=field.unique)

            for field_names in meta.unique_together:
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_name(
                    db_connection,
                    new_db_table,
                    field_names=field_names,
                    unique=True)

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields],
                    unique=True)

            for field_names in getattr(meta, 'index_together', []):
                # Django >= 1.5
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_together_name(
                    db_connection,
                    new_db_table,
                    field_names=[field.name for field in fields])

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields])

            if getattr(meta, 'indexes', None):
                # Django >= 1.11
                for index, orig_index in zip(meta.indexes,
                                             meta.original_attrs['indexes']):
                    if not orig_index.name:
                        # The name was auto-generated. We'll need to generate
                        # it again for the new table name.
                        index.set_name_with_model(model)

                    fields = evolver.get_fields_for_names(
                        model, index.fields, allow_sort_prefixes=True)
                    database_state.add_index(
                        index_name=index.name,
                        table_name=new_db_table,
                        columns=[field.column for field in fields])

        # ManyToManyFields have their own tables, which will also need to be
        # renamed. Go through each of them and figure out what changes need
        # to be made.
        for field in meta.local_many_to_many:
            through = get_remote_field(field).through

            if not through:
                continue

            through_meta = through._meta
            through_orig_model_name = get_model_name(through)
            through_new_model_name = through_orig_model_name

            # Find out if the through table name is a custom table name, or
            # one generated by Django.
            generated_db_table = truncate_name(
                '%s_%s' % (orig_db_table, field.name),
                max_name_length)

            if through_meta.db_table == generated_db_table:
                # This is an auto-generated table name. Start changing the
                # state for it.
                assert through_meta.app_label == orig_app_label
                through_meta.app_label = new_app_label

                # Transform the 'through' table information only if we've
                # transformed the parent db_table.
                if new_db_table != orig_db_table:
                    through_meta.db_table = truncate_name(
                        '%s_%s' % (new_db_table, field.name),
                        max_name_length)

                    through_meta.object_name = \
                        through_meta.object_name.replace(orig_object_name,
                                                         new_object_name)

                    through_new_model_name = \
                        through_orig_model_name.replace(orig_model_name,
                                                        new_model_name)
                    set_model_name(through, through_new_model_name)

            # Change each of the columns for the fields on the
            # ManyToManyField's model to reflect the new model names.
            for through_field in through._meta.local_fields:
                through_remote_field = get_remote_field(through_field)

                if (through_remote_field and
                    get_remote_field_model(through_remote_field)):
                    column = through_field.column

                    if (column.startswith((orig_model_name,
                                           'to_%s' % orig_model_name,
                                           'from_%s' % orig_model_name))):
                        # This is a field that references one end of the
                        # relation or another. Update the model naem in the
                        # field's column.
                        through_field.column = column.replace(orig_model_name,
                                                              new_model_name)

            # Replace the entry in the models cache for the through table,
            # removing the old name and adding the new one.
            if through_orig_model_name in all_models[orig_app_label]:
                unregister_app_model(orig_app_label, through_orig_model_name)

            app_cache[through_new_model_name] = through
            register_app_models(new_app_label,
                                [(through_new_model_name, through)])
            _registered_test_models.append((new_app_label,
                                            through_new_model_name))

        # Unregister with the old model name and register the new one.
        if orig_model_name in all_models[orig_app_label]:
            unregister_app_model(orig_app_label, orig_model_name)

        register_app_models(new_app_label, [(new_model_name, model)])
        app_cache[new_model_name] = model

        _registered_test_models.append((new_app_label, new_model_name))

    # If the app hasn't yet been registered, do that now.
    if not is_app_registered(app):
        register_app(new_app_label, app)

    return app_cache class MigrationExecutor(DjangoMigrationExecutor):
    """Load and execute migrations.

    This is a specialization of Django's own
    :py:class:`~django.db.migrations.executor.MigrationExecutor` that allows
    for providing additional migrations not available on disk, and for
    emitting our own signals when processing migrations.
    """

    def __init__(self, connection, custom_migrations=None, signal_sender=None):
        """Initialize the executor.

        Args:
            connection (django.db.backends.base.BaseDatabaseWrapper):
                The connection to load applied migrations from.

            custom_migrations (dict, optional):
                Custom migrations not available on disk. Each key is a tuple
                of ``(app_label, migration_name)``, and each value is a
                migration.

            signal_sender (object, optional):
                A custom sender to pass when sending signals. This defaults
                to this instance.
        """
        self._signal_sender = signal_sender or self

        super(MigrationExecutor, self).__init__(
            connection=connection,
            progress_callback=self._on_progress)

        # Ideally we would be able to replace this during initialization,
        # or at the very least prevent the default one from loading from
        # disk, but it's not often that these will be constructed, so it's
        # probably fine.
        self.loader = MigrationLoader(connection=connection,
                                      custom_migrations=custom_migrations)

    def run_checks(self):
        """Perform checks on the migrations and any history.

        Raises:
            django_evolution.errors.MigrationConflictsError:
                There are conflicts between migrations loaded from disk.

            django_evolution.errors.MigrationHistoryError:
                There are unapplied dependencies to applied migrations.
        """
        # Make sure that the migration files in the tree form a proper history.
        if hasattr(self.loader, 'check_consistent_history'):
            # Django >= 1.10
            from django.db.migrations.exceptions import \
                InconsistentMigrationHistory

            try:
                self.loader.check_consistent_history(self.connection)
            except InconsistentMigrationHistory as e:
                raise MigrationHistoryError(six.text_type(e))

        # Now check that there aren't any conflicts between any migrations that
        # we may end up working with.
        conflicts = self.loader.detect_conflicts()

        if conflicts:
            raise MigrationConflictsError(conflicts)

    def _on_progress(self, action, migration=None, *args, **kwargs):
        """Handler for progress notifications.

        This will convert certain progress notifications to Django Evolution
        signals.

        Args:
            action (unicode):
                The action reflecting the progress update.

            migration (django.db.migrations.Migration, optional):
                The migration that the progress update applies to. This is
                not provided for all progress updates.

            *args (tuple, unused):
                Additional positional arguments passed for the update.

            **kwargs (dict, unused):
                Additional keyword arguments passed for the update.
        """
        if action == 'apply_start':
            applying_migration.send(sender=self._signal_sender,
                                    migration=migration)
        elif action == 'apply_success':
            applied_migration.send(sender=self._signal_sender,
                                   migration=migration) class MigrationList(object):
    """A list of applied or pending migrations.

    This is used to manage a list of migrations in a way that's independent
    from the underlying representation used in Django. Migrations are tracked
    by app label and name, may be associated with a recorded migration
    database entry, and can be used to convert state to and from both
    signatures and Django migration state.
    """

    @classmethod
    def from_app_sig(cls, app_sig):
        """Create a MigrationList based on an app signature.

        Args:
            app_sig (django_evolution.signature.AppSignature):
                The app signature containing a list of applied migrations.

        Returns:
            MigrationList:
            The new migration list.
        """
        return cls.from_names(app_label=app_sig.app_id,
                              migration_names=app_sig.applied_migrations)

    @classmethod
    def from_names(cls, app_label, migration_names):
        """Create a MigrationList based on a list of migration names.

        Version Added:
            2.1

        Args:
            app_label (unicode):
                The app label common to each migration name.

            migration_names (list of unicode):
                The list of migration names.

        Returns:
            MigrationList:
            The new migration list.
        """
        migration_list = cls()

        if migration_names:
            for name in migration_names:
                migration_list.add_migration_info(app_label=app_label,
                                                  name=name)

        return migration_list

    @classmethod
    def from_database(cls, connection, app_label=None):
        """Create a MigrationList based on recorded migrations.

        Args:
            connection (django.db.backends.base.BaseDatabaseWrapper):
                The database connection used to query for migrations.

            app_label (unicode, optional):
                An app label to filter migrations by.

        Returns:
            MigrationList:
            The new migration list.
        """
        recorder = MigrationRecorder(connection)
        recorder.ensure_schema()

        migration_list = cls()
        queryset = recorder.migration_qs

        if app_label:
            queryset = queryset.filter(app=app_label)

        for recorded_migration in queryset.all():
            migration_list.add_recorded_migration(recorded_migration)

        return migration_list

    def __init__(self):
        """Initialize the list."""
        self._by_app_label = {}
        self._by_id = {}

    def has_migration_info(self, app_label, name):
        """Return whether the list contains an entry for a migration.

        Args:
            app_label (unicode):
                The label for the application that was migrated.

            name (unicode):
                The name of the migration.

        Returns:
            bool:
            ``True`` if the migration is in the list. ``False`` if it is not.
        """
        return (app_label, name) in self._by_id

    def add_migration_targets(self, targets):
        """Add a list of migration targets to the list.

        Args:
            targets (list of tuple):
                The migration targets to each. Each is a tuple containing
                an app label and a migration name.
        """
        for app_label, name in targets:
            self.add_migration_info(app_label=app_label,
                                    name=name)

    def add_migration(self, migration):
        """Add a migration to the list.

        This can only be called on Django 1.7 or higher.

        Args:
            migration (django.db.migrations.Migration):
                The migration instance to add.
        """
        assert Migration is not None
        assert isinstance(migration, Migration)

        self.add_migration_info(app_label=migration.app_label,
                                name=migration.name,
                                migration=migration)

    def add_recorded_migration(self, recorded_migration):
        """Add a recorded migration to the list.

        This can only be called on Django 1.7 or higher.

        Args:
            recorded_migration (django.db.migrations.recorder.
                                MigrationRecorder.Migration):
                The recorded migration model to add.
        """
        assert MigrationRecorder is not None
        assert isinstance(recorded_migration, MigrationRecorder.Migration)

        self.add_migration_info(app_label=recorded_migration.app,
                                name=recorded_migration.name,
                                recorded_migration=recorded_migration)

    def add_migration_info(self, app_label, name, migration=None,
                           recorded_migration=None):
        """Add information on a migration to the list.

        Args:
            app_label (unicode):
                The label for the application that was migrated.

            name (unicode):
                The name of the migration.

            migration (django.db.migrations.Migration, optional):
                An optional migration instance to associate with this entry.

            recorded_migration (django.db.migrations.recorder.
                                MigrationRecorder.Migration, optional):
                An optional recorded migration to associate with this entry.
        """
        info = {
            'app_label': app_label,
            'migration': migration,
            'name': name,
            'recorded_migration': recorded_migration,
        }

        self._by_app_label.setdefault(app_label, []).append(info)
        self._by_id[(app_label, name)] = info

    def update(self, other):
        """Update the list with the contents of another list.

        If there's an entry in another list matching this one, and contains
        information that the entry in this list does not have, this list's
        entry will be updated.

        Args:
            other (MigrationList):
                The list of migrations to put into this list.
        """
        for other_info in other:
            app_label = other_info['app_label']
            name = other_info['name']
            info = self._by_id.get((app_label, name))

            if info is None:
                self.add_migration_info(app_label=app_label,
                                        name=name)
            else:
                for key in ('migration', 'recorded_migration'):
                    if info[key] is None:
                        info[key] = other_info[key]

    def to_targets(self):
        """Return a set of migration targets based on this list.

        Returns:
            set:
            A set of migration targets. Each entry is a tuple containing
            the app label and name.
        """
        return set(
            (info['app_label'], info['name'])
            for info in self
        )

    def get_app_labels(self):
        """Iterate through the app labels.

        Results are sorted alphabetically.

        Returns:
            list of unicode:
            The sorted list of app labels with associated migrations.
        """
        return list(sorted(six.iterkeys(self._by_app_label)))

    def clone(self):
        """Clone the list.

        Returns:
            MigrationList:
            The cloned migration list.
        """
        new_migration_list = MigrationList()

        for info in self:
            new_migration_list.add_migration_info(**info)

        return new_migration_list

    def __bool__(self):
        """Return whether this list is truthy or falsy.

        The list is truthy only if it has items.

        Returns:
            bool:
            ``True`` if the list has items. ``False`` if it's empty.
        """
        return bool(self._by_id)

    def __len__(self):
        """Return the number of items in the list.

        Returns:
            int:
            The number of items in the list.
        """
        return len(self._by_id)

    def __eq__(self, other):
        """Return whether this list is equal to another list.

        The order of migrations is ignored when comparing lists.

        Args:
            other (MigrationList):
                A list of migrations to compare to.

        Returns:
            bool:
            ``True`` if the two lists have the same contents. ``False`` if
            there are differences in contents, or ``other`` is not a
            :py:class:`MigrationList`.
        """
        if other is None or not isinstance(other, MigrationList):
            return False

        return self._by_id == other._by_id

    def __iter__(self):
        """Iterate through the list.

        Entries are sorted first by app label, alphabetically, and then
        the order in which migrations were added for that app label.

        Yields:
            info:
            A dictionary containing the following keys:

            ``app_label`` (:py:class:`unicode`):
                The app label for the migration.

            ``name`` (:py:class:`unicode`):
                The name of the migration.

            ``migration`` (:py:class:`django.db.migrations.Migration`):
                The optional migration instance.

            ``recorded_migration`` (:py:class:`django.db.migrations.recorder.MigrationRecorder.Migration`):
                The optional recorded migration.
        """
        for app_label, info_list in sorted(six.iteritems(self._by_app_label),
                                           key=lambda pair: pair[0]):
            for info in info_list:
                yield info

    def __add__(self, other):
        """Return a combined copy of this list and another list.

        Args:
            other (MigrationList):
                The other list to add to this list.

        Returns:
            MigrationList:
            The new migration list containing contents of both lists.
        """
        new_migration_list = self.clone()
        new_migration_list.update(other)

        return new_migration_list

    def __sub__(self, other):
        """Return a copy of this list with another list's contents excluded.

        Args:
            other (MigrationList):
                The other list containing contents to exclude.

        Returns:
            MigrationList:
            The new migration list containing the contents of this list that
            don't exist in the other list.
        """
        new_migration_list = MigrationList()

        for info in self:
            if not other.has_migration_info(app_label=info['app_label'],
                                            name=info['name']):
                new_migration_list.add_migration_info(**info)

        return new_migration_list

    def __repr__(self):
        """Return a string representation of this list.

        Returns:
            unicode:
            The string representation.
        """
        return '<MigrationList%s>' % list(self) class MigrationLoader(DjangoMigrationLoader):
    """Loads migration files from disk.

    This is a specialization of Django's own
    :py:class:`~django.db.migrations.loader.MigrationLoader` that allows for
    providing additional migrations not available on disk.

    Attributes:
        extra_applied_migrations (MigrationList):
            Migrations to mark as already applied. This can be used to
            augment the results calculated from the database.
    """

    def __init__(self, connection, custom_migrations=None, *args, **kwargs):
        """Initialize the loader.

        Args:
            connection (django.db.backends.base.BaseDatabaseWrapper):
                The connection to load applied migrations from.

            custom_migrations (MigrationList, optional):
                Custom migrations not available on disk.

            *args (tuple):
                Additional positional arguments for the parent class.

            **kwargs (dict):
                Additional keyword arguments for the parent class.
        """
        self._custom_migrations = custom_migrations or MigrationList()
        self._applied_migrations = None
        self._lock_migrations = False

        self.extra_applied_migrations = MigrationList()

        super(MigrationLoader, self).__init__(connection, *args, **kwargs)

    @property
    def applied_migrations(self):
        """The migrations already applied.

        This will contain both the migrations applied from the database
        and any set in :py:attr:`extra_applied_migrations`.
        """
        extra_migrations = self.extra_applied_migrations

        if isinstance(self._applied_migrations, dict):
            # Django >= 3.0
            applied_migrations = self._applied_migrations.copy()

            for info in extra_migrations:
                app_label = info['app_label']
                name = info['name']
                recorded_migration = info['recorded_migration']

                if recorded_migration is None:
                    recorded_migration = MigrationRecorder.Migration(
                        app=app_label,
                        name=name,
                        applied=True)

                applied_migrations[(app_label, name)] = recorded_migration

        elif isinstance(self._applied_migrations, set):
            # Django < 3.0
            applied_migrations = self._applied_migrations | set(
                (info['app_label'], info['name'])
                for info in extra_migrations
            )
        else:
            raise DjangoEvolutionSupportError(
                'Migration.applied_migrations is an unexpected type (%s)'
                % type(self._applied_migrations))

        return applied_migrations

    @applied_migrations.setter
    def applied_migrations(self, value):
        """Set the migrations already applied.

        Args:
            value (set of tuple):
                The migrations already applied to the database.
        """
        if value is not None and not isinstance(value, (dict, set)):
            raise DjangoEvolutionSupportError(
                'Migration.applied_migrations was set to an unexpected type '
                '(%s)'
                % type(value))

        if value is None:
            self._applied_migrations = None
        else:
            if django_version >= (3, 0):
                self._applied_migrations = dict(value)
            else:
                self._applied_migrations = value

    def build_graph(self, reload_migrations=True):
        """Rebuild the migrations graph.

        Args:
            reload_migrations (bool, optional):
                Whether to reload migration instances from disk. If ``False``,
                the ones loaded before will be used.
        """
        if not reload_migrations:
            self._lock_migrations = True

        try:
            super(MigrationLoader, self).build_graph()
        finally:
            self._lock_migrations = False

    def load_disk(self):
        """Load migrations from disk.

        This will also load any custom migrations.
        """
        if self._lock_migrations:
            return

        super(MigrationLoader, self).load_disk()

        for info in self._custom_migrations:
            migration = info['migration']
            assert migration is not None

            app_label = info['app_label']
            name = info['name']

            self.migrated_apps.add(app_label)
            self.unmigrated_apps.discard(app_label)
            self.disk_migrations[(app_label, name)] = migration def apply_migrations(executor, targets, plan, pre_migrate_state):
    """Apply migrations to the database.

    Migrations will be applied using the ``fake_initial`` mode, which means
    that any initial migrations (those constructing the models for an app)
    will be skipped if the models already appear in the database. This is to
    avoid issues with applying those migrations when the models have already
    been created in the past outside of Django's Migrations framework. In
    theory, this could cause some issues if those migrations also perform
    other important operations around data population, but this is really up
    to Django to handle, as this is part of the upgrade method when going
    from pre-1.7 to 1.7+ anyway.

    This can only be called when on Django 1.7 or higher.

    Args:
        executor (django.db.migrations.executor.MigrationExecutor):
            The migration executor that will handle applying the migrations.

        targets (list of tuple):
            The list of migration targets to apply.

        plan (list of tuple):
            The order in which migrations will be applied.

        pre_migrate_state (object):
            The pre-migration state needed to apply these migrations.
            This must be generated with :py:func:`create_pre_migrate_state`
            or a previous call to :py:func:`apply_migrations`.

    Returns:
        object:
        The state generated from applying migrations. Any final state must
        be passed to :py:func:`finalize_migrations`.
    """
    assert supports_migrations, \
        'This cannot be called on Django 1.6 or earlier.'

    migrate_kwargs = {
        'fake': False,
        'plan': plan,
        'targets': targets,
    }

    # Build version-dependent state needed for the signals and migrate
    # operation.
    if django_version >= (1, 8):
        # Mark any migrations that introduce new models that are already in
        # the database as applied.
        migrate_kwargs['fake_initial'] = True

    if django_version >= (1, 10):
        migrate_kwargs['state'] = pre_migrate_state.clone()

    # Perform the migration and record the result. This only returns a value
    # on Django >= 1.10.
    return executor.migrate(**migrate_kwargs) def create_pre_migrate_state(executor):
    """Create state needed before migrations are applied.

    The return value is dependent on the version of Django.

    Args:
        executor (django.db.migrations.executor.MigrationExecutor):
            The migration executor that will handle the migrations.

    Returns:
        django.db.migrations.state.ProjectState:
        The state needed for applying migrations.
    """
    assert supports_migrations, \
        'This cannot be called on Django 1.6 or earlier.'

    if django_version >= (1, 10):
        # Unfortunately, we have to call into a private method here, just as
        # the migrate command does. Ideally, this would be official API.
        return executor._create_project_state(with_applied_migrations=True)

    return None def filter_migration_targets(targets, app_labels=None, exclude=None):
    """Filter migration execution targets based on the given criteria.

    Args:
        targets (list of tuple):
            The migration targets to be executed.

        app_labels (set of unicode, optional):
            The app labels to limit the targets to.

        exclude (set, optional):
            Explicit targets to exclude.

    Returns:
        list of tuple:
        The resulting list of migration targets.
    """
    if app_labels is not None:
        if not isinstance(app_labels, set):
            app_labels = set(app_labels)

        targets = (
            target
            for target in targets
            if target[0] in app_labels
        )

    if exclude:
        if not isinstance(exclude, set):
            exclude = set(exclude)

        targets = (
            target
            for target in targets
            if target not in exclude
        )

    return list(targets) def finalize_migrations(post_migrate_state):
    """Finalize any migrations operations.

    This will update any internal state in Django for any migrations that
    were applied and represented by the provided post-migrate state.

    Args:
        post_migrate_state (object):
            The state generated from applying migrations. This must be the
            result of :py:meth:`apply_migrations`.
    """
    assert supports_migrations, \
        'This cannot be called on Django 1.6 or earlier.'

    if django_version >= (1, 10):
        # On Django 1.10, we have a few more steps for generating the state
        # needed for the signal.
        if django_version >= (1, 11):
            post_migrate_state.clear_delayed_apps_cache()

        post_migrate_apps = post_migrate_state.apps
        assert post_migrate_apps is not None

        model_keys = []

        with post_migrate_apps.bulk_update():
            for model_state in post_migrate_apps.real_models:
                model_key = (model_state.app_label, model_state.name_lower)
                model_keys.append(model_key)
                post_migrate_apps.unregister_model(*model_key)

        post_migrate_apps.render_multiple([
            ModelState.from_model(get_model(*model))
            for model in model_keys
        ]) def has_migrations_module(app):
    """Return whether an app has a migrations module.

    Args:
        app (module):
            The app module.

    Returns:
        bool:
        ``True`` if the app has a ``migrations`` module. ``False`` if it
        does not.
    """
    app_name = get_app_name(app)

    try:
        import_module('%s.migrations' % app_name)
        return True
    except ImportError:
        return False def is_migration_initial(migration):
    """Return whether a migration is an initial migration.

    Initial migrations are those that set up an app or models for the first
    time. Generally, they should be limited to model creations, or to those
    adding fields to a (non-migration-aware) model for the first time. They
    also should not have any dependencies on other migrations within the same
    app.

    An initial migration should be able to be safely soft-applied (in other
    words, ignored if the model already appears to exist in the database).

    Migrations on Django 1.9+ may declare themselves as explicitly initial
    or explicitly not initial.

    Args:
        migration (django.db.migrations.Migration):
            The migration to check.

    Returns:
        bool:
        ``True`` if the migration appears to be an initial migration.
        ``False`` if it does not.
    """
    # NOTE: The general logic here is based on the checks done in
    #       MigrationExecutor.detect_soft_applied.

    # Migration.initial was introduced in Django 1.9.
    initial = getattr(migration, 'initial', None)

    if initial is False:
        return False
    elif initial is None:
        # If the migration has any dependencies within the same app, it can't
        # be initial.
        for dep_app_label, dep_app_name in migration.dependencies:
            if dep_app_label == migration.app_label:
                return False

    return True def record_applied_migrations(connection, migrations):
    """Record a list of applied migrations to the database.

    This can only be called when on Django 1.7 or higher.

    Args:
        connection (django.db.backends.base.BaseDatabaseWrapper):
            The connection used to record applied migrations.

        migrations (MigrationList):
            The list of migration targets to record as applied.
    """
    assert supports_migrations, \
        'This cannot be called on Django 1.6 or earlier.'

    recorder = MigrationRecorder(connection)
    recorder.ensure_schema()

    recorder.migration_qs.bulk_create(
        recorder.Migration(app=info['app_label'],
                           name=info['name'])
        for info in migrations
    ) def unrecord_applied_migrations(connection, app_label, migration_names=None):
    """Remove the recordings of applied migrations from the database.

    This can only be called when on Django 1.7 or higher.

    Args:
        connection (django.db.backends.base.BaseDatabaseWrapper):
            The connection used to unrecord applied migrations.

        app_label (unicode):
            The app label that the migrations pertain to.

        migration_names (list of unicode, optional):
            The list of migration names to unrecord. If not provided, all
            migrations for the app will be unrecorded.
    """
    assert supports_migrations, \
        'This cannot be called on Django 1.6 or earlier.'

    recorder = MigrationRecorder(connection)
    recorder.ensure_schema()

    queryset = recorder.migration_qs.filter(app=app_label)

    if migration_names:
        queryset = queryset.filter(name__in=migration_names)

    queryset.delete()
from django.db import DEFAULT_DB_ALIAS, connections, models
    from django.db import migrations
    from django.db.migrations.recorder import MigrationRecorder
from django_evolution.compat.apps import get_app
from django_evolution.db.state import DatabaseState
from django_evolution.errors import (MigrationConflictsError,
                                     MigrationHistoryError)
from django_evolution.signature import AppSignature
from django_evolution.support import supports_migrations
from django_evolution.tests.base_test_case import (MigrationsTestsMixin,
                                                   TestCase)
from django_evolution.tests.decorators import (
    requires_migrations,
    requires_migration_history_checks)
from django_evolution.tests.models import BaseTestModel
from django_evolution.tests.utils import register_models
from django_evolution.utils.migrations import (MigrationExecutor,
                                               MigrationList,
                                               MigrationLoader,
                                               apply_migrations,
                                               create_pre_migrate_state,
                                               filter_migration_targets,
                                               finalize_migrations,
                                               has_migrations_module,
                                               is_migration_initial,
                                               record_applied_migrations,
                                               unrecord_applied_migrations)
                                           name='0001_initial')
        migration_list2.add_migration_info(app_label='foo',
                                           name='0002_bar')

        new_migration_list = migration_list1 - migration_list2

        self.assertEqual(
            new_migration_list._by_id,
            {
                ('tests', '0002_stuff'): {
                    'app_label': 'tests',
                    'name': '0002_stuff',
                    'migration': None,
                    'recorded_migration': None,
                },
            })
        self.assertEqual(
            new_migration_list._by_app_label,
            {
                'tests': [
                    {
                        'app_label': 'tests',
                        'name': '0002_stuff',
                        'migration': None,
                        'recorded_migration': None,
                    },
                ],
            })

        # Make sure there's no cross-contamination.
        self.assertNotEqual(new_migration_list._by_id, migration_list1._by_id)
        self.assertNotEqual(new_migration_list._by_id, migration_list2._by_id)
        self.assertNotEqual(migration_list1._by_id, migration_list2._by_id)


class MigrationLoadTests(MigrationsTestsMixin, TestCase):
    """Unit tests for django_evolution.utils.migrations.MigrationLoader."""

    @requires_migrations
    def test_build_graph(self):
        """Testing MigrationLoader.build_graph"""
        loader = MigrationLoader(connection=connections[DEFAULT_DB_ALIAS])
        graph = loader.graph
        migration = loader.get_migration('auth', '0001_initial')

        loader.build_graph()
        self.assertIsNot(loader.graph, graph)
        self.assertIsNot(loader.get_migration('auth', '0001_initial'),
                         migration)

    @requires_migrations
    def test_build_graph_with_reload_migrations_false(self):
        """Testing MigrationLoader.build_graph with reload_migrations=False"""
        loader = MigrationLoader(connection=connections[DEFAULT_DB_ALIAS])
        graph = loader.graph
        migration = loader.get_migration('auth', '0001_initial')

        loader.build_graph(reload_migrations=False)
        self.assertIsNot(loader.graph, graph)
        self.assertIs(loader.get_migration('auth', '0001_initial'),
                      migration)


class MigrationExecutorTests(MigrationsTestsMixin, TestCase):
    """Unit tests for django_evolution.utils.migrations.MigrationExecutor."""

    @requires_migration_history_checks
    def test_run_checks_with_bad_history(self):
        """Testing MigrationExecutor.run_checks with bad history"""
        connection = connections[DEFAULT_DB_ALIAS]

        applied_migrations = MigrationList()
        applied_migrations.add_migration_info(app_label='tests',
                                              name='0002_add_field')
        record_applied_migrations(connection=connection,
                                  migrations=applied_migrations)

        custom_migrations = MigrationList()
        custom_migrations.add_migration(
            InitialMigration('0001_initial', 'tests'))
        custom_migrations.add_migration(
            AddFieldMigration('0002_add_field', 'tests'))

        executor = MigrationExecutor(connection=connection,
                                     custom_migrations=custom_migrations)

        with self.assertRaises(MigrationHistoryError):
            executor.run_checks()

    @requires_migrations
    def test_run_checks_with_conflicts(self):
        """Testing MigrationExecutor.run_checks with conflicts"""
        connection = connections[DEFAULT_DB_ALIAS]

        custom_migrations = MigrationList()
        custom_migrations.add_migration(
            InitialMigration('0001_initial', 'tests'))
        custom_migrations.add_migration(
            InitialMigration('0002_also_initial', 'tests'))

        executor = MigrationExecutor(connection=connection,
                                     custom_migrations=custom_migrations)

        message = (
            "Conflicting migrations detected; multiple leaf nodes in the "
            "migration graph: (0001_initial, 0002_also_initial in tests).\n"
            "To fix them run 'python manage.py makemigrations --merge'"
        )

        with self.assertRaisesMessage(MigrationConflictsError, message):
            executor.run_checks()


class MigrationUtilsTests(MigrationsTestsMixin, TestCase):
    """Unit tests for django_evolution.utils.migrations."""

    @requires_migrations
    def test_has_migrations_module(self):
        """Testing has_migrations_module"""
        self.assertFalse(has_migrations_module(get_app('django_evolution')))

        self.assertTrue(has_migrations_module(get_app('auth')))