class EvolutionException(Exception):
    """Base class for a Django Evolution exception."""

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return str(self.msg) class SimulationFailure(EvolutionException):
    """A mutation simulation has failed.""" class AddField(BaseModelFieldMutation):
    """A mutation that adds a field to a model."""

    simulation_failure_error = (
        'Cannot add the field "%(field_name)s" to model '
        '"%(app_label)s.%(model_name)s".'
    )

    def __init__(self, model_name, field_name, field_type, initial=None,
                 **field_attrs):
        """Initialize the mutation.

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

            field_name (unicode):
                The name of the new field.

            field_type (cls):
                The field class to use. This must be a subclass of
                :py:class:`django.db.models.Field`.

            initial (object, optional):
                The initial value for the field. This is required if non-null.

            **field_attrs (dict):
                Attributes to set on the field.
        """
        super(AddField, self).__init__(model_name, field_name)

        self.field_type = field_type
        self.field_attrs = field_attrs
        self.initial = initial

    def get_hint_params(self):
        """Return parameters for the mutation's hinted evolution.

        Returns:
            list of unicode:
            A list of parameter strings to pass to the mutation's constructor
            in a hinted evolution.
        """
        params = [
            self.serialize_attr(key, value)
            for key, value in six.iteritems(self.field_attrs)
        ]

        if self.initial is not None:
            params.append(self.serialize_attr('initial', self.initial))

        return [
            self.serialize_value(self.model_name),
            self.serialize_value(self.field_name),
            self.serialize_value(self.field_type),
        ] + sorted(params)

    def simulate(self, simulation):
        """Simulate the mutation.

        This will alter the database schema to add the specified field.

        Args:
            simulation (Simulation):
                The state for the simulation.

        Raises:
            django_evolution.errors.SimulationFailure:
                The simulation failed. The reason is in the exception's
                message.
        """
        model_sig = simulation.get_model_sig(self.model_name)

        if model_sig.get_field_sig(self.field_name) is not None:
            simulation.fail('A field with this name already exists.')

        if (not issubclass(self.field_type, models.ManyToManyField) and
            not self.field_attrs.get('null')
            and self.initial is None):
            simulation.fail('A non-null initial value must be specified in '
                            'the mutation.')

        field_attrs = self.field_attrs.copy()
        related_model = field_attrs.pop('related_model', None)

        field_sig = FieldSignature(field_name=self.field_name,
                                   field_type=self.field_type,
                                   field_attrs=field_attrs,
                                   related_model=related_model)
        model_sig.add_field_sig(field_sig)

    def mutate(self, mutator, model):
        """Schedule a field addition on the mutator.

        This will instruct the mutator to add a new field on a model. It will
        be scheduled and later executed on the database, if not optimized out.

        Args:
            mutator (django_evolution.mutators.ModelMutator):
                The mutator to perform an operation on.

            model (MockModel):
                The model being mutated.
        """
        if issubclass(self.field_type, models.ManyToManyField):
            self.add_m2m_table(mutator, model)
        else:
            self.add_column(mutator, model)

    def add_column(self, mutator, model):
        """Add a standard column to the model.

        Args:
            mutator (django_evolution.mutators.ModelMutator):
                The mutator to perform an operation on.

            model (MockModel):
                The model being mutated.
        """
        field = self._create_field(mutator, model)

        mutator.add_column(self, field, self.initial)

    def add_m2m_table(self, mutator, model):
        """Add a ManyToMany column to the model and an accompanying table.

        Args:
            mutator (django_evolution.mutators.ModelMutator):
                The mutator to perform an operation on.

            model (MockModel):
                The model being mutated.
        """
        field = self._create_field(mutator, model)

        related_app_label, related_model_name = \
            self.field_attrs['related_model'].split('.')
        related_sig = (
            mutator.project_sig
            .get_app_sig(related_app_label)
            .get_model_sig(related_model_name)
        )
        related_model = MockModel(project_sig=mutator.project_sig,
                                  app_name=related_app_label,
                                  model_name=related_model_name,
                                  model_sig=related_sig,
                                  db_name=mutator.database)
        related = MockRelated(related_model=related_model,
                              model=model,
                              field=field)

        if hasattr(field, '_get_m2m_column_name'):
            # Django < 1.2
            field.m2m_column_name = \
                partial(field._get_m2m_column_name, related)
            field.m2m_reverse_name = \
                partial(field._get_m2m_reverse_name, related)

            field.m2m_column_name = \
                partial(field._get_m2m_attr, related, 'column')
            field.m2m_reverse_name = \
                partial(field._get_m2m_reverse_attr, related, 'column')

        mutator.add_sql(self, mutator.evolver.add_m2m_table(model, field))

    def _create_field(self, mutator, parent_model):
        """Create a new field to add to the model.

        Args:
            mutator (django_evolution.mutators.ModelMutator):
                The mutator to perform an operation on.

            parent_model (django_evolution.mock_models.MockModel):
                The model to add the field to.

        Returns:
            django.db.models.Field:
            The newly-created field.
        """
        field_attrs = self.field_attrs.copy()
        related_model = field_attrs.pop('related_model', None)

        return create_field(project_sig=mutator.project_sig,
                            field_name=self.field_name,
                            field_type=self.field_type,
                            field_attrs=field_attrs,
                            parent_model=parent_model,
                            related_model=related_model) class DeleteField(BaseModelFieldMutation):
    """A mutation that deletes a field from a model."""

    simulation_failure_error = (
        'Cannot delete the field "%(field_name)s" on model '
        '"%(app_label)s.%(model_name)s".'
    )

    def get_hint_params(self):
        """Return parameters for the mutation's hinted evolution.

        Returns:
            list of unicode:
            A list of parameter strings to pass to the mutation's constructor
            in a hinted evolution.
        """
        return [
            self.serialize_value(self.model_name),
            self.serialize_value(self.field_name),
        ]

    def simulate(self, simulation):
        """Simulate the mutation.

        This will alter the database schema to remove the specified field,
        modifying meta fields (``unique_together``) if necessary.

        It will also check to make sure this is not a primary key and that
        the field exists.

        Args:
            simulation (Simulation):
                The state for the simulation.

        Raises:
            django_evolution.errors.SimulationFailure:
                The simulation failed. The reason is in the exception's
                message.
        """
        model_sig = simulation.get_model_sig(self.model_name)
        field_sig = simulation.get_field_sig(self.model_name, self.field_name)

        if field_sig.get_attr_value('primary_key'):
            simulation.fail('The field is a primary key and cannot '
                            'be deleted.')

        # If the field was used in the unique_together attribute, update it.
        new_unique_together = []

        for unique_together_entry in model_sig.unique_together:
            new_entry = tuple(
                field_name
                for field_name in unique_together_entry
                if field_name != self.field_name
            )

            if new_entry:
                new_unique_together.append(new_entry)

        model_sig.unique_together = new_unique_together

        # Simulate the deletion of the field.
        model_sig.remove_field_sig(self.field_name)

    def mutate(self, mutator, model):
        """Schedule a field deletion on the mutator.

        This will instruct the mutator to perform a deletion of a field on
        a model. It will be scheduled and later executed on the database, if
        not optimized out.

        Args:
            mutator (django_evolution.mutators.ModelMutator):
                The mutator to perform an operation on.

            model (MockModel):
                The model being mutated.
        """
        field_sig = mutator.model_sig.get_field_sig(self.field_name)
        field = create_field(project_sig=mutator.project_sig,
                             field_name=self.field_name,
                             field_type=field_sig.field_type,
                             field_attrs=field_sig.field_attrs,
                             parent_model=model,
                             related_model=field_sig.related_model)

        if isinstance(field, models.ManyToManyField):
            mutator.add_sql(
                self,
                mutator.evolver.delete_table(
                    field._get_m2m_db_table(model._meta)))
        else:
            mutator.delete_column(self, field) 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 FieldSignature(BaseSignature):
    """Signature information for a field.

    Field signatures store information on a field on model, including the
    field name, type, and any attribute values needed for migrating the
    schema.
    """

    _ATTRIBUTE_DEFAULTS = {
        '*': {
            'primary_key': False,
            'max_length': None,
            'unique': False,
            'null': False,
            'db_index': False,
            'db_column': None,
            'db_tablespace': global_settings.DEFAULT_TABLESPACE,
        },
        models.DecimalField: {
            'max_digits': None,
            'decimal_places': None,
        },
        models.ForeignKey: {
            'db_index': True,
        },
        models.ManyToManyField: {
            'db_table': None,
        },
        models.OneToOneField: {
            'db_index': True,
        },
    }

    _ATTRIBUTE_ALIASES = {
        # r7790 modified the unique attribute of the meta model to be
        # a property that combined an underlying _unique attribute with
        # the primary key attribute. We need the underlying property,
        # but we don't want to affect old signatures (plus the
        # underscore is ugly :-).
        'unique': '_unique',

        # Django 1.9 moved from 'rel' to 'remote_field' for relations, but
        # for compatibility reasons we want to retain 'rel' in our signatures.
        'rel': 'remote_field',
    }

    @classmethod
    def from_field(cls, field):
        """Create a field signature from a field.

        Args:
            field (django.db.models.Field):
                The field to create a signature from.

        Returns:
            FieldSignature:
            The signature based on the field.
        """
        field_type = type(field)
        field_attrs = {}

        defaults = cls._get_defaults_for_field_type(field_type)

        for attr, default in six.iteritems(defaults):
            alias = cls._ATTRIBUTE_ALIASES.get(attr)

            if alias and hasattr(field, alias):
                value = getattr(field, alias)
            elif hasattr(field, attr):
                value = getattr(field, attr)
            else:
                continue

            if value != default:
                field_attrs[attr] = value

        remote_field = get_remote_field(field)

        if remote_field:
            remote_field_meta = get_remote_field_model(remote_field)._meta

            related_model = '%s.%s' % (
                remote_field_meta.app_label,
                remote_field_meta.object_name,
            )
        else:
            related_model = None

        return cls(field_name=field.name,
                   field_type=field_type,
                   field_attrs=field_attrs,
                   related_model=related_model)

    @classmethod
    def deserialize(cls, field_name, field_sig_dict, sig_version,
                    database=DEFAULT_DB_ALIAS):
        """Deserialize a serialized field signature.

        Args:
            field_name (unicode):
                The name of the field.

            field_sig_dict (dict):
                The dictionary containing field signature data.

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

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

        Returns:
            FieldSignature:
            The resulting signature instance.

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

        if sig_version == 2:
            field_sig_attrs = field_sig_dict.get('attrs', {})

            # Load the class for the referenced field type.
            field_type_module, field_type_name = \
                field_sig_dict['type'].rsplit('.', 1)

            # If we have a field path in the signature that lives in
            # django.db.models.fields, update it to look in django.db.models
            # instead. This is for compatibility across all Django versions.
            if field_type_module.startswith('django.db.models.fields'):
                field_type_module = 'django.db.models'

            try:
                field_type = getattr(import_module(field_type_module),
                                     field_type_name)
            except (AttributeError, ImportError):
                raise ImportError('Unable to locate field type %s'
                                  % '%s.%s' % (field_type_module,
                                               field_type_name))
        elif sig_version == 1:
            field_sig_attrs = field_sig_dict
            field_type = field_sig_dict['field_type']

        field_attrs = {}

        for attr in cls._iter_attrs_for_field_type(field_type):
            if hasattr(cls, attr):
                # This is stored on the field signature class itself, so
                # it's not attribute data we want to load.
                continue

            alias = cls._ATTRIBUTE_ALIASES.get(attr)

            if alias and alias in field_sig_attrs:
                value = field_sig_attrs[alias]
            elif attr in field_sig_attrs:
                value = field_sig_attrs[attr]
            else:
                # The signature didn't contain a value for this attribute.
                continue

            field_attrs[attr] = value

        return cls(field_name=field_name,
                   field_type=field_type,
                   field_attrs=field_attrs,
                   related_model=field_sig_dict.get('related_model'))

    @classmethod
    def _iter_attrs_for_field_type(cls, field_type):
        """Iterate through attribute names for a field type.

        The attributes returned are those that impact the schema for a field's
        column.

        Args:
            field_type (type):
                The class for the field. This would be a subclass of
                :py:class:`django.db.models.Field`.

        Yield:
            unicode:
            An attribute for a field type.
        """
        return six.iterkeys(cls._get_defaults_for_field_type(field_type))

    @classmethod
    def _get_defaults_for_field_type(cls, field_type):
        """Return attribute names and defaults for a field type.

        The attributes returned are those that impact the schema for a field's
        column.

        Args:
            field_type (type):
                The class for the field. This would be a subclass of
                :py:class:`django.db.models.Field`.

        Returns:
            dict:
            The dictionary of attribute names and values.
        """
        defaults = cls._ATTRIBUTE_DEFAULTS['*'].copy()
        defaults.update(cls._ATTRIBUTE_DEFAULTS.get(field_type, {}))

        return defaults

    def __init__(self, field_name, field_type, field_attrs=None,
                 related_model=None):
        """Initialize the signature.

        Args:
            field_name (unicode):
                The name of the field.

            field_type (cls):
                The class for the field. This would be a subclass of
                :py:class:`django.db.models.Field`.

            field_attrs (dict, optional):
                Attributes to set on the field.

            related_model (unicode, optional):
                The full path to a related model.
        """
        self.field_name = field_name
        self.field_type = field_type
        self.field_attrs = field_attrs or OrderedDict()
        self.related_model = related_model

    def get_attr_value(self, attr_name, use_default=True):
        """Return the value for an attribute.

        By default, this will return the default value for the attribute if
        it's not explicitly set.

        Args:
            attr_name (unicode):
                The name of the attribute.

            use_default (bool, optional):
                Whether to return the default value for the attribute if it's
                not explicitly set.

        Returns:
            object:
            The value for the attribute.
        """
        try:
            return self.field_attrs[attr_name]
        except KeyError:
            if use_default:
                return self.get_attr_default(attr_name)

            return None

    def get_attr_default(self, attr_name):
        """Return the default value for an attribute.

        Args:
            attr_name (unicode):
                The attribute name.

        Returns:
            object:
            The default value for the attribute, or ``None``.
        """
        for defaults in (self._ATTRIBUTE_DEFAULTS.get(self.field_type, {}),
                         self._ATTRIBUTE_DEFAULTS['*']):
            try:
                return defaults[attr_name]
            except KeyError:
                continue

        return None

    def is_attr_value_default(self, attr_name):
        """Return whether an attribute is set to its default value.

        Args:
            attr_name (unicode):
                The attribute name.

        Returns:
            bool:
            ``True`` if the attribute's value is set to its default value.
            ``False`` if it has a custom value.
        """
        try:
            attr_value = self.field_attrs[attr_name]
        except KeyError:
            return True

        return attr_value == self.get_attr_default(attr_name)

    def diff(self, old_field_sig):
        """Diff against an older field signature.

        This will return a list of field names that have changed between
        this field signature and an older one.

        Args:
            old_field_sig (FieldSignature):
                The old field signature to diff against.

        Returns:
            list:
            The list of field names.

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

        changed_attrs = [
            attr
            for attr in (set(old_field_sig.field_attrs) |
                         set(self.field_attrs))
            if self.get_attr_value(attr) != old_field_sig.get_attr_value(attr)
        ]

        # See if the field type has changed.
        old_field_type = old_field_sig.field_type
        new_field_type = self.field_type

        if old_field_type is not new_field_type:
            try:
                field_type_changed = (old_field_type().get_internal_type() !=
                                      new_field_type().get_internal_type())
            except TypeError:
                # We can't instantiate those, so assume the field
                # type has indeed changed.
                field_type_changed = True

            if field_type_changed:
                changed_attrs.append('field_type')

        # FieldSignature.related_model is not a field attribute,
        # but we do need to track its changes.
        if old_field_sig.related_model != self.related_model:
            changed_attrs.append('related_model')

        return sorted(changed_attrs)

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

        Returns:
            FieldSignature:
            The cloned signature.
        """
        return FieldSignature(field_name=self.field_name,
                              field_type=self.field_type,
                              field_attrs=deepcopy(self.field_attrs),
                              related_model=self.related_model)

    def serialize(self, sig_version=LATEST_SIGNATURE_VERSION):
        """Serialize field 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)

        field_sig_dict = OrderedDict()

        if sig_version == 2:
            field_module = self.field_type.__module__

            # If the field lives in django.db.models.fields, update it to
            # use django.db.models instead. This is for compatibility across
            # all Django versions.
            if field_module.startswith('django.db.models.fields'):
                field_module = 'django.db.models'

            field_sig_dict['type'] = '%s.%s' % (field_module,
                                                self.field_type.__name__)

            if self.field_attrs:
                field_sig_dict['attrs'] = deepcopy(self.field_attrs)
        elif sig_version == 1:
            field_sig_dict['field_type'] = self.field_type
            field_sig_dict.update(self.field_attrs)

        if self.related_model:
            field_sig_dict['related_model'] = self.related_model

        return field_sig_dict

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

        Args:
            other (FieldSignature):
                The other field signature.

        Returns:
            bool:
            ``True`` if the field signatures are equal. ``False`` if they
            are not.
        """
        return (other is not None and
                self.field_name == other.field_name and
                self.field_type is other.field_type and
                dict.__eq__(self.field_attrs, other.field_attrs) and
                self.related_model == other.related_model)

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

        Returns:
            unicode:
            A string representation of the signature.
        """
        return ('<FieldSignature(field_name=%r, field_type=%r,'
                ' field_attrs=%r, related_model=%r)>'
                % (self.field_name, self.field_type, self.field_attrs,
                   self.related_model)) class ModelSignature(BaseSignature):
    """Signature information for a model.

    Model signatures store information on the model and include signatures for
    its fields and ``_meta`` attributes.
    """

    @classmethod
    def from_model(cls, model):
        """Create a model signature from a model.

        This will store data on the model and its ``_meta`` attributes, and
        create a :py:class:`FieldSignature` for each field.

        Args:
            model (django.db.models.Model):
                The model to create a signature from.

        Returns:
            ModelSignature:
            The signature based on the model.
        """
        meta = model._meta
        model_sig = cls(db_tablespace=meta.db_tablespace,
                        index_together=meta.index_together,
                        model_name=meta.object_name,
                        pk_column=six.text_type(meta.pk.column),
                        table_name=meta.db_table,
                        unique_together=meta.unique_together,
                        unique_together_applied=True)

        if getattr(meta, 'constraints', None):
            # Django >= 2.2
            for constraint in meta.original_attrs['constraints']:
                model_sig.add_constraint(constraint)

        if getattr(meta, 'indexes', None):
            # Django >= 1.11
            for index in meta.original_attrs['indexes']:
                model_sig.add_index(index)

        for field in meta.local_fields + meta.local_many_to_many:
            # Don't generate a signature for generic relations.
            if not isinstance(field, GenericRelation):
                model_sig.add_field(field)

        return model_sig

    @classmethod
    def deserialize(cls, model_name, model_sig_dict, sig_version,
                    database=DEFAULT_DB_ALIAS):
        """Deserialize a serialized model signature.

        Args:
            model_name (unicode):
                The model name.

            model_sig_dict (dict):
                The dictionary containing model signature data.

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

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

        Returns:
            ModelSignature:
            The resulting signature instance.

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

        meta_sig_dict = model_sig_dict['meta']
        fields_sig_dict = model_sig_dict['fields']

        model_sig = cls(
            db_tablespace=meta_sig_dict.get('db_tablespace'),
            index_together=meta_sig_dict.get('index_together', []),
            model_name=model_name,
            pk_column=meta_sig_dict.get('pk_column'),
            table_name=meta_sig_dict.get('db_table'),
            unique_together=meta_sig_dict.get('unique_together', []),
            unique_together_applied=meta_sig_dict.get(
                '__unique_together_applied', False))

        # Django >= 2.2
        for constraint_sig_dict in meta_sig_dict.get('constraints', []):
            model_sig.add_constraint_sig(
                ConstraintSignature.deserialize(
                    constraint_sig_dict=constraint_sig_dict,
                    sig_version=sig_version,
                    database=database))

        # Django >= 1.11
        for index_sig_dict in meta_sig_dict.get('indexes', []):
            model_sig.add_index_sig(
                IndexSignature.deserialize(index_sig_dict=index_sig_dict,
                                           sig_version=sig_version,
                                           database=database))

        for field_name, field_sig_dict in six.iteritems(fields_sig_dict):
            model_sig.add_field_sig(
                FieldSignature.deserialize(field_name=field_name,
                                           field_sig_dict=field_sig_dict,
                                           sig_version=sig_version,
                                           database=database))

        return model_sig

    def __init__(self, model_name, table_name, db_tablespace=None,
                 index_together=[], pk_column=None, unique_together=[],
                 unique_together_applied=False):
        """Initialize the signature.

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

            table_name (unicode):
                The name of the table in the database.

            db_tablespace (unicode, optional):
                The tablespace for the model. This is database-specific.

            index_together (list of tuple, optional):
                A list of fields that are indexed together.

            pk_column (unicode, optional):
                The column for the primary key.

            unique_together (list of tuple, optional):
                The list of fields that are unique together.

            unique_together_applied (bool, optional):
                Whether the ``unique_together`` value was applied to the
                database, rather than simply stored in the signature.

                Version Added:
                    2.1.3
        """
        self.model_name = model_name
        self.db_tablespace = db_tablespace
        self.table_name = table_name
        self.pk_column = pk_column

        self.constraint_sigs = []
        self.index_sigs = []
        self._field_sigs = OrderedDict()
        self._index_together = []
        self._unique_together = []
        self._unique_together_applied = unique_together_applied

        # Set these after we've set up the private state backing it.
        self.index_together = index_together
        self.unique_together = unique_together

    @property
    def index_together(self):
        """A list of fields that are indexed together.

        Type:
            list
        """
        return self._index_together

    @index_together.setter
    def index_together(self, new_value):
        """A list of fields that are indexed together.

        When setting this property, the value will be normalized for storage
        and comparison.

        Args:
            new_value (list):
                The new list of fields indexed together.
        """
        self._index_together = self._normalize_together(new_value)

    @property
    def unique_together(self):
        """A list of fields that are unique together.

        Type:
            list
        """
        return self._unique_together

    @unique_together.setter
    def unique_together(self, new_value):
        """A list of fields that are unique together.

        When setting this property, the value will be normalized for storage
        and comparison.

        Args:
            new_value (list):
                The new list of fields that are unique together.
        """
        self._unique_together = self._normalize_together(new_value)

    @property
    def field_sigs(self):
        """The field signatures on the model signature."""
        return six.itervalues(self._field_sigs)

    def add_field(self, field):
        """Add a field to the model signature.

        This will construct a :py:class:`FieldSignature` and add it to this
        model signature.

        Args:
            field (django.db.models.Field):
                The field to create the signature from.
        """
        self.add_field_sig(FieldSignature.from_field(field))

    def add_field_sig(self, field_sig):
        """Add a field signature to the model signature.

        Args:
            field_sig (FieldSignature):
                The field signature to add.
        """
        self._field_sigs[field_sig.field_name] = field_sig

    def remove_field_sig(self, field_name):
        """Remove a field signature from the model signature.

        Args:
            field_name (unicode):
                The name of the field.

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

    def get_field_sig(self, field_name, required=False):
        """Return a field signature for the given field name.

        Args:
            field_name (unicode):
                The name of the field.

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

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

        Raises:
            django_evolution.errors.MissingSignatureError:
                The model signature was not found, and ``required`` was
                ``True``.
        """
        field_sig = self._field_sigs.get(field_name)

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

        return field_sig

    def add_constraint(self, constraint):
        """Add an explicit constraint to the models.

        This is only used on Django 2.2 or higher. It corresponds to the
        :py:attr:`model._meta.constraints
        <django.db.models.Options.constraints` attribute.

        Args:
            constraint (django.db.models.BaseConstraint):
                The constraint to add.
        """
        self.add_constraint_sig(
            ConstraintSignature.from_constraint(constraint))

    def add_constraint_sig(self, constraint_sig):
        """Add an explicit constraint signature to the models.

        This is only used on Django 2.2 or higher. It corresponds to the
        :py:attr:`model._meta.constraints
        <django.db.models.Options.constraints` attribute.

        Args:
            constraint_sig (ConstraintSignature):
                The constraint signature to add.
        """
        self.constraint_sigs.append(constraint_sig)

    def add_index(self, index):
        """Add an explicit index to the models.

        This is only used on Django 1.11 or higher. It corresponds to the
        :py:attr:`model._meta.indexes <django.db.models.Options.indexes`
        attribute.

        Args:
            index (django.db.models.Index):
                The index to add.
        """
        self.add_index_sig(IndexSignature.from_index(index))

    def add_index_sig(self, index_sig):
        """Add an explicit index signature to the models.

        This is only used on Django 1.11 or higher. It corresponds to the
        :py:attr:`model._meta.indexes <django.db.models.Options.indexes`
        attribute.

        Args:
            index_sig (IndexSignature):
                The index signature to add.
        """
        self.index_sigs.append(index_sig)

    def apply_unique_together(self, unique_together):
        """Record an applied unique_together change to the model.

        This will store the new unique together value and set a flag indicating
        it's been applied to the database.

        The flag exists to deal with a situation from old versions of
        Django Evolution where the unique_together state was stored in the
        signature but not applied to the database.

        Version Added:
            2.1.3

        Args:
            unique_together (list):
                The new unique_together value.
        """
        self.unique_together = unique_together
        self._unique_together_applied = True

    def has_unique_together_changed(self, old_model_sig):
        """Return whether unique_together has changed between signatures.

        ``unique_together`` is considered to have changed under the following
        conditions:

        * They are different in value.
        * Either the old or new is non-empty (even if equal) and evolving
          from an older signature from Django Evolution pre-0.7, where
          unique_together wasn't applied to the database.

        Args:
            old_model_sig (ModelSignature):
                The old model signature to compare against.

        Return:
            bool:
            ``True`` if the value has changed. ``False`` if they're
            considered equal for the purposes of evolution.
        """
        old_unique_together = old_model_sig.unique_together
        new_unique_together = self.unique_together

        return (old_unique_together != new_unique_together or
                ((old_unique_together or new_unique_together) and
                 old_model_sig._unique_together_applied is not
                 self._unique_together_applied))

    def diff(self, old_model_sig):
        """Diff against an older model signature.

        This will return a dictionary containing the differences in fields
        and meta information between two signatures.

        Args:
            old_model_sig (ModelSignature):
                The old model signature to diff against.

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

                {
                    'added': [
                        <field name>,
                        ...
                    ],
                    'deleted': [
                        <field name>,
                        ...
                    ],
                    'changed': {
                        <field name>: <FieldSignature diff>,
                        ...
                    },
                    'meta_changed': [
                        <'constraints'>,
                        <'indexes'>,
                        <'index_together'>,
                        <'unique_together'>,
                    ],
                }

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

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

        # Go through all the fields, looking for changed and deleted fields.
        changed_fields = OrderedDict()
        deleted_fields = []

        for old_field_sig in old_model_sig.field_sigs:
            field_name = old_field_sig.field_name
            new_field_sig = self.get_field_sig(field_name)

            if new_field_sig:
                # Go through all the attributes on the field, looking for
                # changes.
                changed_field_attrs = new_field_sig.diff(old_field_sig)

                if changed_field_attrs:
                    # There were attribute changes. Store those with the field.
                    changed_fields[field_name] = changed_field_attrs
            else:
                # The field has been deleted.
                deleted_fields.append(field_name)

        # Go through the list of added fields and add any that don't
        # exist in the original field list.
        added_fields = [
            field_sig.field_name
            for field_sig in self.field_sigs
            if not old_model_sig.get_field_sig(field_sig.field_name)
        ]

        # Build a list of changes to Model.Meta attributes.
        meta_changed = []

        if self.has_unique_together_changed(old_model_sig):
            meta_changed.append('unique_together')

        if self.index_together != old_model_sig.index_together:
            meta_changed.append('index_together')

        if list(self.index_sigs) != list(old_model_sig.index_sigs):
            meta_changed.append('indexes')

        if list(self.constraint_sigs) != list(old_model_sig.constraint_sigs):
            meta_changed.append('constraints')

        return OrderedDict(
            (key, value)
            for key, value in (('added', added_fields),
                               ('changed', changed_fields),
                               ('deleted', deleted_fields),
                               ('meta_changed', meta_changed))
            if value
        )

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

        Returns:
            ModelSignature:
            The cloned signature.
        """
        cloned_sig = ModelSignature(
            model_name=self.model_name,
            table_name=self.table_name,
            db_tablespace=self.db_tablespace,
            index_together=self.index_together,
            pk_column=self.pk_column,
            unique_together=self.unique_together)
        cloned_sig._unique_together_applied = self._unique_together_applied

        for field_sig in self.field_sigs:
            cloned_sig.add_field_sig(field_sig.clone())

        for constraint_sig in self.constraint_sigs:
            cloned_sig.add_constraint_sig(constraint_sig.clone())

        for index_sig in self.index_sigs:
            cloned_sig.add_index_sig(index_sig.clone())

        return cloned_sig

    def serialize(self, sig_version=LATEST_SIGNATURE_VERSION):
        """Serialize model 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)

        return {
            'meta': {
                'constraints': [
                    constraint_sig.serialize(sig_version)
                    for constraint_sig in self.constraint_sigs
                ],
                'db_table': self.table_name,
                'db_tablespace': self.db_tablespace,
                'index_together': deepcopy(self.index_together),
                'indexes': [
                    index_sig.serialize(sig_version)
                    for index_sig in self.index_sigs
                ],
                'pk_column': self.pk_column,
                'unique_together': deepcopy(self.unique_together),
                '__unique_together_applied': self._unique_together_applied,
            },
            'fields': OrderedDict(
                (field_name, field_sig.serialize(sig_version))
                for field_name, field_sig in six.iteritems(self._field_sigs)
            ),
        }

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

        Args:
            other (ModelSignature):
                The other model signature.

        Returns:
            bool:
            ``True`` if the model signatures are equal. ``False`` if they
            are not.
        """
        return (other is not None and
                self.table_name == other.table_name and
                self.db_tablespace == other.db_tablespace and
                set(self.constraint_sigs) == set(other.constraint_sigs) and
                set(self.index_sigs) == set(other.index_sigs) and
                set(self.index_together) == set(other.index_together) and
                self.model_name == other.model_name and
                self.pk_column == other.pk_column and
                dict.__eq__(self._field_sigs, other._field_sigs) and
                not self.has_unique_together_changed(other))

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

        Returns:
            unicode:
            A string representation of the signature.
        """
        return '<ModelSignature(model_name=%r)>' % self.model_name

    def _normalize_together(self, together):
        """Normalize a <field>_together value.

        This is intended to normalize ``index_together`` and
        ``unique_together`` values so that they're reliably stored in a
        consistent format.

        Args:
            together (object):
                The value to normalize.

        Returns:
            list of tuple:
            The normalized value.
        """
        if not together:
            return []

        if not isinstance(together[0], (tuple, list)):
            together = (together,)

        return [
            tuple(
                six.text_type(_value)
                for _value in _item
            )
            for _item in together
        ] class ProjectSignature(BaseSignature):
    """Signature information for a project.

    Projects are the top-level signature deserialized from and serialized to
    a :py:class:`~django_evolution.models.Version` model. They contain a
    signature version and information on all the applications tracked for the
    project.
    """

    @classmethod
    def from_database(cls, database):
        """Create a project signature from the database.

        This will look up all the applications registered in Django, turning
        each of them into a :py:class:`AppSignature` stored in this
        project signature.

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

        Returns:
            ProjectSignature:
            The project signature based on the current application and
            database state.
        """
        project_sig = cls()

        for app in get_apps():
            project_sig.add_app(app, database)

        return project_sig

    @classmethod
    def deserialize(cls, project_sig_dict, database=DEFAULT_DB_ALIAS):
        """Deserialize a serialized project signature.

        Args:
            project_sig_dict (dict):
                The dictionary containing project signature data.

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

        Returns:
            ProjectSignature:
            The resulting signature instance.

        Raises:
            django_evolution.errors.InvalidSignatureVersion:
                The signature version found in the dictionary is unsupported.
        """
        sig_version = project_sig_dict['__version__']
        validate_sig_version(sig_version)

        project_sig = cls()

        if sig_version == 2:
            app_sigs_dict = project_sig_dict['apps']
        elif sig_version == 1:
            app_sigs_dict = OrderedDict(
                (app_id, app_sig_dict)
                for app_id, app_sig_dict in six.iteritems(project_sig_dict)
                if app_id != '__version__'
            )

        for app_id, app_sig_dict in six.iteritems(app_sigs_dict):
            project_sig.add_app_sig(AppSignature.deserialize(
                app_id=app_id,
                app_sig_dict=app_sig_dict,
                sig_version=sig_version,
                database=database))

        return project_sig

    def __init__(self):
        """Initialize the signature."""
        self._app_sigs = OrderedDict()

    @property
    def app_sigs(self):
        """The application signatures in the project signature."""
        return six.itervalues(self._app_sigs)

    def add_app(self, app, database):
        """Add an application to the project signature.

        This will construct an :py:class:`AppSignature` and add it
        to the project signature.

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

            database (unicode):
                The database name.
        """
        self.add_app_sig(AppSignature.from_app(app, database))

    def add_app_sig(self, app_sig):
        """Add an application signature to the project signature.

        Args:
            app_sig (AppSignature):
                The application signature to add.
        """
        self._app_sigs[app_sig.app_id] = app_sig

    def remove_app_sig(self, app_id):
        """Remove an application signature from the project signature.

        Args:
            app_id (unicode):
                The ID of the application signature to remove.

        Raises:
            django_evolution.errors.MissingSignatureError:
                The application ID does not represent a known application
                signature.
        """
        try:
            del self._app_sigs[app_id]
        except KeyError:
            raise MissingSignatureError(
                _('An application signature for "%s" could not be found.')
                % app_id)

    def get_app_sig(self, app_id, required=False):
        """Return an application signature with the given ID.

        Args:
            app_id (unicode):
                The ID of the application signature. This may be a modern
                app label, or a legacy app label.

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

        Returns:
            AppSignature:
            The application signature, if found. If no application signature
            matches the ID, ``None`` will be returned.

        Raises:
            django_evolution.errors.MissingSignatureError:
                The application signature was not found, and ``required`` was
                ``True``.
        """
        app_sig = self._app_sigs.get(app_id)

        if app_sig is None:
            for temp_app_sig in six.itervalues(self._app_sigs):
                if temp_app_sig.legacy_app_label == app_id:
                    app_sig = temp_app_sig
                    break

        if app_sig is None and required:
            raise MissingSignatureError(
                _('Unable to find an application signature for "%s". '
                  'syncdb/migrate might need to be run first.')
                % (app_id,))

        return app_sig

    def diff(self, old_project_sig):
        """Diff against an older project signature.

        This will return a dictionary of changes between two project
        signatures.

        Args:
            old_project_sig (ProjectSignature):
                The old project signature to diff against.

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

                {
                    'changed': {
                        <app ID>: <AppSignature diff>,
                        ...
                    },
                    'deleted': [
                        <app ID>: [
                            <model name>,
                            ...
                        ],
                        ...
                    ],
                }

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

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

        changed_apps = OrderedDict()
        deleted_apps = OrderedDict()

        for old_app_sig in old_project_sig.app_sigs:
            new_app_sig = self.get_app_sig(old_app_sig.app_id)

            if new_app_sig:
                app_changes = new_app_sig.diff(old_app_sig)

                if app_changes:
                    # There are changes for this application. Store that
                    # in the diff.
                    changed_apps[new_app_sig.app_id] = app_changes
            else:
                # The application has been deleted.
                deleted_apps[old_app_sig.app_id] = [
                    model_sig.model_name
                    for model_sig in old_app_sig.model_sigs
                ]

        return OrderedDict(
            (key, value)
            for key, value in (('changed', changed_apps),
                               ('deleted', deleted_apps))
            if value
        )

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

        Returns:
            ProjectSignature:
            The cloned signature.
        """
        cloned_sig = ProjectSignature()

        for app_sig in self.app_sigs:
            cloned_sig.add_app_sig(app_sig.clone())

        return cloned_sig

    def serialize(self, sig_version=LATEST_SIGNATURE_VERSION):
        """Serialize project 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)

        project_sig_dict = {
            '__version__': sig_version,
        }

        if sig_version == 2:
            app_sigs_dict = OrderedDict()
            project_sig_dict['apps'] = app_sigs_dict
        elif sig_version == 1:
            app_sigs_dict = project_sig_dict

        for app_id, app_sig in six.iteritems(self._app_sigs):
            app_sigs_dict[app_id] = app_sig.serialize(sig_version)

        return project_sig_dict

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

        Args:
            other (ProjectSignature):
                The other project signature.

        Returns:
            bool:
            ``True`` if the project signatures are equal. ``False`` if they
            are not.
        """
        return (other is not None and
                dict.__eq__(self._app_sigs, other._app_sigs))

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

        Returns:
            unicode:
            A string representation of the signature.
        """
        return ('<ProjectSignature(apps=%r)>'
                % list(six.iterkeys(self._app_sigs))) class EvolutionTestCase(TestCase):
    """Base class for test cases that need to evolve the database."""

    default_model_name = 'TestModel'
    default_base_model = None
    default_pre_extra_models = []
    default_extra_models = []

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

        self.base_model = None
        self.pre_extra_models = []
        self.extra_models = []
        self.start = None
        self.start_sig = None
        self.database_state = None
        self.test_database_state = None
        self._models_registered = False

        if self.default_base_model:
            self.set_base_model(base_model=self.default_base_model,
                                pre_extra_models=self.default_pre_extra_models,
                                extra_models=self.default_extra_models)

    def tearDown(self):
        if self._models_registered:
            unregister_app('tests')

        super(EvolutionTestCase, self).tearDown()

    def default_create_test_data(self, db_name):
        """Default function for creating test data for base models.

        By default, this won't do anything.

        Args:
            db_name (unicode):
                The name of the database to create models on.
        """
        pass

    def set_base_model(self, base_model, name=None, extra_models=[],
                       pre_extra_models=[], db_name=None):
        """Set the base model(s) that will be mutated in a test.

        These models will be registered in Django's model registry and
        queued up to be written to the database. Starting signatures based
        on these models will be provided, which the test is expected to
        mutate.

        Args:
            base_model (type):
                The base :py:class:`~django.db.models.Model` to register and
                write to the database that the test will then mutate.

            name (unicode, optional):
                The name to register for the model. This defaults to
                :py:attr:`default_model_name`.

            extra_models (list of type, optional):
                The list of extra models to register and write to the
                database after writing ``base_model``. These may form
                relations to ``base_model``.

            pre_extra_models (list of type, optional):
                The list of extra models to write to the database before
                writing ``base_model``. ``base_model`` may form relations to
                these models.

            db_name (unicode, optional):
                The name of the database to write the models to. This
                defaults to :py:attr:`default_database_name`.
        """
        name = name or self.default_model_name
        db_name = db_name or self.default_database_name

        if self.base_model:
            unregister_app('tests')

        self.base_model = base_model
        self.pre_extra_models = pre_extra_models
        self.extra_models = extra_models
        self.database_state = DatabaseState(db_name)

        self.start = self.register_model(model=base_model,
                                         name=name,
                                         register_indexes=True,
                                         db_name=db_name)
        self.start_sig = self.create_test_proj_sig(model=base_model,
                                                   name=name)

    def make_end_signatures(self, dest_model, model_name, db_name=None):
        """Return signatures for a model representing the end of a mutation.

        Callers should construct a model that reflects the expected result
        of any mutations and provide that. This will register that model
        and construct a signature from it.

        Args:
            dest_model (type):
                The destination :py:class:`~django.db.models.Model`
                representing the expected result of an evolution.

            model_name (unicode):
                The name to register for the model.

            db_name (unicode, optional):
                The name of the database to write the models to. This
                defaults to :py:attr:`default_database_name`.

        Returns:
            tuple:
            A tuple containing:

            1. A :py:class:`collections.OrderedDict` mapping the model name
               to the model class.
            2. A :py:class:`django_evolution.signature.ProjectSignature`
               for the provided model.
        """
        db_name = db_name or self.default_database_name

        end = self.register_model(model=dest_model,
                                  name=model_name,
                                  db_name=db_name)
        end_sig = self.create_test_proj_sig(model=dest_model,
                                            name=model_name)

        return end, end_sig

    def perform_evolution_tests(self,
                                dest_model,
                                evolutions,
                                diff_text=None,
                                expected_hint=None,
                                sql_name=None,
                                model_name=None,
                                end=None,
                                end_sig=None,
                                expect_noop=False,
                                rescan_indexes=True,
                                use_hinted_evolutions=False,
                                perform_simulations=True,
                                perform_mutations=True,
                                db_name=None,
                                create_test_data_func=None):
        """Perform test evolutions and validate results.

        This is used for most common evolution-related tests. It handles
        generating signatures for a base model and an expected post-evolution
        model, ensuring that the mutations result in an empty diff.

        It then optionally simulates the evolutions on the signatures
        (using :py:meth:`perform_simulations)`, and then optionally performs
        the actual evolutions on the database (using
        :py:meth:`perform_mutations`), verifying all results.

        Args:
            dest_model (type):
                The destination :py:class:`~django.db.models.Model`
                representing the expected result of an evolution.

            evolutions (list of django_evolution.mutations.BaseMutation):
                The combined series of evolutions (list of mutations) to apply
                to the base model.

            diff_text (unicode, optional):
                The expected generated text describing a diff that must
                match, if provided.

            expected_hint (unicode, optional):
                The expected generated hint text that must match, if provided.

            sql_name (unicode, optional):
                The name of the registered SQL content for the database being
                tested.

                This must be provided if ``perform_mutations`` is ``True`.

            model_name (unicode, optional):
                The name of the model to register. This defaults to
                :py:attr:`default_model_name`.

            end (collections.OrderedDict, optional):
                The expected model map at the end of the evolution. This
                is generated by :py:meth:`make_end_signatures`.

                If not provided, one will be generated.

            end_sig (django_evolution.signature.ProjectSignature, optional):
                The expected project signature at the end of the evolution.
                This is generated by :py:meth:`make_end_signatures`.

                If not provided, one will be generated.

            expect_noop (bool, optional):
                Whether the evolution is expected not to change anything.

            rescan_indexes (bool, optional):
                Whether to re-scan the list of table indexes after performing
                mutations.

                This is ignored if ``perform_mutations`` is ``False``.

            use_hinted_evolutions (bool, optional):
                Whether to use the hinted evolutions generated by the
                signatures. This cannot be used if ``evolutions`` is
                non-empty.

            perform_simulations (bool, optional):
                Whether to simulate the evolution and compare results.

            perform_mutations (bool, optional):
                Whether to apply the mutations and compare results.

            db_name (unicode, optional):
                The name of the database to apply evolutions to. This
                defaults to :py:attr:`default_database_name`.

            create_test_data_func (callable, optional):
                A function to call in order to create data in the database for
                the initial models, before applying mutations. It must take
                a database name.

                If not provided, :py:meth:`default_create_test_data` will be
                used.

        Raises:
            AssertionError:
                A diff, simulation, or mutation test has failed.
        """
        model_name = model_name or self.default_model_name
        db_name = db_name or self.default_database_name

        if end is None or end_sig is None:
            end, end_sig = self.make_end_signatures(dest_model=dest_model,
                                                    model_name=model_name,
                                                    db_name=db_name)

        # See if the diff between signatures contains the contents we expect.
        d = self.perform_diff_test(end_sig=end_sig,
                                   diff_text=diff_text,
                                   expected_hint=expected_hint,
                                   expect_empty=expect_noop)

        if use_hinted_evolutions:
            assert not evolutions, (
                'The evolutions= argument cannot be provided when providing '
                'use_hinted_evolutions=True'
            )

            evolutions = d.evolution()['tests']

        if perform_simulations:
            self.perform_simulations(evolutions=evolutions,
                                     end_sig=end_sig,
                                     db_name=db_name)

        if perform_mutations:
            self.perform_mutations(
                evolutions=evolutions,
                end=end,
                end_sig=end_sig,
                sql_name=sql_name,
                rescan_indexes=rescan_indexes,
                db_name=db_name,
                create_test_data_func=(create_test_data_func or
                                       self.default_create_test_data))

    def perform_diff_test(self, end_sig, diff_text=None, expected_hint=None,
                          expect_empty=False):
        """Generate a diff between signatures and check for expected results.

        The registered base signature and the provided ending signature will
        be diffed, asserted to be empty/not empty (depending on the arguments),
        and then checked against the provided diff text and hint.

        Args:
            end_sig (django_evolution.signature.ProjectSignature):
                The expected project signature at the end of the evolution.
                This is generated by :py:meth:`make_end_signatures`.

            diff_text (unicode, optional):
                The expected generated text describing a diff that must
                match, if provided.

            expected_hint (unicode, optional):
                The expected generated hint text that must match, if provided.

            expect_empty (bool, optional):
                Whether the diff is expected to be empty.

        Returns:
            django_evolution.diff.Diff:
            The resulting diff.

        Raises:
            AssertionError:
                One of the expectations has failed.
        """
        d = Diff(self.start_sig, end_sig)
        self.assertEqual(d.is_empty(), expect_empty)

        if not expect_empty:
            if diff_text is not None:
                self.assertEqual(str(d), diff_text)

            if expected_hint is not None:
                self.assertEqual(
                    [str(e) for e in d.evolution()['tests']],
                    expected_hint)

        return d

    def perform_simulations(self, evolutions, end_sig, ignore_apps=False,
                            db_name=None):
        """Run simulations and verify that they result in an end signature.

        This will run through an evolution chain, simulating each one on a
        copy of the starting signature, and then verifying that the signature
        is properly transformed into the expected ending signature.

        Args:
            evolutions (list of django_evolution.mutations.BaseMutation):
                The evolution chain to run simulations on.

            end_sig (django_evolution.signature.ProjectSignature):
                The expected ending signature.

            ignore_apps (bool, optional):
                Whether to ignore changes to the list of applications.

            db_name (unicode, optional):
                The name of the database to perform the simulations against.

        Returns:
            django_evolution.signature.ProjectSignature:
            The resulting modified signature.

        Raises:
            AssertionError:
                The modified signature did not match the expected end
                signature.
        """
        db_name = db_name or self.default_database_name

        self.test_database_state = self.database_state.clone()
        test_sig = self.start_sig.clone()

        for mutation in evolutions:
            mutation.run_simulation(app_label='tests',
                                    project_sig=test_sig,
                                    database_state=self.test_database_state,
                                    database=db_name)

        # Check that the simulation's changes results in an empty diff.
        d = Diff(test_sig, end_sig)
        self.assertTrue(d.is_empty(ignore_apps=ignore_apps))

        return test_sig

    def perform_mutations(self, evolutions, end, end_sig, sql_name=None,
                          rescan_indexes=True, db_name=None,
                          create_test_data_func=None):
        """Apply mutations that and verify the results.

        This will run through the evolution chain, applying each mutation
        on the database and against the signature, and then verifying the
        resulting signature and generated SQL.

        Args:
            evolutions (list of django_evolution.mutations.BaseMutation):
                The evolution chain to run simulations on.

            end (collections.OrderedDict):
                The expected model map at the end of the evolution. This
                is generated by :py:meth:`make_end_signatures`.

            end_sig (django_evolution.signature.ProjectSignature):
                The expected ending signature. This is generated by
                :py:meth:`make_end_signatures`.

            sql_name (unicode, optional):
                The name of the registered SQL content for the database being
                tested. If not provided, the SQL won't be compared.

            rescan_indexes (bool, optional):
                Whether to re-scan the list of table indexes after applying
                the mutations.

            db_name (unicode, optional):
                The name of the database to apply the evolutions against.

            create_test_data_func (callable, optional):
                A function to call in order to create data in the database for
                the initial models, before applying mutations. It must take
                a database name.

        Raises:
            AssertionError:
                The resulting SQL did not match.

            django.db.utils.OperationalError:
                There was an error executing SQL.
        """
        app_label = 'tests'

        def run_mutations():
            if rescan_indexes:
                self.test_database_state.rescan_tables()

            app_mutator = AppMutator(app_label=app_label,
                                     project_sig=test_sig,
                                     database_state=self.test_database_state,
                                     database=db_name)
            app_mutator.run_mutations(evolutions)

            return app_mutator.to_sql()

        db_name = db_name or self.default_database_name

        self.test_database_state = self.database_state.clone()
        test_sig = self.start_sig.clone()

        with ensure_test_db(model_entries=six.iteritems(self.start),
                            end_model_entries=six.iteritems(end),
                            app_label=app_label,
                            database=db_name):
            if create_test_data_func:
                create_test_data_func(db_name)

            sql = execute_test_sql(run_mutations(),
                                   database=db_name)

        if sql_name is not None:
            self.assertSQLMappingEqual(sql,
                                       sql_name,
                                       database=db_name)

    def register_model(self, model, name, db_name=None, **kwargs):
        """Register a model for the test.

        This will register not only this model, but any models in
        :py:attr:`pre_extra_models` and :py:attr:`extra_models`. It will
        not include :py:attr:`base_model`.

        Args:
            model (type):
                The main :py:class:`~django.db.models.Model` to register.

            name (unicode):
                The name to use when for the model when registering. This
                doesn't have to match the model's actual name.

            db_name (unicode, optional):
                The name of the database to register this model on.

            **kwargs (dict):
                Additional keyword arguments to pass to
                :py:func:`~django_evolution.tests.utils.register_models`.

        Returns:
            collections.OrderedDict:
            A dictionary of registered models. The keys are model names, and
            the values are the models.
        """
        self._models_registered = True

        models = self.pre_extra_models + [(name, model)] + self.extra_models

        return register_models(database_state=self.database_state,
                               models=models,
                               new_app_label='tests',
                               db_name=db_name or self.default_database_name,
                               **kwargs)

    def create_test_proj_sig(self, model, name, extra_models=[],
                             pre_extra_models=[]):
        """Create a project signature for the given models.

        The signature will include not only these models, but any models in
        :py:attr:`pre_extra_models` and :py:attr:`extra_models`. It will
        not include :py:attr:`base_model`.

        Args:
            model (type):
                The main :py:class:`~django.db.models.Model` to include
                in the signature.

            name (unicode):
                The name to use when for the model. This doesn't have to match
                the model's actual name.

            extra_models (list of type, optional):
                An additional list of extra models to register after ``model``
                (but before the class-defined :py:attr:`extra_models`).

            pre_extra_models (list of type, optional):
                An additional list of extra models to register before ``model``
                (but after :py:attr:`pre_extra_models`).

        Returns:
            django_evolution.signature.ProjectSignature:
            The generated project signature.
        """
        return create_test_project_sig(models=(
            self.pre_extra_models + pre_extra_models + [(name, model)] +
            extra_models + self.extra_models
        ))

    def copy_models(self, models):
        """Copy a list of models.

        This will be a deep copy, allowing any of the copied models to be
        altered without affecting the originals.

        Args:
            models (list of type):
                The list of :py:class:`~django.db.models.Model` classes.

        Returns:
            list of type:
            The copied list of :py:class:`~django.db.models.Model` classes.
        """
        return copy.deepcopy(models)

    @contextmanager
    def override_db_routers(self, routers):
        """Override database routers for a test.

        This clears the router cache before and after the test, allowing
        custom routers to be used during unit tests.

        Args:
            routers (list):
                The list of router class paths or instances.

        Yields:
            The context.
        """
        try:
            with override_settings(DATABASE_ROUTERS=routers):
                self.clear_routers_cache()
                yield
        finally:
            self.clear_routers_cache()

    def clear_routers_cache(self):
        """Clear the router cache."""
        router.routers = ConnectionRouter().routers 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'
from datetime import datetime
from django.db import connection, models
from django_evolution.errors import EvolutionException, SimulationFailure
from django_evolution.mutations import AddField, DeleteField
from django_evolution.signature import (AppSignature,
                                        FieldSignature,
                                        ModelSignature,
                                        ProjectSignature)
from django_evolution.tests.base_test_case import EvolutionTestCase
from django_evolution.tests.models import BaseTestModel
from __future__ import unicode_literals





class AddSequenceFieldInitial(object):
    def __init__(self, suffix):
        self.suffix = suffix

    def __call__(self):
        return connection.ops.quote_name('int_field')


class AddAnchor1(BaseTestModel):
    value = models.IntegerField()


class AddAnchor2(BaseTestModel):
    value = models.IntegerField()

    class Meta(BaseTestModel.Meta):
        db_table = 'custom_add_anchor_table'


class AddBaseModel(BaseTestModel):
    char_field = models.CharField(max_length=20)
    int_field = models.IntegerField()


class CustomAddTableModel(BaseTestModel):
    value = models.IntegerField()
    alt_value = models.CharField(max_length=20)

    class Meta(BaseTestModel.Meta):
        db_table = 'custom_table_name'


class AddFieldTests(EvolutionTestCase):
    """Testing AddField mutations."""
    sql_mapping_key = 'add_field'
    default_base_model = AddBaseModel
    default_extra_models = [
        ('AddAnchor1', AddAnchor1),
        ('AddAnchor2', AddAnchor2),
    ]

    DIFF_TEXT = (
        "In model tests.TestModel:\n"
        "    Field 'added_field' has been added"
    )

    def default_create_test_data(self, db_name):
        """Create test data for the base model.

        Args:
            db_name (unicode):
                The name of the database to create models on.
        """
        AddBaseModel.objects.using(db_name).create(char_field='test',
                                                   int_field=100)

    def test_with_bad_app(self):
        """Testing AddField with application not in signature"""
        mutation = AddField('TestModel', 'char_field1', models.CharField)

        message = (
            'Cannot add the field "char_field1" to model "badapp.TestModel". '
            'The application could not be found in the signature.'
        )

        with self.assertRaisesMessage(SimulationFailure, message):
            mutation.run_simulation(app_label='badapp',

                                    project_sig=ProjectSignature(),