diff --git a/django_evolution/compat/apps.py b/django_evolution/compat/apps.py
index ebe036b5502a871160b5c89728a4fc2b8423c11a..b882f5863233af400375fd197a0ded98cddbe511 100644
--- a/django_evolution/compat/apps.py
+++ b/django_evolution/compat/apps.py
@@ -4,8 +4,12 @@
 translate to the various versions of Django that are supported.
 """
 
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
 try:
     # Django >= 1.7
+    from django.apps.config import AppConfig
     from django.apps.registry import apps
 
     cache = None
@@ -14,6 +18,7 @@
     from django.db.models.loading import cache
 
     apps = None
+    AppConfig = None
 
 from django_evolution.compat.datastructures import OrderedDict
 from django_evolution.compat.models import all_models
@@ -48,7 +53,21 @@ def get_app(app_label, emptyOK=False):
     """
     if apps:
         # Django >= 1.7
-        return apps.get_app(app_label)
+        try:
+            models_module = apps.get_app_config(app_label).models_module
+        except LookupError as e:
+            # Convert this to an ImproperlyConfigured.
+            raise ImproperlyConfigured(*e.args)
+
+        if models_module is None:
+            if emptyOK:
+                raise LookupError('..')
+
+            raise ImproperlyConfigured(
+                'App with label %s is missing a models.py module.'
+                % app_label)
+
+        return models_module
     else:
         # Django < 1.7
         return cache.get_app(app_label, emptyOK)
@@ -57,14 +76,19 @@ def get_app(app_label, emptyOK=False):
 def get_apps():
     """Return the list of all installed apps with models.
 
-    This returns the apps from the old-style cache on Django < 1.7.
+    This returns the apps from the app registry on Django >= 1.7, and from
+    the old-style cache on Django < 1.7.
 
     Returns:
         list: A list of all the modules containing model classes.
     """
     if apps:
         # Django >= 1.7
-        raise NotImplementedError
+        return [
+            app.models_module
+            for app in apps.get_app_configs()
+            if app.models_module is not None
+        ]
     else:
         # Django < 1.7
         return cache.get_apps()
@@ -83,7 +107,7 @@ def is_app_registered(app):
     """
     if apps:
         # Django >= 1.7
-        raise NotImplementedError
+        return apps.is_installed(app.__name__)
     else:
         # Django < 1.7
         return app in cache.app_store
@@ -103,7 +127,10 @@ def register_app(app_label, app):
     """
     if apps:
         # Django >= 1.7
-        raise NotImplementedError
+        app_config = AppConfig(app.__name__, app)
+        app_config.label = app_label
+
+        apps.set_installed_apps(settings.INSTALLED_APPS + [app_config])
     else:
         # Django < 1.7
         cache.app_store[app] = len(cache.app_store)
@@ -121,6 +148,13 @@ def unregister_app(app_label):
         app_label (str):
             The label of the app to register.
     """
+    if apps:
+        # Django >= 1.7
+        #
+        # We need to balance the ``set_installed_apps`` from
+        # :py:func:`register_app` here.
+        apps.unset_installed_apps()
+
     all_models[app_label].clear()
     clear_app_cache()
 
@@ -141,6 +175,9 @@ def register_app_models(app_label, model_infos, reset=False):
             If set, the old list will be overwritten with the new list.
     """
     if app_label not in all_models:
+        # This isn't really needed for Django 1.7+ (which uses defaultdict
+        # with OrderedDict), but it's needed for earlier versions, so do it
+        # explicitly.
         all_models[app_label] = OrderedDict()
 
     model_dict = all_models[app_label]
@@ -176,7 +213,15 @@ def clear_app_cache():
     """
     if apps:
         # Django >= 1.7
-        raise NotImplementedError
+        apps.clear_cache()
     elif hasattr(cache, '_get_models_cache'):
         # Django >= 1.2, < 1.7
         cache._get_models_cache.clear()
+
+
+__all__ = [
+    'apps',
+    'clear_app_cache',
+    'get_app',
+    'get_apps',
+]
diff --git a/django_evolution/compat/db.py b/django_evolution/compat/db.py
index 19b6b2c05289431a6c1bdc855411473b679dc99b..7d7cfb82239897841d20c8c5615268d314b13767 100644
--- a/django_evolution/compat/db.py
+++ b/django_evolution/compat/db.py
@@ -10,9 +10,32 @@
 import django
 from django.core.management import color, sql
 from django.db import connections, transaction
-from django.db.backends.util import truncate_name
 from django.db.utils import DEFAULT_DB_ALIAS
 
+try:
+    # Django >= 1.7
+    from django.apps.registry import apps
+    from django.db.backends.utils import truncate_name
+    from django.db.migrations.executor import MigrationExecutor
+except ImportError:
+    # Django < 1.7
+    from django.db.backends.util import truncate_name
+
+    apps = None
+    MigrationExecutor = None
+
+try:
+    # Django >= 1.8
+    from django.db.backends.base.schema import BaseDatabaseSchemaEditor
+except ImportError:
+    try:
+        # Django == 1.7
+        from django.db.backends.schema import BaseDatabaseSchemaEditor
+    except ImportError:
+        # Django < 1.7
+        BaseDatabaseSchemaEditor = None
+
+from django_evolution.compat.models import get_models
 from django_evolution.support import supports_index_together
 
 
@@ -69,7 +92,18 @@ def digest(connection, *args):
         str:
         The resulting digest hash.
     """
-    return connection.creation._digest(*args)
+    if (BaseDatabaseSchemaEditor and
+        hasattr(BaseDatabaseSchemaEditor, '_digest')):
+        # Django >= 1.8
+        #
+        # Note that _digest() is a classmethod that is common across all
+        # database backends. We don't need to worry about using a
+        # per-instance version. If that changes, we'll need to create a
+        # SchemaEditor.
+        return BaseDatabaseSchemaEditor._digest(*args)
+    else:
+        # Django < 1.8
+        return connection.creation._digest(*args)
 
 
 def sql_create(app, db_name=None):
@@ -91,10 +125,19 @@ def sql_create(app, db_name=None):
     """
     connection = connections[db_name or DEFAULT_DB_ALIAS]
 
-    style = color.no_style()
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        with connection.schema_editor(collect_sql=True) as schema_editor:
+            for model in get_models(app):
+                schema_editor.create_model(model)
+
+        return schema_editor.collected_sql
+    else:
+        # Django < 1.7
+        style = color.no_style()
 
-    return (sql.sql_create(app, style, connection) +
-            sql.sql_indexes(app, style, connection))
+        return (sql.sql_create(app, style, connection) +
+                sql.sql_indexes(app, style, connection))
 
 
 def _sql_delete_model(connection, schema_editor, model, deleted_models,
@@ -199,9 +242,30 @@ def sql_delete(app, db_name=None):
         The list of SQL statements for deleting the models and constraints.
     """
     connection = connections[db_name or DEFAULT_DB_ALIAS]
-    style = color.no_style()
 
-    return sql.sql_delete(app, style, connection)
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        all_table_names = set(connection.introspection.table_names())
+        constraints_sql = []
+        models_sql = []
+        deleted_models = set()
+        deleted_refs = set()
+
+        with connection.schema_editor(collect_sql=True) as schema_editor:
+            for model in get_models(app, include_auto_created=True):
+                temp_constraints_sql, temp_models_sql = \
+                    _sql_delete_model(connection, schema_editor, model,
+                                      deleted_models, deleted_refs,
+                                      all_table_names)
+                constraints_sql += temp_constraints_sql
+                models_sql += temp_models_sql
+
+        return constraints_sql + models_sql
+    else:
+        # Django < 1.7
+        style = color.no_style()
+
+        return sql.sql_delete(app, style, connection)
 
 
 def sql_create_for_many_to_many_field(connection, model, field):
@@ -223,30 +287,38 @@ def sql_create_for_many_to_many_field(connection, model, field):
         list:
         The list of SQL statements for creating the table and constraints.
     """
-    style = color.no_style()
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        with connection.schema_editor(collect_sql=True) as schema_editor:
+            schema_editor.create_model(field.rel.through)
 
-    if field.rel.through:
-        references = {}
-        pending_references = {}
+        return schema_editor.collected_sql
+    else:
+        # Django < 1.7
+        style = color.no_style()
 
-        sql, references = connection.creation.sql_create_model(
-            field.rel.through, style)
+        if field.rel.through:
+            references = {}
+            pending_references = {}
 
-        # Sort the list, in order to create consistency in the order of
-        # ALTER TABLEs. This is primarily needed for unit tests.
-        for refto, refs in sorted(references.iteritems(),
-                                  key=lambda i: repr(i)):
-            pending_references.setdefault(refto, []).extend(refs)
-            sql.extend(sql_add_constraints(connection, refto,
-                                           pending_references))
+            sql, references = connection.creation.sql_create_model(
+                field.rel.through, style)
 
-        sql.extend(sql_add_constraints(connection, field.rel.through,
-                                       pending_references))
-    else:
-        sql = connection.creation.sql_for_many_to_many_field(
-            model, field, style)
+            # Sort the list, in order to create consistency in the order of
+            # ALTER TABLEs. This is primarily needed for unit tests.
+            for refto, refs in sorted(references.iteritems(),
+                                      key=lambda i: repr(i)):
+                pending_references.setdefault(refto, []).extend(refs)
+                sql.extend(sql_add_constraints(connection, refto,
+                                               pending_references))
+
+            sql.extend(sql_add_constraints(connection, field.rel.through,
+                                           pending_references))
+        else:
+            sql = connection.creation.sql_for_many_to_many_field(
+                model, field, style)
 
-    return sql
+        return sql
 
 
 def sql_indexes_for_field(connection, model, field):
@@ -268,8 +340,21 @@ def sql_indexes_for_field(connection, model, field):
         list:
         The list of SQL statements for creating the indexes.
     """
-    return connection.creation.sql_indexes_for_field(model, field,
-                                                     color.no_style())
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        #
+        # Unlike sql_indexes_for_field(), _create_index_sql() won't be
+        # checking whether it *should* create an index for the given field.
+        # We have to check that here instead.
+        if not field.db_index or field.unique:
+            return []
+
+        with connection.schema_editor() as schema_editor:
+            return ['%s;' % schema_editor._create_index_sql(model, [field])]
+    else:
+        # Django < 1.7
+        return connection.creation.sql_indexes_for_field(model, field,
+                                                         color.no_style())
 
 
 def sql_indexes_for_fields(connection, model, fields, index_together=False):
@@ -294,8 +379,20 @@ def sql_indexes_for_fields(connection, model, fields, index_together=False):
         list:
         The list of SQL statements for creating the indexes.
     """
-    return connection.creation.sql_indexes_for_fields(model, fields,
-                                                      color.no_style())
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        if index_together:
+            suffix = '_idx'
+        else:
+            suffix = ''
+
+        with connection.schema_editor() as schema_editor:
+            return ['%s;' % schema_editor._create_index_sql(model, fields,
+                                                            suffix=suffix)]
+    else:
+        # Django < 1.7
+        return connection.creation.sql_indexes_for_fields(model, fields,
+                                                          color.no_style())
 
 
 def sql_indexes_for_model(connection, model):
@@ -314,8 +411,17 @@ def sql_indexes_for_model(connection, model):
         list:
         The list of SQL statements for creating the indexes.
     """
-    return connection.creation.sql_indexes_for_model(model,
-                                                     color.no_style())
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        with connection.schema_editor() as schema_editor:
+            return [
+                '%s;' % s
+                for s in schema_editor._model_indexes_sql(model)
+            ]
+    else:
+        # Django < 1.7
+        return connection.creation.sql_indexes_for_model(model,
+                                                         color.no_style())
 
 
 def sql_delete_constraints(connection, model, remove_refs):
@@ -341,8 +447,29 @@ def sql_delete_constraints(connection, model, remove_refs):
         list:
         The list of SQL statements for deleting constraints.
     """
-    return connection.creation.sql_remove_table_constraints(
-        model, remove_refs, color.no_style())
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        meta = model._meta
+
+        if not meta.managed or meta.swapped or meta.proxy:
+            return []
+
+        sql = []
+
+        with connection.schema_editor() as schema_editor:
+            for rel_class, f in remove_refs[model]:
+                fk_names = schema_editor._constraint_names(
+                    rel_class, [f.column], foreign_key=True)
+
+                for fk_name in fk_names:
+                    sql.append('%s;' % schema_editor._delete_constraint_sql(
+                        schema_editor.sql_delete_fk, rel_class, fk_name))
+
+        return sql
+    else:
+        # Django < 1.7
+        return connection.creation.sql_remove_table_constraints(
+            model, remove_refs, color.no_style())
 
 
 def sql_add_constraints(connection, model, refs):
@@ -368,8 +495,60 @@ def sql_add_constraints(connection, model, refs):
         list:
         The list of SQL statements for adding constraints.
     """
-    return connection.creation.sql_for_pending_references(
-        model, color.no_style(), refs)
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        meta = model._meta
+
+        if not meta.managed or meta.swapped:
+            return []
+
+        sql = []
+
+        if model in refs:
+            with connection.schema_editor() as schema_editor:
+                qn = schema_editor.quote_name
+
+                for rel_class, f in refs[model]:
+                    # Ideally, we would use schema_editor._create_fk_sql here,
+                    # but it depends on a lot more state than we have
+                    # available currently in our mocks. So we have to build
+                    # the SQL ourselves. It's not a lot of work, fortunately.
+                    #
+                    # For reference, this is what we'd ideally do:
+                    #
+                    #     sql.append('%s;' % schema_editor._create_fk_sql(
+                    #         rel_class, f,
+                    #         '_fk_%(to_table)s_%(to_column)s'))
+                    #
+                    rel_meta = rel_class._meta
+                    to_column = meta.get_field(f.rel.field_name).column
+
+                    suffix = '_fk_%(to_table)s_%(to_column)s' % {
+                        'to_table': meta.db_table,
+                        'to_column': to_column,
+                    }
+
+                    name = schema_editor._create_index_name(rel_class,
+                                                            [f.column],
+                                                            suffix=suffix)
+
+                    create_sql = schema_editor.sql_create_fk % {
+                        'table': qn(rel_meta.db_table),
+                        'name': qn(name),
+                        'column': qn(f.column),
+                        'to_table': qn(meta.db_table),
+                        'to_column': qn(to_column),
+                    }
+
+                    sql.append('%s;' % create_sql)
+
+            del refs[model]
+
+        return sql
+    else:
+        # Django < 1.7
+        return connection.creation.sql_for_pending_references(
+            model, color.no_style(), refs)
 
 
 def create_index_name(connection, table_name, field_names=[], col_names=[],
@@ -401,8 +580,24 @@ def create_index_name(connection, table_name, field_names=[], col_names=[],
         str:
         The generated index name for this version of Django.
     """
-    if django.VERSION[:2] >= (1, 5):
-        # Django >= 1.5
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        #
+        # Fake a table for the call. It only needs _meta.db_table.
+        class TempModel(object):
+            class _meta:
+                db_table = table_name
+
+        if unique:
+            assert not suffix
+            suffix = '_uniq'
+
+        with connection.schema_editor() as schema_editor:
+            return schema_editor._create_index_name(TempModel,
+                                                    col_names or field_names,
+                                                    suffix=suffix)
+    elif django.VERSION[:2] >= (1, 5):
+        # Django >= 1.5, < 1.7
         #
         # This comes from sql_indexes_for_fields().
         index_name = '%s_%s' % (table_name,
@@ -452,15 +647,23 @@ def create_index_together_name(connection, table_name, field_names):
         str:
         The generated index name for this version of Django.
     """
-    # Django < 1.7
-    #
-    # index_together was introduced in Django 1.5, and prior to 1.7, the
-    # format was identical to that of normal indexes.
-    assert django.VERSION[:2] >= (1, 5)
+    if BaseDatabaseSchemaEditor:
+        # Django >= 1.7
+        #
+        # Starting in 1.7, the index_together indexes were given a "_idx"
+        # suffix.
+        return create_index_name(connection, table_name, field_names,
+                                 field_names, suffix='_idx')
+    else:
+        # Django < 1.7
+        #
+        # index_together was introduced in Django 1.5, and prior to 1.7, the
+        # format was identical to that of normal indexes.
+        assert django.VERSION[:2] >= (1, 5)
 
-    index_name = '%s_%s' % (table_name, digest(connection, field_names))
+        index_name = '%s_%s' % (table_name, digest(connection, field_names))
 
-    return truncate_name(index_name, connection.ops.max_name_length())
+        return truncate_name(index_name, connection.ops.max_name_length())
 
 
 def create_constraint_name(connection, r_col, col, r_table, table):
@@ -488,9 +691,20 @@ def create_constraint_name(connection, r_col, col, r_table, table):
         str:
         The generated constraint name for this version of Django.
     """
-    return truncate_name(
-        '%s_refs_%s_%s' % (r_col, col, digest(connection, r_table, table)),
-        connection.ops.max_name_length())
+    if BaseDatabaseSchemaEditor:
+        suffix = '_fk_%(to_table)s_%(to_column)s' % {
+            'to_table': table,
+            'to_column': col,
+        }
+
+        # No need to truncate here, since create_index_name() will do it for
+        # us.
+        return create_index_name(connection, r_table, col_names=[r_col],
+                                 suffix=suffix)
+    else:
+        return truncate_name(
+            '%s_refs_%s_%s' % (r_col, col, digest(connection, r_table, table)),
+            connection.ops.max_name_length())
 
 
 __all__ = [
diff --git a/django_evolution/compat/models.py b/django_evolution/compat/models.py
index 8e743033a3c3e5491a660f06c008d161d1431690..2bc26fd778eb44e8eb1fb413caf2f39ae62445d5 100644
--- a/django_evolution/compat/models.py
+++ b/django_evolution/compat/models.py
@@ -7,6 +7,8 @@
 try:
     # Django >= 1.7
     from django.apps.registry import apps
+    from django.contrib.contenttypes.fields import (GenericForeignKey,
+                                                    GenericRelation)
 
     cache = None
     all_models = apps.all_models
@@ -40,7 +42,18 @@ def get_models(app_mod=None, include_auto_created=False):
     """
     if apps:
         # Django >= 1.7
-        raise NotImplementedError
+        if app_mod is None:
+            return apps.get_models(include_auto_created=include_auto_created)
+        else:
+            app_label = app_mod.__name__.split('.')[-2]
+
+            try:
+                app_config = apps.get_app_config(app_label)
+
+                return list(app_config.get_models(
+                    include_auto_created=include_auto_created))
+            except LookupError:
+                return []
     else:
         # Django < 1.7
         return _get_models(app_mod, include_auto_created=include_auto_created)
diff --git a/django_evolution/db/common.py b/django_evolution/db/common.py
index 68eabcaeadd098e9ba155705f1357afb7d86678c..dcbef6bf4d55db4125eadce0ac55984dc59f31d9 100644
--- a/django_evolution/db/common.py
+++ b/django_evolution/db/common.py
@@ -165,19 +165,18 @@ def rename_table(self, model, old_db_tablename, db_tablename):
         sql_result.add(self.get_rename_table_sql(
             model, old_db_tablename, db_tablename))
 
-        if self.supports_constraints:
-            for relto in models:
-                for rel_class, f in refs[relto]:
-                    if rel_class._meta.db_table == old_db_tablename:
-                        rel_class._meta.db_table = db_tablename
-
-                    rel_class._meta.db_table = \
-                        truncate_name(rel_class._meta.db_table,
-                                      max_name_length)
-
-                if self.supports_constraints:
-                    sql_result.add_post_sql(sql_add_constraints(
-                        self.connection, relto, refs))
+        for relto in models:
+            for rel_class, f in refs[relto]:
+                if rel_class._meta.db_table == old_db_tablename:
+                    rel_class._meta.db_table = db_tablename
+
+                rel_class._meta.db_table = \
+                    truncate_name(rel_class._meta.db_table,
+                                  max_name_length)
+
+            if self.supports_constraints:
+                sql_result.add_post_sql(sql_add_constraints(
+                    self.connection, relto, refs))
 
         return sql_result
 
diff --git a/django_evolution/db/mysql.py b/django_evolution/db/mysql.py
index bbee8eec84b23d3ad2af26b8406912cddc46aee8..592b4a3df39870f4be30d6fe5f694ea44911f244 100644
--- a/django_evolution/db/mysql.py
+++ b/django_evolution/db/mysql.py
@@ -1,6 +1,7 @@
 from django.core.management import color
 
 from django_evolution.compat.db import sql_delete_constraints
+from django_evolution.compat.models import get_rel_target_field
 from django_evolution.db.common import BaseEvolutionOperations
 from django_evolution.db.sql_result import AlterTableSQLResult, SQLResult
 
@@ -178,6 +179,41 @@ def get_rename_table_sql(self, model, old_db_tablename, db_tablename):
             % (qn(old_db_tablename), qn(db_tablename))
         ])
 
+    def get_default_index_name(self, table_name, field):
+        """Return a default index name for the database.
+
+        This will return an index name for the given field that matches what
+        the database or Django database backend would automatically generate
+        when marking a field as indexed or unique.
+
+        This can be overridden by subclasses if the database or Django
+        database backend provides different values.
+
+        Args:
+            table_name (str):
+                The name of the table for the index.
+
+            field (django.db.models.Field):
+                The field for the index.
+
+        Returns:
+            str:
+            The name of the index.
+        """
+        if (hasattr(self.connection, 'schema_editor') and
+            field.rel and field.db_constraint):
+            # Django >= 1.7
+            target_field = get_rel_target_field(field)
+
+            return self.connection.schema_editor()._create_index_name(
+                field.model,
+                [field.column],
+                suffix='_fk_%s_%s' % (target_field.model._meta.db_table,
+                                      target_field.column))
+
+        return super(EvolutionOperations, self).get_default_index_name(
+            table_name, field)
+
     def get_indexes_for_table(self, table_name):
         cursor = self.connection.cursor()
         qn = self.connection.ops.quote_name
diff --git a/django_evolution/db/postgresql.py b/django_evolution/db/postgresql.py
index d4cc6c346a83dc3082eb77fed0839d6f71951840..f95e0dc07ee36d7847d5c8ac8341e30a9b47b978 100644
--- a/django_evolution/db/postgresql.py
+++ b/django_evolution/db/postgresql.py
@@ -1,3 +1,5 @@
+import django
+
 from django_evolution.compat.db import truncate_name
 from django_evolution.db.common import BaseEvolutionOperations
 from django_evolution.db.sql_result import AlterTableSQLResult
@@ -62,14 +64,21 @@ def get_default_index_name(self, table_name, field):
             str:
             The name of the index.
         """
-        assert field.unique or field.db_index
+        if django.VERSION[:2] >= (1, 7):
+            # On Django 1.7+, the default behavior for the index name is used.
+            return super(EvolutionOperations, self).get_default_index_name(
+                table_name, field)
+        else:
+            # On Django < 1.7, a custom form of index name is used.
+            assert field.unique or field.db_index
 
-        if field.unique:
-            index_name = '%s_%s_key' % (table_name, field.column)
-        elif field.db_index:
-            index_name = '%s_%s' % (table_name, field.column)
+            if field.unique:
+                index_name = '%s_%s_key' % (table_name, field.column)
+            elif field.db_index:
+                index_name = '%s_%s' % (table_name, field.column)
 
-        return truncate_name(index_name, self.connection.ops.max_name_length())
+            return truncate_name(index_name,
+                                 self.connection.ops.max_name_length())
 
     def get_indexes_for_table(self, table_name):
         cursor = self.connection.cursor()
diff --git a/django_evolution/management/__init__.py b/django_evolution/management/__init__.py
index 07b31c63ae26c05a23794ca4444bad7a2743ec3e..f9b18b06fc244c5d34fe8ef97498d3a762d9d59c 100644
--- a/django_evolution/management/__init__.py
+++ b/django_evolution/management/__init__.py
@@ -1,8 +1,10 @@
+import logging
 try:
     import cPickle as pickle
 except ImportError:
     import pickle as pickle
 
+import django
 from django.conf import settings
 from django.core.management.color import color_style
 from django.db.models import signals
@@ -140,4 +142,9 @@ def evolution(app, created_models, verbosity=1, **kwargs):
 
 
 if getattr(settings, 'DJANGO_EVOLUTION_ENABLED', True):
-    signals.post_syncdb.connect(evolution)
+    if hasattr(signals, 'post_syncdb'):
+        signals.post_syncdb.connect(evolution)
+    else:
+        logging.error('Django Evolution cannot automatically install '
+                      'baselines or evolve on Django %s',
+                      django.get_version())
diff --git a/django_evolution/mutations.py b/django_evolution/mutations.py
index 18180349ceb5e82652cd97631af90efdccbb1022..eced2bea387b2a4b389ec12fb696b2a2b59df376 100644
--- a/django_evolution/mutations.py
+++ b/django_evolution/mutations.py
@@ -114,6 +114,9 @@ def create_field(proj_sig, field_name, field_type, field_attrs, parent_model):
 
     field.set_attributes_from_name(field_name)
 
+    # Needed in Django >= 1.7, for index building.
+    field.model = parent_model
+
     return field
 
 
@@ -133,6 +136,7 @@ def __init__(self, proj_sig, app_name, model_name, model_sig):
             'has_auto_field': None,
             'db_tablespace': None,
             'swapped': False,
+            'index_together': [],
         }
         self.meta.update(model_sig['meta'])
         self._fields = OrderedDict()
@@ -166,6 +170,9 @@ def setup_fields(self, model, stub=False):
                     self.pk = field
 
     def __getattr__(self, name):
+        if name == 'model_name':
+            return self.object_name
+
         return self.meta[name]
 
     def get_field(self, name):
@@ -763,8 +770,15 @@ def __init__(self, model_name, prop_name, new_value):
         self.new_value = new_value
 
     def __str__(self):
+        if self.prop_name in ('index_together', 'unique_together'):
+            # Make sure these always appear as lists and not tuples, for
+            # compatibility.
+            norm_value = list(self.new_value)
+        else:
+            norm_value = self.new_value
+
         return ("ChangeMeta('%s', '%s', %r)"
-                % (self.model_name, self.prop_name, self.new_value))
+                % (self.model_name, self.prop_name, norm_value))
 
     def simulate(self, app_label, proj_sig, database_sig, database=None):
         app_sig = proj_sig[app_label]
diff --git a/django_evolution/tests/db/mysql.py b/django_evolution/tests/db/mysql.py
index 3e50c668cc3f8c62c0231db40faa2c7b90d244ed..a71bd8b56e682c72136369d91becbfd3f1d49f13 100644
--- a/django_evolution/tests/db/mysql.py
+++ b/django_evolution/tests/db/mysql.py
@@ -1,3 +1,6 @@
+import django
+from django.db import connection
+
 from django_evolution.tests.utils import (generate_unique_constraint_name,
                                           make_generate_constraint_name,
                                           make_generate_index_name)
@@ -7,6 +10,12 @@
 generate_index_name = make_generate_index_name('mysql')
 
 
+if hasattr(connection, 'data_types'):
+    datetime_type = connection.data_types['DateTimeField']
+else:
+    datetime_type = 'datetime'
+
+
 add_field = {
     'AddNonNullNonCallableColumnModel': '\n'.join([
         'ALTER TABLE `tests_testmodel`'
@@ -53,8 +62,9 @@
 
     'AddDateColumnModel': '\n'.join([
         'ALTER TABLE `tests_testmodel`'
-        ' ADD COLUMN `added_field` datetime NOT NULL'
-        ' DEFAULT 2007-12-13 16:42:00;',
+        ' ADD COLUMN `added_field` %s NOT NULL'
+        ' DEFAULT 2007-12-13 16:42:00;'
+        % datetime_type,
 
         'ALTER TABLE `tests_testmodel`'
         ' ALTER COLUMN `added_field` DROP DEFAULT;',
@@ -125,79 +135,350 @@
         % generate_index_name('tests_testmodel', 'added_field_id',
                               'added_field'),
     ]),
+}
 
-    'AddManyToManyDatabaseTableModel': '\n'.join([
-        'CREATE TABLE `tests_testmodel_added_field` (',
-        '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
-        '    `testmodel_id` integer NOT NULL,',
-        '    `addanchor1_id` integer NOT NULL,',
-        '    UNIQUE (`testmodel_id`, `addanchor1_id`)',
-        ')',
-        ';',
-
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor1_id`)'
-        ' REFERENCES `tests_addanchor1` (`id`);'
-        % generate_constraint_name('addanchor1_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_addanchor1'),
-
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
-        ' REFERENCES `tests_testmodel` (`id`);'
-        % generate_constraint_name('testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
-
-    'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
-        'CREATE TABLE `tests_testmodel_added_field` (',
-        '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
-        '    `testmodel_id` integer NOT NULL,',
-        '    `addanchor2_id` integer NOT NULL,',
-        '    UNIQUE (`testmodel_id`, `addanchor2_id`)',
-        ')',
-        ';',
-
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor2_id`)'
-        ' REFERENCES `custom_add_anchor_table` (`id`);'
-        % generate_constraint_name('addanchor2_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'custom_add_anchor_table'),
-
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
-        ' REFERENCES `tests_testmodel` (`id`);'
-        % generate_constraint_name('testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
-
-    'AddManyToManySelf': '\n'.join([
-        'CREATE TABLE `tests_testmodel_added_field` (',
-        '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
-        '    `from_testmodel_id` integer NOT NULL,',
-        '    `to_testmodel_id` integer NOT NULL,',
-        '    UNIQUE (`from_testmodel_id`, `to_testmodel_id`)',
-        ')',
-        ';',
 
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`from_testmodel_id`)'
-        ' REFERENCES `tests_testmodel` (`id`);'
-        % generate_constraint_name('from_testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
+if django.VERSION[:2] >= (1, 9):
+    # Django 1.9+ no longer includes a UNIQUE keyword in the table creation,
+    # instead creating these through constraints.
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor1_id` integer NOT NULL'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor1_id`)'
+            ' REFERENCES `tests_addanchor1` (`id`);'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` UNIQUE (`testmodel_id`, `addanchor1_id`);'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor1_id']),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor2_id` integer NOT NULL'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor2_id`)'
+            ' REFERENCES `custom_add_anchor_table` (`id`);'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` UNIQUE (`testmodel_id`, `addanchor2_id`);'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor2_id']),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `from_testmodel_id` integer NOT NULL,'
+            ' `to_testmodel_id` integer NOT NULL'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`from_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`to_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` UNIQUE'
+            ' (`from_testmodel_id`, `to_testmodel_id`);'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['from_testmodel_id', 'to_testmodel_id']),
+        ]),
+    })
+elif django.VERSION[:2] == (1, 8):
+    # Django 1.8+ no longer creates indexes for the ForeignKeys on the
+    # ManyToMany table.
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor1_id` integer NOT NULL,'
+            ' UNIQUE (`testmodel_id`, `addanchor1_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor1_id`)'
+            ' REFERENCES `tests_addanchor1` (`id`);'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor2_id` integer NOT NULL,'
+            ' UNIQUE (`testmodel_id`, `addanchor2_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor2_id`)'
+            ' REFERENCES `custom_add_anchor_table` (`id`);'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `from_testmodel_id` integer NOT NULL,'
+            ' `to_testmodel_id` integer NOT NULL,'
+            ' UNIQUE (`from_testmodel_id`, `to_testmodel_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`from_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`to_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+    })
+elif django.VERSION[:2] == (1, 7):
+    # Django 1.7 introduced more condensed CREATE TABLE statements, and
+    # indexes for fields on the model. (The indexes were removed for MySQL
+    # in subsequent releases.)
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor1_id` integer NOT NULL,'
+            ' UNIQUE (`testmodel_id`, `addanchor1_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor1_id`)'
+            ' REFERENCES `tests_addanchor1` (`id`);'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`testmodel_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`addanchor1_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `testmodel_id` integer NOT NULL,'
+            ' `addanchor2_id` integer NOT NULL,'
+            ' UNIQUE (`testmodel_id`, `addanchor2_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor2_id`)'
+            ' REFERENCES `custom_add_anchor_table` (`id`);'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`testmodel_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`addanchor2_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` '
+            '(`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,'
+            ' `from_testmodel_id` integer NOT NULL,'
+            ' `to_testmodel_id` integer NOT NULL,'
+            ' UNIQUE (`from_testmodel_id`, `to_testmodel_id`)'
+            ');',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`from_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`to_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`from_testmodel_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX `%s` ON'
+            ' `tests_testmodel_added_field` (`to_testmodel_id`);'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+else:
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `testmodel_id` integer NOT NULL,',
+            '    `addanchor1_id` integer NOT NULL,',
+            '    UNIQUE (`testmodel_id`, `addanchor1_id`)',
+            ')',
+            ';',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor1_id`)'
+            ' REFERENCES `tests_addanchor1` (`id`);'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `testmodel_id` integer NOT NULL,',
+            '    `addanchor2_id` integer NOT NULL,',
+            '    UNIQUE (`testmodel_id`, `addanchor2_id`)',
+            ')',
+            ';',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`addanchor2_id`)'
+            ' REFERENCES `custom_add_anchor_table` (`id`);'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `from_testmodel_id` integer NOT NULL,',
+            '    `to_testmodel_id` integer NOT NULL,',
+            '    UNIQUE (`from_testmodel_id`, `to_testmodel_id`)',
+            ')',
+            ';',
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`from_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE `tests_testmodel_added_field`'
+            ' ADD CONSTRAINT `%s` FOREIGN KEY (`to_testmodel_id`)'
+            ' REFERENCES `tests_testmodel` (`id`);'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+    })
 
-        'ALTER TABLE `tests_testmodel_added_field`'
-        ' ADD CONSTRAINT `%s` FOREIGN KEY (`to_testmodel_id`)'
-        ' REFERENCES `tests_testmodel` (`id`);'
-        % generate_constraint_name('to_testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
-}
 
 delete_field = {
     'DefaultNamedColumnModel': (
@@ -573,26 +854,13 @@
                                           ['int_field1', 'char_field1']),
     ]),
 
-    'replace_list': '\n'.join([
-        'DROP INDEX `int_field1` ON `tests_testmodel`;',
-
-        'CREATE UNIQUE INDEX %s'
-        ' ON tests_testmodel (`int_field2`, `char_field2`);'
-        % generate_unique_constraint_name('tests_testmodel',
-                                          ['int_field2', 'char_field2']),
-    ]),
-
     'append_list': '\n'.join([
         'CREATE UNIQUE INDEX %s'
         ' ON tests_testmodel (`int_field2`, `char_field2`);'
         % generate_unique_constraint_name('tests_testmodel',
                                           ['int_field2', 'char_field2']),
     ]),
 
-    'removing': '\n'.join([
-        'DROP INDEX `int_field1` ON `tests_testmodel`;',
-    ]),
-
     'set_remove': '\n'.join([
         'CREATE UNIQUE INDEX %s'
         ' ON tests_testmodel (`int_field1`, `char_field1`);'
@@ -615,6 +883,46 @@
     ),
 }
 
+if django.VERSION[:2] >= (1, 9):
+    # In Django >= 1.9, unique_together indexes are created specifically
+    # after table creation, using Django's generated constraint names.
+    unique_together.update({
+        'removing': (
+            'DROP INDEX `%s` ON `tests_testmodel`;'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field1', 'char_field1'])
+        ),
+
+        'replace_list': '\n'.join([
+            'DROP INDEX `%s` ON `tests_testmodel`;'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field1', 'char_field1']),
+
+            'CREATE UNIQUE INDEX %s'
+            ' ON tests_testmodel (`int_field2`, `char_field2`);'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field2', 'char_field2']),
+        ]),
+    })
+else:
+    # In Django < 1.9, unique_together indexes are created during table
+    # creation, using MySQL's default name scheme, instead of using a
+    # generated name, so we need to drop with those hard-coded names.
+    unique_together.update({
+        'removing': (
+            'DROP INDEX `int_field1` ON `tests_testmodel`;'
+        ),
+
+        'replace_list': '\n'.join([
+            'DROP INDEX `int_field1` ON `tests_testmodel`;',
+
+            'CREATE UNIQUE INDEX %s'
+            ' ON tests_testmodel (`int_field2`, `char_field2`);'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field2', 'char_field2']),
+        ]),
+    })
+
 index_together = {
     'setting_from_empty': '\n'.join([
         'CREATE INDEX `%s`'
@@ -627,7 +935,8 @@
     'replace_list': '\n'.join([
         'DROP INDEX `%s` ON `tests_testmodel`;'
         % generate_index_name('tests_testmodel',
-                              ['int_field1', 'char_field1']),
+                              ['int_field1', 'char_field1'],
+                              index_together=True),
 
         'CREATE INDEX `%s`'
         ' ON `tests_testmodel` (`int_field2`, `char_field2`);'
diff --git a/django_evolution/tests/db/postgresql.py b/django_evolution/tests/db/postgresql.py
index a95b644f4f4f1cec5f3c393573dde0aa7eb860e1..9bde85dc9817192a6f4a609044528884c49ae7af 100644
--- a/django_evolution/tests/db/postgresql.py
+++ b/django_evolution/tests/db/postgresql.py
@@ -1,3 +1,5 @@
+import django
+
 from django_evolution.tests.utils import (generate_unique_constraint_name,
                                           make_generate_constraint_name,
                                           make_generate_index_name)
@@ -124,86 +126,433 @@
         % generate_index_name('tests_testmodel', 'added_field_id',
                               'added_field'),
     ]),
-
-    'AddManyToManyDatabaseTableModel': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" serial NOT NULL PRIMARY KEY,',
-        '    "testmodel_id" integer NOT NULL,',
-        '    "addanchor1_id" integer NOT NULL,',
-        '    UNIQUE ("testmodel_id", "addanchor1_id")',
-        ')',
-        ';',
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor1_id")'
-        ' REFERENCES "tests_addanchor1" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('addanchor1_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_addanchor1'),
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
-        ' REFERENCES "tests_testmodel" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
-
-    'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" serial NOT NULL PRIMARY KEY,',
-        '    "testmodel_id" integer NOT NULL,',
-        '    "addanchor2_id" integer NOT NULL,',
-        '    UNIQUE ("testmodel_id", "addanchor2_id")',
-        ')',
-        ';',
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor2_id")'
-        ' REFERENCES "custom_add_anchor_table" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('addanchor2_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'custom_add_anchor_table'),
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
-        ' REFERENCES "tests_testmodel" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
-
-    'AddManyToManySelf': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" serial NOT NULL PRIMARY KEY,',
-        '    "from_testmodel_id" integer NOT NULL,',
-        '    "to_testmodel_id" integer NOT NULL,',
-        '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
-        ')',
-        ';',
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("from_testmodel_id")'
-        ' REFERENCES "tests_testmodel" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('from_testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-
-        'ALTER TABLE "tests_testmodel_added_field"'
-        ' ADD CONSTRAINT "%s" FOREIGN KEY ("to_testmodel_id")'
-        ' REFERENCES "tests_testmodel" ("id")'
-        ' DEFERRABLE INITIALLY DEFERRED;'
-        % generate_constraint_name('to_testmodel_id', 'id',
-                                   'tests_testmodel_added_field',
-                                   'tests_testmodel'),
-    ]),
 }
 
+if django.VERSION[:2] >= (1, 9):
+    # Django 1.9+ no longer includes a UNIQUE keyword in the table creation,
+    # instead creating these through constraints. It also brings back indexes.
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor1_id" integer NOT NULL'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor1_id")'
+            ' REFERENCES "tests_addanchor1" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" UNIQUE ("testmodel_id", "addanchor1_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor1_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor1_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor2_id" integer NOT NULL'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor2_id")'
+            ' REFERENCES "custom_add_anchor_table" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" UNIQUE ("testmodel_id", "addanchor2_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor2_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor2_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "from_testmodel_id" integer NOT NULL,'
+            ' "to_testmodel_id" integer NOT NULL'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("from_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("to_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" UNIQUE'
+            ' ("from_testmodel_id", "to_testmodel_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['from_testmodel_id', 'to_testmodel_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("from_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("to_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+elif django.VERSION[:2] == (1, 8):
+    # Django 1.8+ no longer creates indexes for the ForeignKeys on the
+    # ManyToMany table.
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor1_id" integer NOT NULL,'
+            ' UNIQUE ("testmodel_id", "addanchor1_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor1_id")'
+            ' REFERENCES "tests_addanchor1" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor1_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor2_id" integer NOT NULL,'
+            ' UNIQUE ("testmodel_id", "addanchor2_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor2_id")'
+            ' REFERENCES "custom_add_anchor_table" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor2_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "from_testmodel_id" integer NOT NULL,'
+            ' "to_testmodel_id" integer NOT NULL,'
+            ' UNIQUE ("from_testmodel_id", "to_testmodel_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("from_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("to_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("from_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("to_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+elif django.VERSION[:2] >= (1, 7):
+    # Django 1.7 introduced more condensed CREATE TABLE statements, and
+    # indexes for fields on the model. (The indexes were removed for Postgres
+    # in subsequent releases.)
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor1_id" integer NOT NULL,'
+            ' UNIQUE ("testmodel_id", "addanchor1_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor1_id")'
+            ' REFERENCES "tests_addanchor1" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor1_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "testmodel_id" integer NOT NULL,'
+            ' "addanchor2_id" integer NOT NULL,'
+            ' UNIQUE ("testmodel_id", "addanchor2_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor2_id")'
+            ' REFERENCES "custom_add_anchor_table" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor2_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" serial NOT NULL PRIMARY KEY,'
+            ' "from_testmodel_id" integer NOT NULL,'
+            ' "to_testmodel_id" integer NOT NULL,'
+            ' UNIQUE ("from_testmodel_id", "to_testmodel_id")'
+            ');',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("from_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("to_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("from_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("to_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+else:
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL,',
+            '    "addanchor1_id" integer NOT NULL,',
+            '    UNIQUE ("testmodel_id", "addanchor1_id")',
+            ')',
+            ';',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor1_id")'
+            ' REFERENCES "tests_addanchor1" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor1_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_addanchor1'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL,',
+            '    "addanchor2_id" integer NOT NULL,',
+            '    UNIQUE ("testmodel_id", "addanchor2_id")',
+            ')',
+            ';',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("addanchor2_id")'
+            ' REFERENCES "custom_add_anchor_table" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('addanchor2_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'custom_add_anchor_table'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "from_testmodel_id" integer NOT NULL,',
+            '    "to_testmodel_id" integer NOT NULL,',
+            '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
+            ')',
+            ';',
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("from_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('from_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+
+            'ALTER TABLE "tests_testmodel_added_field"'
+            ' ADD CONSTRAINT "%s" FOREIGN KEY ("to_testmodel_id")'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ' DEFERRABLE INITIALLY DEFERRED;'
+            % generate_constraint_name('to_testmodel_id', 'id',
+                                       'tests_testmodel_added_field',
+                                       'tests_testmodel'),
+        ]),
+    })
+
 delete_field = {
     'DefaultNamedColumnModel': (
         'ALTER TABLE "tests_testmodel" DROP COLUMN "int_field" CASCADE;'
@@ -577,28 +926,13 @@
                                           ['int_field1', 'char_field1'])
     ),
 
-    'replace_list': '\n'.join([
-        'ALTER TABLE "tests_testmodel"'
-        ' DROP CONSTRAINT tests_testmodel_int_field1_char_field1_key;',
-
-        'CREATE UNIQUE INDEX %s'
-        ' ON tests_testmodel ("int_field2", "char_field2");'
-        % generate_unique_constraint_name('tests_testmodel',
-                                          ['int_field2', 'char_field2']),
-    ]),
-
     'append_list': (
         'CREATE UNIQUE INDEX %s'
         ' ON tests_testmodel ("int_field2", "char_field2");'
         % generate_unique_constraint_name('tests_testmodel',
                                           ['int_field2', 'char_field2'])
     ),
 
-    'removing': (
-        'ALTER TABLE "tests_testmodel"'
-        ' DROP CONSTRAINT tests_testmodel_int_field1_char_field1_key;'
-    ),
-
     'set_remove': (
         'CREATE UNIQUE INDEX %s'
         ' ON tests_testmodel ("int_field1", "char_field1");'
@@ -621,6 +955,48 @@
     ),
 }
 
+if django.VERSION[:2] >= (1, 9):
+    # In Django >= 1.9, unique_together indexes are created specifically
+    # after table creation, using Django's generated constraint names.
+    unique_together.update({
+        'removing': (
+            'ALTER TABLE "tests_testmodel" DROP CONSTRAINT %s;'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field1', 'char_field1'])
+        ),
+
+        'replace_list': '\n'.join([
+            'ALTER TABLE "tests_testmodel" DROP CONSTRAINT %s;'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field1', 'char_field1']),
+
+            'CREATE UNIQUE INDEX %s'
+            ' ON tests_testmodel ("int_field2", "char_field2");'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field2', 'char_field2']),
+        ]),
+    })
+else:
+    # In Django < 1.9, unique_together indexes are created during table
+    # creation, using Postgres's default naming scheme, instead of using a
+    # generated name, so we need to drop with those hard-coded names.
+    unique_together.update({
+        'removing': (
+            'ALTER TABLE "tests_testmodel"'
+            ' DROP CONSTRAINT tests_testmodel_int_field1_char_field1_key;'
+        ),
+
+        'replace_list': '\n'.join([
+            'ALTER TABLE "tests_testmodel"'
+            ' DROP CONSTRAINT tests_testmodel_int_field1_char_field1_key;',
+
+            'CREATE UNIQUE INDEX %s'
+            ' ON tests_testmodel ("int_field2", "char_field2");'
+            % generate_unique_constraint_name('tests_testmodel',
+                                              ['int_field2', 'char_field2']),
+        ]),
+    })
+
 index_together = {
     'setting_from_empty': '\n'.join([
         'CREATE INDEX "%s"'
diff --git a/django_evolution/tests/db/sqlite3.py b/django_evolution/tests/db/sqlite3.py
index 66f1c0ec0c89b7b1584252c22457b2e472370306..5762ccf11da0620f6d76eeebcc1d13cab96ce988 100644
--- a/django_evolution/tests/db/sqlite3.py
+++ b/django_evolution/tests/db/sqlite3.py
@@ -1,10 +1,33 @@
+import django
+from django.db.backends.sqlite3.creation import DatabaseCreation
+from django.db.backends.sqlite3.base import DatabaseWrapper
+
 from django_evolution.tests.utils import (generate_unique_constraint_name,
                                           make_generate_index_name)
 
 
 generate_index_name = make_generate_index_name('sqlite3')
 
 
+try:
+    # Django >= 1.8
+    data_types_suffix = DatabaseWrapper.data_types_suffix
+except AttributeError:
+    try:
+        # Django == 1.7
+        data_types_suffix = DatabaseCreation.data_types_suffix
+    except AttributeError:
+        # Django < 1.7
+        data_types_suffix = {}
+
+
+def get_field_suffix(field_type):
+    try:
+        return ' %s' % data_types_suffix[field_type]
+    except KeyError:
+        return ''
+
+
 add_field = {
     'AddNonNullNonCallableColumnModel': '\n'.join([
         'CREATE TEMPORARY TABLE "TEMP_TABLE"'
@@ -464,36 +487,198 @@
         % generate_index_name('tests_testmodel', 'added_field_id',
                               'added_field'),
     ]),
-
-    'AddManyToManyDatabaseTableModel': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" integer NOT NULL PRIMARY KEY,',
-        '    "testmodel_id" integer NOT NULL,',
-        '    "addanchor1_id" integer NOT NULL,',
-        '    UNIQUE ("testmodel_id", "addanchor1_id")',
-        ')',
-        ';',
-    ]),
-    'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" integer NOT NULL PRIMARY KEY,',
-        '    "testmodel_id" integer NOT NULL,',
-        '    "addanchor2_id" integer NOT NULL,',
-        '    UNIQUE ("testmodel_id", "addanchor2_id")',
-        ')',
-        ';',
-    ]),
-    'AddManyToManySelf': '\n'.join([
-        'CREATE TABLE "tests_testmodel_added_field" (',
-        '    "id" integer NOT NULL PRIMARY KEY,',
-        '    "from_testmodel_id" integer NOT NULL,',
-        '    "to_testmodel_id" integer NOT NULL,',
-        '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
-        ')',
-        ';',
-    ]),
 }
 
+if django.VERSION[:2] >= (1, 9):
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "addanchor1_id" integer NOT NULL'
+            ' REFERENCES "tests_addanchor1" ("id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE UNIQUE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id", "addanchor1_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor1_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor1_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "addanchor2_id" integer NOT NULL'
+            ' REFERENCES "custom_add_anchor_table" ("id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE UNIQUE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id", "addanchor2_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['testmodel_id', 'addanchor2_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor2_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "from_testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "to_testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE UNIQUE INDEX "%s" ON "tests_testmodel_added_field"'
+            ' ("from_testmodel_id", "to_testmodel_id");'
+            % generate_unique_constraint_name(
+                'tests_testmodel_added_field',
+                ['from_testmodel_id', 'to_testmodel_id']),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("from_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("to_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+elif django.VERSION[:2] >= (1, 7):
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "addanchor1_id" integer NOT NULL'
+            ' REFERENCES "tests_addanchor1" ("id"),'
+            ' UNIQUE ("testmodel_id", "addanchor1_id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor1_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor1_id'),
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "addanchor2_id" integer NOT NULL'
+            ' REFERENCES "custom_add_anchor_table" ("id"),'
+            ' UNIQUE ("testmodel_id", "addanchor2_id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("addanchor2_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'addanchor2_id'),
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" '
+            '("id" integer NOT NULL PRIMARY KEY%s,'
+            ' "from_testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' "to_testmodel_id" integer NOT NULL'
+            ' REFERENCES "tests_testmodel" ("id"),'
+            ' UNIQUE ("from_testmodel_id", "to_testmodel_id")'
+            ');'
+            % get_field_suffix('AutoField'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("from_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'from_testmodel_id'),
+
+            'CREATE INDEX "%s" ON'
+            ' "tests_testmodel_added_field" ("to_testmodel_id");'
+            % generate_index_name('tests_testmodel_added_field',
+                                  'to_testmodel_id'),
+        ]),
+    })
+else:
+    add_field.update({
+        'AddManyToManyDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY%s,'
+            % get_field_suffix('AutoField'),
+
+            '    "testmodel_id" integer NOT NULL,',
+            '    "addanchor1_id" integer NOT NULL,',
+            '    UNIQUE ("testmodel_id", "addanchor1_id")',
+            ')',
+            ';',
+        ]),
+
+        'AddManyToManyNonDefaultDatabaseTableModel': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY%s,'
+            % get_field_suffix('AutoField'),
+
+            '    "testmodel_id" integer NOT NULL,',
+            '    "addanchor2_id" integer NOT NULL,',
+            '    UNIQUE ("testmodel_id", "addanchor2_id")',
+            ')',
+            ';',
+        ]),
+
+        'AddManyToManySelf': '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY%s,'
+            % get_field_suffix('AutoField'),
+
+            '    "from_testmodel_id" integer NOT NULL,',
+            '    "to_testmodel_id" integer NOT NULL,',
+            '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
+            ')',
+            ';',
+        ]),
+    })
+
 delete_field = {
     'DefaultNamedColumnModel': '\n'.join([
         'CREATE TEMPORARY TABLE "TEMP_TABLE"'
diff --git a/django_evolution/tests/test_add_field.py b/django_evolution/tests/test_add_field.py
index 9bd4bfad9aed575563360baf5ce8478167cda865..d6f5bca6218fc3d045163bca1080e33c1c492f62 100644
--- a/django_evolution/tests/test_add_field.py
+++ b/django_evolution/tests/test_add_field.py
@@ -32,7 +32,7 @@ class AddBaseModel(models.Model):
     int_field = models.IntegerField()
 
 
-class CustomTableModel(models.Model):
+class CustomAddTableModel(models.Model):
     value = models.IntegerField()
     alt_value = models.CharField(max_length=20)
 
@@ -336,7 +336,7 @@ class DestModel(models.Model):
             class Meta:
                 db_table = 'custom_table_name'
 
-        self.set_base_model(CustomTableModel, name='CustomTableModel')
+        self.set_base_model(CustomAddTableModel, name='CustomTableModel')
 
         self.perform_evolution_tests(
             DestModel,
diff --git a/django_evolution/tests/test_database_sig.py b/django_evolution/tests/test_database_sig.py
index 76a891a1ec17c377b9b01c98c858f7c800c248d6..e1ca1c928f88975c4cacfa3345745a7f38910252 100644
--- a/django_evolution/tests/test_database_sig.py
+++ b/django_evolution/tests/test_database_sig.py
@@ -1,7 +1,6 @@
 from django.test.testcases import TestCase
 
 from django_evolution.db import EvolutionOperationsMulti
-from django_evolution.models import Evolution
 from django_evolution.signature import create_database_sig
 
 
@@ -25,14 +24,11 @@ def test_initial_state(self):
         self.assertTrue('indexes' in self.database_sig['django_evolution'])
 
         # Check the Evolution model
-        index_name = self.evolver.get_default_index_name(
-            Evolution._meta.db_table, Evolution._meta.get_field('version'))
         indexes = self.database_sig['django_evolution']['indexes']
 
-        self.assertTrue(index_name in indexes)
-        self.assertEqual(
-            indexes[index_name],
+        self.assertIn(
             {
                 'unique': False,
                 'columns': ['version_id'],
-            })
+            },
+            indexes.values())
diff --git a/django_evolution/tests/test_delete_field.py b/django_evolution/tests/test_delete_field.py
index f5355e422f8da7ad200bf8c07018f58bbacad871..2f7bba6e26ce3b52197a5c39e46a28142056349e 100644
--- a/django_evolution/tests/test_delete_field.py
+++ b/django_evolution/tests/test_delete_field.py
@@ -32,7 +32,7 @@ class DeleteBaseModel(models.Model):
                                         db_table='non-default_m2m_table')
 
 
-class CustomTableModel(models.Model):
+class CustomDeleteTableModel(models.Model):
     value = models.IntegerField()
     alt_value = models.CharField(max_length=20)
 
@@ -207,7 +207,7 @@ class DestModel(models.Model):
             class Meta:
                 db_table = 'custom_table_name'
 
-        self.set_base_model(CustomTableModel, name='CustomTableModel')
+        self.set_base_model(CustomDeleteTableModel, name='CustomTableModel')
 
         self.perform_evolution_tests(
             DestModel,
diff --git a/django_evolution/tests/test_index_together.py b/django_evolution/tests/test_index_together.py
index 214a11b20d0257d5a5d95f36292ee4a553c1fb63..aa222dde007259ec08f035ffb1b220ec24405719 100644
--- a/django_evolution/tests/test_index_together.py
+++ b/django_evolution/tests/test_index_together.py
@@ -1,5 +1,5 @@
 from django.db import models
-from django.utils.unittest import SkipTest
+from nose import SkipTest
 
 from django_evolution.mutations import ChangeMeta
 from django_evolution.tests.base_test_case import EvolutionTestCase
diff --git a/django_evolution/tests/test_preprocessing.py b/django_evolution/tests/test_preprocessing.py
index eb0a1f93a01185f8aef7847bdb80d0b53ad2aef4..4f059633d7286909e2ca13aa3720704dfe8c7df3 100644
--- a/django_evolution/tests/test_preprocessing.py
+++ b/django_evolution/tests/test_preprocessing.py
@@ -6,7 +6,7 @@
 from django_evolution.tests.base_test_case import EvolutionTestCase
 
 
-class BaseModel(models.Model):
+class PreprocBaseModel(models.Model):
     my_id = models.AutoField(primary_key=True)
     char_field = models.CharField(max_length=20)
 
@@ -18,7 +18,7 @@ class ReffedPreprocModel(models.Model):
 class PreprocessingTests(EvolutionTestCase):
     """Testing pre-processing of mutations."""
     sql_mapping_key = 'preprocessing'
-    default_base_model = BaseModel
+    default_base_model = PreprocBaseModel
 
     def test_add_delete_field(self):
         """Testing pre-processing AddField + DeleteField"""
diff --git a/django_evolution/tests/utils.py b/django_evolution/tests/utils.py
index 1ca1a195dafd936b204af95d54576106ab32dd09..d95b44806b68239e4cd2de7b0e5b98de2f3de6b6 100644
--- a/django_evolution/tests/utils.py
+++ b/django_evolution/tests/utils.py
@@ -499,22 +499,40 @@ def generate_index_name(db_type, table, col_names, field_names=None,
 
     assert len(field_names) == len(col_names)
 
+    django_version = django.VERSION[:2]
+
     # Note that we're checking Django versions/engines specifically, since
     # we want to test that we're getting the right index names for the
     # right versions of Django, rather than asking Django for them.
-    if db_type == 'postgres' and not index_together:
+    #
+    # The order here matters.
+    if django_version >= (1, 7):
+        if len(col_names) == 1:
+            assert not index_together
+
+            # Django 1.7 went back to passing a single column name (and
+            # not a list as a single variable argument) when there's only
+            # one column.
+            name = digest(connection, col_names[0])
+        else:
+            assert index_together
+
+            index_unique_name = _generate_index_unique_name_hash(
+                connection, table, col_names)
+            name = '%s%s_idx' % (col_names[0], index_unique_name)
+    elif db_type == 'postgres' and not index_together:
         # Postgres computes the index names separately from the rest of
         # the engines. It just uses '<tablename>_<colname>", same as
         # Django < 1.2. We only do this for normal indexes, though, not
         # index_together.
         name = col_names[0]
-    elif django.VERSION >= (1, 5):
+    elif django_version >= (1, 5):
         # Django >= 1.5 computed the digest of the representation of a
         # list of either field names or column names. Note that digest()
         # takes variable positional arguments, which this is not passing.
         # This is due to a design bug in these versions.
         name = digest(connection, field_names or col_names)
-    elif django.VERSION >= (1, 2):
+    elif django_version >= (1, 2):
         # Django >= 1.2, < 1.7 used the digest of the name of the first
         # column. There was no index_together in these releases.
         name = digest(connection, col_names[0])
@@ -588,8 +606,27 @@ def generate_constraint_name(db_type, r_col, col, r_table, table):
         str:
         The expected name for a constraint.
     """
-    return '%s_refs_%s_%s' % (r_col, col,
-                              digest(connection, r_table, table))
+    if django.VERSION[:2] >= (1, 7):
+        # This is an approximation of what Django 1.7+ uses for constraint
+        # naming. It's actually the same as index naming, but for test
+        # purposes, we want to keep this distinct from the index naming above.
+        # It also doesn't cover all the cases that
+        # BaseDatabaseSchemaEditor._create_index_name covers, but they're not
+        # necessary for our tests (and we'll know if it all blows up somehow).
+        max_length = connection.ops.max_name_length() or 200
+        index_unique_name = _generate_index_unique_name_hash(
+            connection, r_table, [r_col])
+
+        name = '_%s%s_fk_%s_%s' % (r_col, index_unique_name, table, col)
+        full_name = '%s%s' % (r_table, name)
+
+        if len(full_name) > max_length:
+            full_name = '%s%s' % (r_table[:(max_length - len(name))], name)
+
+        return full_name
+    else:
+        return '%s_refs_%s_%s' % (r_col, col,
+                                  digest(connection, r_table, table))
 
 
 def make_generate_constraint_name(db_type):
@@ -628,7 +665,52 @@ def generate_unique_constraint_name(table, col_names):
     Returns:
         The expected constraint name for this version of Django.
     """
-    name = digest(connection, col_names)
+    if django.VERSION[:2] >= (1, 7):
+        max_length = connection.ops.max_name_length() or 200
+        index_unique_name = _generate_index_unique_name_hash(
+            connection, table, col_names)
+        name = '_%s%s_uniq' % (col_names[0], index_unique_name)
+        full_name = '%s%s' % (table, name)
 
-    return truncate_name('%s_%s' % (table, name),
-                         connection.ops.max_name_length())
+        if len(full_name) > max_length:
+            full_name = '%s%s' % (table[:(max_length - len(name))], name)
+
+        return full_name
+    else:
+        name = digest(connection, col_names)
+
+        return truncate_name('%s_%s' % (table, name),
+                             connection.ops.max_name_length())
+
+
+def _generate_index_unique_name_hash(connection, table, col_names):
+    """Return the hash for the unique part of an index name.
+
+    This is used on Django 1.7+ to generate a unique hash as part of an
+    index name.
+
+    Args:
+        connection (object):
+            The database connection.
+
+        table (str):
+            The name of the table.
+
+        col_names (list of str):
+            The list of column names for the index.
+
+    Returns:
+        str:
+        A hash for the unique part of an index.
+    """
+    assert isinstance(col_names, list)
+
+    if django.VERSION[:2] >= (1, 9):
+        # Django >= 1.9
+        #
+        # Django 1.9 introduced a new format for the unique index hashes,
+        # switching back to using digest() instead of hash().
+        return '_%s' % digest(connection, table, *col_names)
+    else:
+        # Django >= 1.7, < 1.9
+        return '_%x' % abs(hash((table, ','.join(col_names))))
diff --git a/tests/runtests.py b/tests/runtests.py
index 971eaf357dbae99dc97bcd4cd0e042d64d1bc78d..99014ee4eba694d6ca6ef7902d2d8e322500c08e 100644
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -3,6 +3,8 @@
 import os
 import sys
 
+import django
+
 
 def run_tests(verbosity=1, interactive=False):
     from django.conf import settings
@@ -11,6 +13,10 @@ def run_tests(verbosity=1, interactive=False):
     from django.test.utils import setup_test_environment, \
                                   teardown_test_environment
 
+    if hasattr(django, 'setup'):
+        # Django >= 1.7
+        django.setup()
+
     setup_test_environment()
     settings.DEBUG = False
 
@@ -23,8 +29,12 @@ def run_tests(verbosity=1, interactive=False):
         connection.creation.create_test_db(verbosity,
                                            autoclobber=not interactive)
 
-    management.call_command('syncdb', verbosity=verbosity,
-                            interactive=interactive)
+    if django.VERSION[:2] >= (1, 7):
+        management.call_command('migrate', verbosity=verbosity,
+                                interactive=interactive)
+    else:
+        management.call_command('syncdb', verbosity=verbosity,
+                                interactive=interactive)
 
     nose_argv = ['runtests.py', '-v',
                  '--with-coverage',
