Index: django_evolution/__init__.py
===================================================================
--- django_evolution/__init__.py	(revision 183)
+++ django_evolution/__init__.py	(working copy)
@@ -60,3 +60,12 @@
 
 class EvolutionNotImplementedError(EvolutionException, NotImplementedError):
     pass
+
+try:
+    from django.db import connections
+    __is_multi_db = True
+except:
+    __is_multi_db = False
+
+def is_multi_db():
+    return __is_multi_db
\ No newline at end of file
Index: django_evolution/db/__init__.py
===================================================================
--- django_evolution/db/__init__.py	(revision 183)
+++ django_evolution/db/__init__.py	(working copy)
@@ -1,9 +1,21 @@
 # Establish the common EvolutionOperations instance, called evolver.
 
 from django.conf import settings
+from django.db import connection
 
-module_name = ['django_evolution.db',settings.DATABASE_ENGINE]
-module = __import__('.'.join(module_name),{},{},[''])
+class EvolutionOperationsMulti():
+    def __init__(self, db_name):
+        try:
+            from django.db import connections, router
+            engine = settings.DATABASES[db_name]['ENGINE'].split('.')[-1]
+            connection = connections[db_name]
+            module_name = ['django_evolution.db', engine]
+            module = __import__('.'.join(module_name), {}, {}, [''])
+            self.evolver = module.EvolutionOperations(connection)
+        except:
+            module_name = ['django_evolution.db',settings.DATABASE_ENGINE]
+            module = __import__('.'.join(module_name),{},{},[''])
+            self.evolver = module.EvolutionOperations()
+    def get_evolver(self):
+        return self.evolver
 
-evolver = module.EvolutionOperations()
-
Index: django_evolution/db/common.py
===================================================================
--- django_evolution/db/common.py	(revision 183)
+++ django_evolution/db/common.py	(working copy)
@@ -1,11 +1,16 @@
 import django
 from django.core.management import color
-from django.db import connection
+from django.db import connection as default_connection
 from django.db.backends.util import truncate_name
 import copy
 
 
 class BaseEvolutionOperations(object):
+    connection = None
+
+    def __init__(self, connection = default_connection):
+        self.connection = connection
+        
     def quote_sql_param(self, param):
         "Add protective quoting around an SQL string parameter"
         if isinstance(param, basestring):
@@ -19,9 +24,9 @@
             return []
 
         style = color.no_style()
-        qn = connection.ops.quote_name
-        max_name_length = connection.ops.max_name_length()
-        creation = connection.creation
+        qn = self.connection.ops.quote_name
+        max_name_length = self.connection.ops.max_name_length()
+        creation = self.connection.creation
 
         sql = []
         refs = {}
@@ -61,18 +66,18 @@
         return sql
 
     def delete_column(self, model, f):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         params = (qn(model._meta.db_table), qn(f.column))
 
         return ['ALTER TABLE %s DROP COLUMN %s CASCADE;' % params]
 
     def delete_table(self, table_name):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         return ['DROP TABLE %s;' % qn(table_name)]
 
     def add_m2m_table(self, model, f):
         style = color.no_style()
-        creation = connection.creation
+        creation = self.connection.creation
 
         if f.rel.through:
             references = {}
@@ -93,7 +98,7 @@
         return sql
 
     def add_column(self, model, f, initial):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
 
         if f.rel:
             # it is a foreign key field
@@ -106,7 +111,7 @@
             if f.unique or f.primary_key:
                 constraints.append('UNIQUE')
             params = (qn(model._meta.db_table), qn(f.column), f.db_type(), ' '.join(constraints),
-                qn(related_table), qn(related_pk_col), connection.ops.deferrable_sql())
+                qn(related_table), qn(related_pk_col), self.connection.ops.deferrable_sql())
             output = ['ALTER TABLE %s ADD COLUMN %s %s %s REFERENCES %s (%s) %s;' % params]
         else:
             null_constraints = '%sNULL' % (not f.null and 'NOT ' or '')
@@ -137,7 +142,7 @@
         return output
 
     def set_field_null(self, model, f, null):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         params = (qn(model._meta.db_table), qn(f.column),)
         if null:
            return 'ALTER TABLE %s ALTER COLUMN %s DROP NOT NULL;' % params
@@ -148,25 +153,25 @@
         "Returns the CREATE INDEX SQL statements."
         style = color.no_style()
 
-        return connection.creation.sql_indexes_for_field(model, f, style)
+        return self.connection.creation.sql_indexes_for_field(model, f, style)
 
     def drop_index(self, model, f):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         index_name = self.get_index_name(model, f)
-        max_length = connection.ops.max_name_length()
+        max_length = self.connection.ops.max_name_length()
 
         return ['DROP INDEX %s;' % qn(truncate_name(index_name, max_length))]
 
     def get_index_name(self, model, f):
         if django.VERSION >= (1, 2):
-            colname = connection.creation._digest(f.column)
+            colname = self.connection.creation._digest(f.column)
         else:
             colname = f.column
 
         return '%s_%s' % (model._meta.db_table, colname)
 
     def change_null(self, model, field_name, new_null_attr, initial=None):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         opts = model._meta
         f = opts.get_field(field_name)
         output = []
@@ -189,7 +194,7 @@
         return output
 
     def change_max_length(self, model, field_name, new_max_length, initial=None):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         opts = model._meta
         f = opts.get_field(field_name)
         f.max_length = new_max_length
@@ -215,11 +220,11 @@
             return self.drop_index(model, f)
 
     def change_unique(self, model, field_name, new_unique_value, initial=None):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         opts = model._meta
         f = opts.get_field(field_name)
         constraint_name = truncate_name('%s_%s_key' % (opts.db_table, f.column),
-                                        connection.ops.max_name_length())
+                                        self.connection.ops.max_name_length())
 
         if new_unique_value:
             params = (qn(opts.db_table), constraint_name, qn(f.column),)
Index: django_evolution/db/mysql.py
===================================================================
--- django_evolution/db/mysql.py	(revision 183)
+++ django_evolution/db/mysql.py	(working copy)
@@ -1,5 +1,4 @@
 from django.core.management import color
-from django.db import connection
 
 from common import BaseEvolutionOperations
 
@@ -9,7 +8,7 @@
             # No Operation
             return []
 
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         style = color.no_style()
 
         ###
@@ -27,22 +26,22 @@
             field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
         if f.unique:
             field_output.append(style.SQL_KEYWORD('UNIQUE'))
-        if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys:
+        if tablespace and self.connection.features.supports_tablespaces and (f.unique or f.primary_key) and self.connection.features.autoindexes_primary_keys:
             # We must specify the index tablespace inline, because we
             # won't be generating a CREATE INDEX statement for this field.
-            field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
+            field_output.append(self.connection.ops.tablespace_sql(tablespace, inline=True))
         if f.rel:
             field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \
                 style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \
                 style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' +
-                connection.ops.deferrable_sql()
+                self.connection.ops.deferrable_sql()
             )
 
         params = (qn(opts.db_table), qn(old_field.column), ' '.join(field_output))
         return ['ALTER TABLE %s CHANGE COLUMN %s %s;' % params]
 
     def set_field_null(self, model, f, null):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         params = (qn(model._meta.db_table), qn(f.column), f.db_type())
         if null:
             return 'ALTER TABLE %s MODIFY COLUMN %s %s DEFAULT NULL;' % params
@@ -50,7 +49,7 @@
             return 'ALTER TABLE %s MODIFY COLUMN %s %s NOT NULL;' % params
 
     def change_max_length(self, model, field_name, new_max_length, initial=None):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         opts = model._meta
         f = opts.get_field(field_name)
         f.max_length = new_max_length
@@ -64,12 +63,12 @@
                 'ALTER TABLE %(table)s MODIFY COLUMN %(column)s %(type)s;' % params]
 
     def drop_index(self, model, f):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         params = (qn(self.get_index_name(model, f)), qn(model._meta.db_table))
         return ['DROP INDEX %s ON %s;' % params]
 
     def change_unique(self, model, field_name, new_unique_value, initial=None):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         opts = model._meta
         f = opts.get_field(field_name)
         constraint_name = '%s' % (f.column,)
@@ -84,6 +83,6 @@
         if old_db_tablename == db_tablename:
             return []
 
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         params = (qn(old_db_tablename), qn(db_tablename))
         return ['RENAME TABLE %s TO %s;' % params]
Index: django_evolution/db/postgresql.py
===================================================================
--- django_evolution/db/postgresql.py	(revision 183)
+++ django_evolution/db/postgresql.py	(working copy)
@@ -1,5 +1,4 @@
 from django.core.management import color
-from django.db import connection
 from django.db.backends.util import truncate_name
 
 from common import BaseEvolutionOperations
@@ -12,9 +11,9 @@
             return []
 
         style = color.no_style()
-        qn = connection.ops.quote_name
-        max_name_length = connection.ops.max_name_length()
-        creation = connection.creation
+        qn = self.connection.ops.quote_name
+        max_name_length = self.connection.ops.max_name_length()
+        creation = self.connection.creation
         sql = []
         refs = {}
         models = []
Index: django_evolution/db/sqlite3.py
===================================================================
--- django_evolution/db/sqlite3.py	(revision 183)
+++ django_evolution/db/sqlite3.py	(working copy)
@@ -1,5 +1,5 @@
 from django.core.management import color
-from django.db import connection, models
+from django.db import models
 
 from common import BaseEvolutionOperations
 
@@ -24,7 +24,7 @@
         return output
 
     def copy_to_temp_table(self, source_table_name, field_list):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         columns = []
         for field in field_list:
             if not models.ManyToManyField == field.__class__:
@@ -33,7 +33,7 @@
         return ['INSERT INTO %s SELECT %s FROM %s;' % (qn(TEMP_TABLE_NAME), column_names, qn(source_table_name))]
 
     def copy_from_temp_table(self, dest_table_name, field_list):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         columns = []
         for field in field_list:
             if not models.ManyToManyField == field.__class__:
@@ -48,7 +48,7 @@
         return ['INSERT INTO %(dest_table_name)s (%(column_names)s) SELECT %(column_names)s FROM %(temp_table)s;' % params]
 
     def insert_to_temp_table(self, field, initial):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
 
         # At this point, initial can only be None if null=True, otherwise it is
         # a user callable or the default AddFieldInitialCallback which will shortly raise an exception.
@@ -85,10 +85,10 @@
                 self._meta = FakeMeta(table_name, field_list)
 
         style = color.no_style()
-        return connection.creation.sql_indexes_for_model(FakeModel(table_name, field_list), style)
+        return self.connection.creation.sql_indexes_for_model(FakeModel(table_name, field_list), style)
 
     def create_table(self, table_name, field_list, temporary=False, create_index=True):
-        qn = connection.ops.quote_name
+        qn = self.connection.ops.quote_name
         output = []
 
         create = ['CREATE']
Index: django_evolution/evolve.py
===================================================================
--- django_evolution/evolve.py	(revision 183)
+++ django_evolution/evolve.py	(working copy)
@@ -1,6 +1,6 @@
 import os
 
-from django_evolution import EvolutionException
+from django_evolution import EvolutionException, is_multi_db
 from django_evolution.models import Evolution
 from django_evolution.mutations import SQLMutation
 
@@ -13,14 +13,18 @@
     except:
         return []
 
-def get_unapplied_evolutions(app):
+def get_unapplied_evolutions(app, database):
     "Obtain the list of unapplied evolutions for an application"
     sequence = get_evolution_sequence(app)
     app_label = app.__name__.split('.')[-2]
-    applied = [evo.label for evo in Evolution.objects.filter(app_label=app_label)]
+    if is_multi_db():
+        evolutions = Evolution.objects.using(database).filter(app_label=app_label)
+    else:
+        evolutions = Evolution.objects.filter(app_label=app_label)
+    applied = [evo.label for evo in evolutions]
     return [seq for seq in sequence if seq not in applied]
 
-def get_mutations(app, evolution_labels):
+def get_mutations(app, evolution_labels, database):
     """
     Obtain the list of mutations described by the named evolutions.
     """
@@ -36,12 +40,20 @@
     for label in evolution_labels:
         directory_name = os.path.dirname(evolution_module.__file__)
         sql_file_name = os.path.join(directory_name, label+'.sql')
+        sql_file_name_database = os.path.join(directory_name, "%s_%s.sql" % (database, label))
+        #keep this test for compatibility purpose
         if os.path.exists(sql_file_name):
             sql = []
             sql_file = open(sql_file_name)
             for line in sql_file:
                 sql.append(line)
             mutations.append(SQLMutation(label, sql))
+        elif os.path.exists(sql_file_name_database):
+            sql = []
+            sql_file = open(sql_file_name_database)
+            for line in sql_file:
+                sql.append(line)
+            mutations.append(SQLMutation(label, sql))
         else:
             try:
                 module_name = [evolution_module.__name__,label]
Index: django_evolution/management/__init__.py
===================================================================
--- django_evolution/management/__init__.py	(revision 183)
+++ django_evolution/management/__init__.py	(working copy)
@@ -6,7 +6,7 @@
 from django.core.management.color import color_style
 from django.db.models import signals, get_apps
 
-from django_evolution import models as django_evolution
+from django_evolution import is_multi_db, models as django_evolution
 from django_evolution.evolve import get_evolution_sequence, get_unapplied_evolutions
 from django_evolution.signature import create_project_sig
 from django_evolution.diff import Diff
@@ -18,18 +18,30 @@
     A hook into syncdb's post_syncdb signal, that is used to notify the user
     if a model evolution is necessary.
     """
-    proj_sig = create_project_sig()
+    default_db = None
+    if is_multi_db():
+        from django.db.utils import DEFAULT_DB_ALIAS
+        default_db = DEFAULT_DB_ALIAS
+
+    db = kwargs.get('db', default_db)
+    proj_sig = create_project_sig(db)
     signature = pickle.dumps(proj_sig)
 
     try:
-        latest_version = django_evolution.Version.objects.latest('when')
+        if is_multi_db():
+            latest_version = django_evolution.Version.objects.using(db).latest('when')
+        else:
+            latest_version = django_evolution.Version.objects.latest('when')
     except django_evolution.Version.DoesNotExist:
         # We need to create a baseline version.
         if verbosity > 0:
             print "Installing baseline version"
 
         latest_version = django_evolution.Version(signature=signature)
-        latest_version.save()
+        if is_multi_db():
+            latest_version.save(using=db)
+        else:
+            latest_version.save()
 
         for a in get_apps():
             app_label = a.__name__.split('.')[-2]
@@ -41,9 +53,12 @@
                 evolution = django_evolution.Evolution(app_label=app_label,
                                                        label=evo_label,
                                                        version=latest_version)
-                evolution.save()
+                if is_multi_db():
+                    evolution.save(using=db)
+                else:
+                    evolution.save()
 
-    unapplied = get_unapplied_evolutions(app)
+    unapplied = get_unapplied_evolutions(app, db)
     if unapplied:
         print style.NOTICE('There are unapplied evolutions for %s.' % app.__name__.split('.')[-2])
 
@@ -76,7 +91,10 @@
             if verbosity > 0:
                 print "Adding baseline version for new models"
             latest_version = django_evolution.Version(signature=pickle.dumps(old_proj_sig))
-            latest_version.save()
+            if is_multi_db():
+                latest_version.save(using=db)
+            else:
+                latest_version.save()
 
         # TODO: Model introspection step goes here.
         # # If the current database state doesn't match the last
Index: django_evolution/management/commands/evolve.py
===================================================================
--- django_evolution/management/commands/evolve.py	(revision 183)
+++ django_evolution/management/commands/evolve.py	(working copy)
@@ -11,7 +11,7 @@
 from django.db.models import get_apps, get_app
 from django.db import connection, transaction
 
-from django_evolution import CannotSimulate, EvolutionException
+from django_evolution import CannotSimulate, EvolutionException, is_multi_db
 from django_evolution.diff import Diff
 from django_evolution.evolve import get_unapplied_evolutions, get_mutations
 from django_evolution.models import Version, Evolution
@@ -19,7 +19,6 @@
 from django_evolution.signature import create_project_sig
 from django_evolution.utils import write_sql, execute_sql
 
-
 class Command(BaseCommand):
     option_list = BaseCommand.option_list + (
         make_option('--noinput', action='store_false', dest='interactive', default=True,
@@ -32,6 +31,8 @@
             help='Compile a Django evolution script into SQL.'),
         make_option('-x', '--execute', action='store_true', dest='execute', default=False,
             help='Apply the evolution to the database.'),
+        make_option('--database', action='store', dest='database',
+            help='Nominates a database to synchronize.'),
     )
     if '--verbosity' not in [opt.get_opt_string() for opt in BaseCommand.option_list]:
         option_list += make_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
@@ -53,6 +54,7 @@
         compile_sql = options['compile_sql']
         hint = options['hint']
         purge = options['purge']
+        database = options['database']
 
         # Use the list of all apps, unless app labels are specified.
         if app_labels:
@@ -71,11 +73,11 @@
         sql = []
         new_evolutions = []
 
-        current_proj_sig = create_project_sig()
+        current_proj_sig = create_project_sig(database)
         current_signature = pickle.dumps(current_proj_sig)
 
         try:
-            latest_version = Version.objects.latest('when')
+            latest_version = Version.objects.using(database).latest('when')
             database_sig = pickle.loads(str(latest_version.signature))
             diff = Diff(database_sig, current_proj_sig)
         except Evolution.DoesNotExist:
@@ -87,30 +89,37 @@
                 if hint:
                     evolutions = []
                     hinted_evolution = diff.evolution()
-                    mutations = hinted_evolution.get(app_label, [])
+                    temp_mutations = hinted_evolution.get(app_label, [])
                 else:
-                    evolutions = get_unapplied_evolutions(app)
-                    mutations = get_mutations(app, evolutions)
+                    evolutions = get_unapplied_evolutions(app, database)
+                    temp_mutations = get_mutations(app, evolutions, database)
 
+                mutations = []
+                if temp_mutations:
+                    for mutation in temp_mutations:
+                        # test the mutation against the current database
+                        if mutation.is_mutable(app_label, database_sig, database):
+                            mutations.append(mutation)
+                    
                 if mutations:
                     app_sql = ['-- Evolve application %s' % app_label]
                     evolution_required = True
                     for mutation in mutations:
                         # Only compile SQL if we want to show it
-                        if compile_sql or execute:
-                            app_sql.extend(mutation.mutate(app_label, database_sig))
+                       if compile_sql or execute:
+                           app_sql.extend(mutation.mutate(app_label, database_sig, database))
 
-                        # Now run the simulation, which will modify the signatures
-                        try:
-                            mutation.simulate(app_label, database_sig)
-                        except CannotSimulate:
-                            simulated = False
+                           # Now run the simulation, which will modify the signatures
+                           try:
+                               mutation.simulate(app_label, database_sig, database)
+                           except CannotSimulate:
+                               simulated = False
                     new_evolutions.extend(Evolution(app_label=app_label, label=label)
                                             for label in evolutions)
 
                     if not execute:
                         if compile_sql:
-                            write_sql(app_sql)
+                            write_sql(app_sql, database)
                         else:
                             print '#----- Evolution for %s' % app_label
                             print 'from django_evolution.mutations import *'
@@ -134,14 +143,15 @@
                     delete_app = DeleteApplication()
                     purge_sql = []
                     for app_label in diff.deleted:
-                        if compile_sql or execute:
-                            purge_sql.append('-- Purge application %s' % app_label)
-                            purge_sql.extend(delete_app.mutate(app_label, database_sig))
-                        delete_app.simulate(app_label, database_sig)
+                        if delete_app.is_mutable(app_label, database_sig, database):
+                            if compile_sql or execute:
+                                purge_sql.append('-- Purge application %s' % app_label)
+                                purge_sql.extend(delete_app.mutate(app_label, database_sig, database))
+                            delete_app.simulate(app_label, database_sig, database)
 
                     if not execute:
                         if compile_sql:
-                            write_sql(purge_sql)
+                            write_sql(purge_sql, database)
                         else:
                             print 'The following application(s) can be purged:'
                             for app_label in diff.deleted:
@@ -168,7 +178,7 @@
                 print
                 print 'The following are the changes that could not be resolved:'
                 print diff
-                raise CommandError('Your models contain changes that Django Evolution cannot resolve automatically.')
+                sys.exit(1)
         else:
             print self.style.NOTICE('Evolution could not be simulated, possibly due to raw SQL mutations')
 
@@ -185,31 +195,48 @@
 
 Are you sure you want to execute the evolutions?
 
-Type 'yes' to continue, or 'no' to cancel: """ % settings.DATABASE_NAME)
+Type 'yes' to continue, or 'no' to cancel: """ % database)
                 else:
                     confirm = 'yes'
-
+                if is_multi_db():
+                    from django.db import connections
                 if confirm.lower() == 'yes':
                     # Begin Transaction
-                    transaction.enter_transaction_management()
-                    transaction.managed(True)
-                    cursor = connection.cursor()
+                    if is_multi_db():
+                        transaction.enter_transaction_management(using=database)
+                        transaction.managed(flag=True, using=database)
+                        cursor = connections[database].cursor()
+                    else:
+                        transaction.enter_transaction_management()
+                        transaction.managed(flag=True)
+                        cursor = connection.cursor()
                     try:
                         # Perform the SQL
                         execute_sql(cursor, sql)
 
                         # Now update the evolution table
                         version = Version(signature=current_signature)
-                        version.save()
+                        if is_multi_db():
+                            version.save(using=database)
+                        else:
+                            version.save()
                         for evolution in new_evolutions:
                             evolution.version = version
-                            evolution.save()
-
-                        transaction.commit()
+                            if is_multi_db():
+                                evolution.save(using=database)
+                            else:
+                                evolution.save()
+                        if is_multi_db():
+                            transaction.commit(using=database)
+                        else:
+                            transaction.commit()
                     except Exception, ex:
-                        transaction.rollback()
+                        if is_multi_db():
+                            transaction.rollback(using=database)
+                        else:
+                            transaction.rollback()
                         raise CommandError('Error applying evolution: %s' % str(ex))
-                    transaction.leave_transaction_management()
+                    transaction.leave_transaction_management(using=database)
 
                     if verbosity > 0:
                         print 'Evolution successful.'
Index: django_evolution/mutations.py
===================================================================
--- django_evolution/mutations.py	(revision 183)
+++ django_evolution/mutations.py	(working copy)
@@ -7,11 +7,14 @@
 from django.utils.functional import curry
 
 from django_evolution.signature import ATTRIBUTE_DEFAULTS
-from django_evolution import CannotSimulate, SimulationFailure, EvolutionNotImplementedError
-from django_evolution.db import evolver
+from django_evolution import CannotSimulate, SimulationFailure, EvolutionNotImplementedError, is_multi_db
+from django_evolution.db import EvolutionOperationsMulti
 
 FK_INTEGER_TYPES = ['AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField']
 
+if is_multi_db():
+    from django.db import router
+
 def create_field(proj_sig, field_name, field_type, field_attrs, parent_model):
     """
     Create an instance of a field from a field signature. This is useful for
@@ -215,14 +218,14 @@
     def __init__(self):
         pass
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, target_database = None):
         """
         Performs the mutation on the database. Database changes will occur
         after this function is invoked.
         """
         raise NotImplementedError()
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, target_database = None):
         """
         Performs a simulation of the mutation to be performed. The purpose of
         the simulate function is to ensure that after all mutations have occured
@@ -231,6 +234,35 @@
         """
         raise NotImplementedError()
 
+    def is_mutable(self, app_label, proj_sig, database):
+        """
+        test if the current mutation could be applied to the given database
+        """
+        return False
+
+class MonoBaseMutation(BaseMutation):
+    # introducting model_name at this stage will prevent subclasses to be
+    # cross databases
+    def __init__(self, model_name = None):
+        BaseMutation.__init__(self)
+        self.model_name = model_name
+
+    def evolver(self, model):
+        db_name = None
+        if is_multi_db():
+            db_name = router.db_for_write(model)
+        return EvolutionOperationsMulti(db_name).get_evolver()
+
+    def is_mutable(self, app_label, proj_sig, database):
+        if is_multi_db():
+            app_sig = proj_sig[app_label]
+            model_sig = app_sig[self.model_name]
+            model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+            db_name = router.db_for_write(model)
+            return db_name and db_name == database
+        else:
+            return True
+
 class SQLMutation(BaseMutation):
     def __init__(self, tag, sql, update_func=None):
         self.tag = tag
@@ -240,27 +272,29 @@
     def __str__(self):
         return "SQLMutation('%s')" % self.tag
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         "SQL mutations cannot be simulated unless an update function is provided"
         if callable(self.update_func):
             self.update_func(app_label, proj_sig)
         else:
             raise CannotSimulate('Cannot simulate SQLMutations')
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         "The mutation of an SQL mutation returns the raw SQL"
         return self.sql
+    
+    def is_mutable(self, app_label, proj_sig, database):
+        return True
 
-class DeleteField(BaseMutation):
+class DeleteField(MonoBaseMutation):
     def __init__(self, model_name, field_name):
-
-        self.model_name = model_name
+        MonoBaseMutation.__init__(self, model_name)
         self.field_name = field_name
 
     def __str__(self):
         return "DeleteField('%s', '%s')" % (self.model_name, self.field_name)
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
 
@@ -287,7 +321,7 @@
         except KeyError:
             raise SimulationFailure('Cannot find the field named "%s".' % self.field_name)
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
         field_sig = model_sig['fields'][self.field_name]
@@ -301,15 +335,15 @@
         field_sig['field_type'] = field_type
 
         if field_type == models.ManyToManyField:
-            sql_statements = evolver.delete_table(field._get_m2m_db_table(model._meta))
+            sql_statements = self.evolver(model).delete_table(field._get_m2m_db_table(model._meta))
         else:
-            sql_statements = evolver.delete_column(model, field)
+            sql_statements = self.evolver(model).delete_column(model, field)
 
         return sql_statements
 
-class AddField(BaseMutation):
+class AddField(MonoBaseMutation):
     def __init__(self, model_name, field_name, field_type, initial=None, **kwargs):
-        self.model_name = model_name
+        MonoBaseMutation.__init__(self, model_name)
         self.field_name = field_name
         self.field_type = field_type
         self.field_attrs = kwargs
@@ -326,7 +360,7 @@
             str_output.append("%s=%s" % (key,repr(value)))
         return 'AddField(' + ', '.join(str_output) + ')'
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
 
@@ -344,7 +378,7 @@
         }
         model_sig['fields'][self.field_name].update(self.field_attrs)
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         if self.field_type == models.ManyToManyField:
             return self.add_m2m_table(app_label, proj_sig)
         else:
@@ -358,10 +392,10 @@
         field = create_field(proj_sig, self.field_name, self.field_type,
                              self.field_attrs, model)
 
-        sql_statements = evolver.add_column(model, field, self.initial)
+        sql_statements = self.evolver(model).add_column(model, field, self.initial)
 
         # Create SQL index if necessary
-        sql_statements.extend(evolver.create_index(model, field))
+        sql_statements.extend(self.evolver(model).create_index(model, field))
 
         return sql_statements
 
@@ -390,14 +424,14 @@
             field.m2m_reverse_name = curry(field._get_m2m_reverse_attr,
                                            related, 'column')
 
-        sql_statements = evolver.add_m2m_table(model, field)
+        sql_statements = self.evolver(model).add_m2m_table(model, field)
 
         return sql_statements
 
-class RenameField(BaseMutation):
+class RenameField(MonoBaseMutation):
     def __init__(self, model_name, old_field_name, new_field_name,
                  db_column=None, db_table=None):
-        self.model_name = model_name
+        MonoBaseMutation.__init__(self, model_name)
         self.old_field_name = old_field_name
         self.new_field_name = new_field_name
         self.db_column = db_column
@@ -413,7 +447,7 @@
 
         return "RenameField(%s)" % params
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
         field_dict = model_sig['fields']
@@ -434,7 +468,7 @@
 
         field_dict[self.new_field_name] = field_dict.pop(self.old_field_name)
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
         old_field_sig = model_sig['fields'][self.old_field_name]
@@ -467,13 +501,13 @@
             old_m2m_table = old_field._get_m2m_db_table(model._meta)
             new_m2m_table = new_field._get_m2m_db_table(model._meta)
 
-            return evolver.rename_table(model, old_m2m_table, new_m2m_table)
+            return self.evolver(model).rename_table(model, old_m2m_table, new_m2m_table)
         else:
-            return evolver.rename_column(model._meta, old_field, new_field)
+            return self.evolver(model).rename_column(model._meta, old_field, new_field)
 
-class ChangeField(BaseMutation):
+class ChangeField(MonoBaseMutation):
     def __init__(self, model_name, field_name, initial=None, **kwargs):
-        self.model_name = model_name
+        MonoBaseMutation.__init__(self, model_name)
         self.field_name = field_name
         self.field_attrs = kwargs
         self.initial = initial
@@ -494,7 +528,7 @@
         return 'ChangeField(' + ', '.join(str_output) + ')'
 
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
         field_sig = model_sig['fields'][self.field_name]
@@ -509,7 +543,7 @@
                     raise SimulationFailure("Cannot change column '%s' on '%s.%s' without a non-null initial value." % (
                             self.field_name, app_label, self.model_name))
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
         old_field_sig = model_sig['fields'][self.field_name]
@@ -521,7 +555,7 @@
             # Avoid useless SQL commands if nothing has changed.
             if not old_field_attr == attr_value:
                 try:
-                    evolver_func = getattr(evolver, 'change_%s' % field_attr)
+                    evolver_func = getattr(self.evolver(model), 'change_%s' % field_attr)
                     if field_attr == 'null':
                         sql_statements.extend(evolver_func(model, self.field_name, attr_value, self.initial))
                     elif field_attr == 'db_table':
@@ -533,19 +567,19 @@
 
         return sql_statements
 
-class DeleteModel(BaseMutation):
+class DeleteModel(MonoBaseMutation):
     def __init__(self, model_name):
-        self.model_name = model_name
+        MonoBaseMutation.__init__(self, model_name)
 
     def __str__(self):
         return "DeleteModel(%r)" % self.model_name
 
-    def simulate(self, app_label, proj_sig):
+    def simulate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         # Simulate the deletion of the model.
         del app_sig[self.model_name]
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         app_sig = proj_sig[app_label]
         model_sig = app_sig[self.model_name]
 
@@ -556,9 +590,9 @@
             if field_sig['field_type'] == models.ManyToManyField:
                 field = model._meta.get_field(field_name)
                 m2m_table = field._get_m2m_db_table(model._meta)
-                sql_statements += evolver.delete_table(m2m_table)
+                sql_statements += self.evolver(model).delete_table(m2m_table)
         # Remove the table itself.
-        sql_statements += evolver.delete_table(model._meta.db_table)
+        sql_statements += self.evolver(model).delete_table(model._meta.db_table)
 
         return sql_statements
 
@@ -566,12 +600,27 @@
     def __str__(self):
         return 'DeleteApplication()'
 
-    def simulate(self, app_label, proj_sig):
-        del proj_sig[app_label]
+    def simulate(self, app_label, proj_sig, database = None):
+        if database:
+            app_sig = proj_sig[app_label]
+            # Simulate the deletion of the models.
+            for model_name in app_sig.keys():
+                mutation = DeleteModel(model_name)
+                if mutation.is_mutable(app_label, proj_sig, database):
+                    del app_sig[self.model_name]
 
-    def mutate(self, app_label, proj_sig):
+    def mutate(self, app_label, proj_sig, database = None):
         sql_statements = []
-        app_sig = proj_sig[app_label]
-        for model_name in app_sig.keys():
-            sql_statements.extend(DeleteModel(model_name).mutate(app_label, proj_sig))
+        # this test will introduce a regression, but we can't afford to remove
+        # all models at a same time if they aren't owned by the same database
+        if database:
+            app_sig = proj_sig[app_label]
+            for model_name in app_sig.keys():
+                mutation = DeleteModel(model_name)
+                if mutation.is_mutable(app_label, proj_sig, database):
+                    sql_statements.extend(mutation.mutate(app_label, proj_sig))
         return sql_statements
+
+    def is_mutable(self, app_label, proj_sig, database):
+        # the test is done in the mutate method above. We can return True
+        return True
\ No newline at end of file
Index: django_evolution/signature.py
===================================================================
--- django_evolution/signature.py	(revision 183)
+++ django_evolution/signature.py	(working copy)
@@ -3,8 +3,12 @@
 from django.conf import global_settings
 from django.contrib.contenttypes import generic
 from django.utils.datastructures import SortedDict
+from django_evolution import is_multi_db
 
+if is_multi_db():
+    from django.db import router
 
+
 ATTRIBUTE_DEFAULTS = {
     # Common to all fields
     'primary_key': False,
@@ -73,7 +77,7 @@
             model_sig['fields'][field.name] = create_field_sig(field)
     return model_sig
 
-def create_app_sig(app):
+def create_app_sig(app, database):
     """
     Creates a dictionary representation of the models in a given app.
     Only those attributes that are interesting from a schema-evolution
@@ -81,10 +85,12 @@
     """
     app_sig = SortedDict()
     for model in get_models(app):
-        app_sig[model._meta.object_name] = create_model_sig(model)
+        # only include those who want to be syncdb
+        if not is_multi_db() or router.allow_syncdb(database, model):
+            app_sig[model._meta.object_name] = create_model_sig(model)
     return app_sig
 
-def create_project_sig():
+def create_project_sig(database):
     """
     Create a dictionary representation of the apps in a given project.
     """
@@ -92,5 +98,5 @@
         '__version__': 1,
     }
     for app in get_apps():
-        proj_sig[app.__name__.split('.')[-2]] = create_app_sig(app)
+        proj_sig[app.__name__.split('.')[-2]] = create_app_sig(app, database)
     return proj_sig
Index: django_evolution/tests/__init__.py
===================================================================
--- django_evolution/tests/__init__.py	(revision 183)
+++ django_evolution/tests/__init__.py	(working copy)
@@ -9,6 +9,7 @@
 from ordering import tests as ordering_tests
 from generics import tests as generics_tests
 from inheritance import tests as inheritance_tests
+from django_evolution import is_multi_db
 # Define doctests
 __test__ = {
     'signature': signature_tests,
@@ -23,3 +24,7 @@
     'generics': generics_tests,
     'inheritance': inheritance_tests
 }
+
+if is_multi_db():
+    from multi_db import tests as multi_db_tests
+    __test__['multi_db'] = multi_db_tests
Index: django_evolution/tests/db/sqlite3.py
===================================================================
--- django_evolution/tests/db/sqlite3.py	(revision 183)
+++ django_evolution/tests/db/sqlite3.py	(working copy)
@@ -428,6 +428,152 @@
         ]),
 }
 
+multi_db = {
+    "SetNotNullChangeModelWithConstant":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NOT NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "char_field1" = \'abc\\\'s xyz\';',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NOT NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "SetNotNullChangeModelWithCallable":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NOT NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "char_field1" = "char_field";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NOT NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "SetNullChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "NoOpChangeModel": '',
+    "IncreasingMaxLengthChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(45) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(45) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "DecreasingMaxLengthChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(1) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(1) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "DBColumnChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "customised_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "customised_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'CREATE INDEX "%s" ON "app_multi_testmodel" ("int_field1");'
+            % generate_index_name('app_multi_testmodel', 'int_field1'),
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "customised_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "customised_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "M2MDBTableChangeModel": 'ALTER TABLE "multi_db_non-default_m2m_table" RENAME TO "custom_m2m_db_table_name";',
+    "AddDBIndexChangeModel": 'CREATE INDEX "%s" ON "app_multi_testmodel" ("int_field2");'
+        % generate_index_name('app_multi_testmodel', 'int_field2'),
+    "RemoveDBIndexChangeModel": 'DROP INDEX "%s";'
+        % generate_index_name('app_multi_testmodel', 'int_field1'),
+    "AddUniqueChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL UNIQUE, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL UNIQUE, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "RemoveUniqueChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "MultiAttrChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column2" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column2" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'CREATE INDEX "%s" ON "app_multi_testmodel" ("int_field1");'
+            % generate_index_name('app_multi_testmodel', 'int_field1'),
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column2", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column2", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column2" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(35) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column2", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column2" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(35) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column2", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column2", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "MultiAttrSingleFieldChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(35) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(35) NOT NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(35) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(35) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    "RedundantAttrsChangeModel":
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column3" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column3" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'CREATE INDEX "%s" ON "app_multi_testmodel" ("int_field1");'
+            % generate_index_name('app_multi_testmodel', 'int_field1'),
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column3", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column3", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field4" integer NOT NULL, "custom_db_column3" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(35) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field4", "custom_db_column3", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "app_multi_testmodel";',
+            'DROP TABLE "app_multi_testmodel";',
+            'CREATE TABLE "app_multi_testmodel"("int_field4" integer NOT NULL, "custom_db_column3" integer NOT NULL, "int_field1" integer NOT NULL, "int_field2" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "alt_pk" integer NOT NULL, "char_field" varchar(35) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field1" varchar(25) NULL, "char_field2" varchar(30) NULL);',
+            'INSERT INTO "app_multi_testmodel" ("int_field4", "custom_db_column3", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2") SELECT "int_field4", "custom_db_column3", "int_field1", "int_field2", "int_field3", "alt_pk", "char_field", "my_id", "char_field1", "char_field2" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+}
+
 delete_model = {
     'BasicModel':
         'DROP TABLE "tests_basicmodel";',
@@ -454,6 +600,7 @@
             'DROP TABLE "tests_testmodel";',
             'DROP TABLE "app_delete_custom_table_name";',
         ]),
+    'DeleteApplicationWithoutDatabase': "",
 }
 
 rename_field = {
Index: django_evolution/tests/delete_app.py
===================================================================
--- django_evolution/tests/delete_app.py	(revision 183)
+++ django_evolution/tests/delete_app.py	(working copy)
@@ -68,6 +68,20 @@
 >>> for sql_list in test_sql:
 ...     for sql in sql_list:
 ...         print sql
+%(DeleteApplicationWithoutDatabase)s
+
+>>> test_sql = []
+>>> delete_app = DeleteApplication()
+>>> for app_label in d.deleted.keys():
+...     test_sql.append(delete_app.mutate(app_label, test_sig, 'default'))
+...     delete_app.simulate(app_label, test_sig)
+
+>>> Diff(test_sig, deleted_app_sig).is_empty(ignore_apps=True)
+True
+
+>>> for sql_list in test_sql:
+...     for sql in sql_list:
+...         print sql
 %(DeleteApplication)s
 
 # Clean up after the applications that were installed
Index: django_evolution/tests/multi_db.py
===================================================================
--- django_evolution/tests/multi_db.py	(revision 0)
+++ django_evolution/tests/multi_db.py	(revision 0)
@@ -0,0 +1,687 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+
+>>> from django_evolution.mutations import ChangeField
+>>> from django_evolution.tests.utils import test_proj_sig_multi, execute_test_sql, register_models_multi, deregister_models
+>>> from django_evolution.diff import Diff
+
+>>> import copy
+
+# Use Cases:
+# Setting a null constraint
+# -- without an initial value
+# -- with a null initial value
+# -- with a good initial value (constant)
+# -- with a good initial value (callable)
+# Removing a null constraint
+# Invoking a no-op change field
+# Changing the max_length of a character field
+# -- increasing the max_length
+# -- decreasing the max_length
+# Renaming a column
+# Changing the db_table of a many to many relationship
+# Adding an index
+# Removing an index
+# Adding a unique constraint
+# Removing a unique constraint
+# Redundant attributes. (Some attribute have changed, while others haven't but are specified anyway.)
+# Changing more than one attribute at a time (on different fields)
+# Changing more than one attribute at a time (on one field)
+
+
+### This one is a bit dubious because changing the primary key of a model will mean
+### that all referenced foreign keys and M2M relationships need to be updated
+# Adding a primary key constraint
+# Removing a Primary Key (Changing the primary key column)
+
+
+
+# Options that apply to all fields:
+# DB related options
+# null
+# db_column
+# db_index
+# db_tablespace (Ignored)
+# primary_key
+# unique
+# db_table (only for many to many relationships)
+# -- CharField
+# max_length
+
+# Non-DB options
+# blank
+# core
+# default
+# editable
+# help_text
+# radio_admin
+# unique_for_date
+# unique_for_month
+# unique_for_year
+# validator_list
+
+# I don't know yet
+# choices
+
+>>> class ChangeSequenceFieldInitial(object):
+...     def __init__(self, suffix):
+...         self.suffix = suffix
+...
+...     def __call__(self):
+...         from django.db import connections
+...         qn = connections['db_multi'].ops.quote_name
+...         return qn('char_field')
+
+# Now, a useful test model we can use for evaluating diffs
+>>> class ChangeAnchor1(models.Model):
+...     value = models.IntegerField()
+
+>>> class ChangeBaseModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+# Store the base signatures
+>>> anchors = [('ChangeAnchor1', ChangeAnchor1)]
+>>> test_model = ('TestModel', ChangeBaseModel)
+
+>>> start = register_models_multi('app_multi', 'db_multi', *anchors)
+>>> start.update(register_models_multi('app_multi', 'db_multi', test_model))
+>>> start_sig = test_proj_sig_multi('app_multi', test_model, *anchors)
+
+# Setting a null constraint without an initial value
+>>> class SetNotNullChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=False)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', SetNotNullChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', SetNotNullChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field1':
+        Property 'null' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # SetNotNullChangeModel
+["ChangeField('TestModel', 'char_field1', initial=<<USER VALUE REQUIRED>>, null=False)"]
+
+# Without an initial value
+>>> evolution = [ChangeField('TestModel', 'char_field1', null=False)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot change column 'char_field1' on 'app_multi.TestModel' without a non-null initial value.
+
+# With a null initial value
+>>> evolution = [ChangeField('TestModel', 'char_field1', null=False, initial=None)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot change column 'char_field1' on 'app_multi.TestModel' without a non-null initial value.
+
+# With a good initial value (constant)
+>>> evolution = [ChangeField('TestModel', 'char_field1', null=False, initial="abc's xyz")]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # SetNotNullChangedModelWithConstant
+%(SetNotNullChangeModelWithConstant)s
+
+# With a good initial value (callable)
+>>> evolution = [ChangeField('TestModel', 'char_field1', null=False, initial=ChangeSequenceFieldInitial('SetNotNullChangeModel'))]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi')
+%(SetNotNullChangeModelWithCallable)s
+
+# Removing a null constraint
+>>> class SetNullChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=True)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', SetNullChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', SetNullChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field2':
+        Property 'null' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # SetNullChangeModel
+["ChangeField('TestModel', 'char_field2', initial=None, null=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # SetNullChangeModel
+%(SetNullChangeModel)s
+
+# Removing a null constraint
+>>> class NoOpChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', NoOpChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', NoOpChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+<BLANKLINE>
+
+>>> evolution = [ChangeField('TestModel', 'char_field1', null=True)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # NoOpChangeModel
+%(NoOpChangeModel)s
+
+# Increasing the max_length of a character field
+>>> class IncreasingMaxLengthChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=45)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', IncreasingMaxLengthChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', IncreasingMaxLengthChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field':
+        Property 'max_length' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # IncreasingMaxLengthChangeModel
+["ChangeField('TestModel', 'char_field', initial=None, max_length=45)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # IncreasingMaxLengthChangeModel
+%(IncreasingMaxLengthChangeModel)s
+
+# Decreasing the max_length of a character field
+>>> class DecreasingMaxLengthChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=1)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', DecreasingMaxLengthChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', DecreasingMaxLengthChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field':
+        Property 'max_length' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # DecreasingMaxLengthChangeModel
+["ChangeField('TestModel', 'char_field', initial=None, max_length=1)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # DecreasingMaxLengthChangeModel
+%(DecreasingMaxLengthChangeModel)s
+
+# Renaming a column
+>>> class DBColumnChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='customised_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', DBColumnChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', DBColumnChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'int_field':
+        Property 'db_column' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # DBColumnChangeModel
+["ChangeField('TestModel', 'int_field', initial=None, db_column='customised_db_column')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # DBColumnChangeModel
+%(DBColumnChangeModel)s
+
+# Changing the db_table of a many to many relationship
+>>> class M2MDBTableChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='custom_m2m_db_table_name')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', M2MDBTableChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', M2MDBTableChangeModel), *anchors)
+
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'm2m_field1':
+        Property 'db_table' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # M2MDBTableChangeModel
+["ChangeField('TestModel', 'm2m_field1', initial=None, db_table='custom_m2m_db_table_name')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # M2MDBTableChangeModel
+%(M2MDBTableChangeModel)s
+
+# Adding an index
+>>> class AddDBIndexChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=True)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', AddDBIndexChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', AddDBIndexChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'int_field2':
+        Property 'db_index' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # AddDBIndexChangeModel
+["ChangeField('TestModel', 'int_field2', initial=None, db_index=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # AddDBIndexChangeModel
+%(AddDBIndexChangeModel)s
+
+# Removing an index
+>>> class RemoveDBIndexChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=False)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', RemoveDBIndexChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', RemoveDBIndexChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'int_field1':
+        Property 'db_index' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # RemoveDBIndexChangeModel
+["ChangeField('TestModel', 'int_field1', initial=None, db_index=False)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # RemoveDBIndexChangeModel
+%(RemoveDBIndexChangeModel)s
+
+# Adding a unique constraint
+>>> class AddUniqueChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=True)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', AddUniqueChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', AddUniqueChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'int_field4':
+        Property 'unique' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # AddUniqueChangeModel
+["ChangeField('TestModel', 'int_field4', initial=None, unique=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # AddUniqueChangeModel
+%(AddUniqueChangeModel)s
+
+# Remove a unique constraint
+>>> class RemoveUniqueChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=False)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', RemoveUniqueChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', RemoveUniqueChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'int_field3':
+        Property 'unique' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # RemoveUniqueChangeModel
+["ChangeField('TestModel', 'int_field3', initial=None, unique=False)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # RemoveUniqueChangeModel
+%(RemoveUniqueChangeModel)s
+
+# Changing more than one attribute at a time (on different fields)
+>>> class MultiAttrChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column2')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=35)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=True)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', MultiAttrChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', MultiAttrChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field2':
+        Property 'null' has changed
+    In field 'int_field':
+        Property 'db_column' has changed
+    In field 'char_field':
+        Property 'max_length' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # MultiAttrChangeModel
+["ChangeField('TestModel', 'char_field2', initial=None, null=True)", "ChangeField('TestModel', 'int_field', initial=None, db_column='custom_db_column2')", "ChangeField('TestModel', 'char_field', initial=None, max_length=35)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # MultiAttrChangeModel
+%(MultiAttrChangeModel)s
+
+# Changing more than one attribute at a time (on one fields)
+>>> class MultiAttrSingleFieldChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=35, null=True)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', MultiAttrSingleFieldChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', MultiAttrSingleFieldChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model app_multi.TestModel:
+    In field 'char_field2':
+        Property 'max_length' has changed
+        Property 'null' has changed
+
+>>> print [str(e) for e in d.evolution()['app_multi']] # MultiAttrSingleFieldChangeModel
+["ChangeField('TestModel', 'char_field2', initial=None, max_length=35, null=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['app_multi']:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # MultiAttrSingleFieldChangeModel
+%(MultiAttrSingleFieldChangeModel)s
+
+# Redundant attributes. (Some attribute have changed, while others haven't but are specified anyway.)
+>>> class RedundantAttrsChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column3')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = models.IntegerField(unique=False)
+...     char_field = models.CharField(max_length=35)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=True)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', RedundantAttrsChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', RedundantAttrsChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> evolutions = [
+...     ChangeField("TestModel", "char_field2", initial=None, null=True, max_length=30),
+...     ChangeField("TestModel", "int_field", initial=None, db_column="custom_db_column3", primary_key=False, unique=False, db_index=False),
+...     ChangeField("TestModel", "char_field", initial=None, max_length=35),
+... ]
+
+>>> for mutation in evolutions:
+...     test_sql.extend(mutation.mutate('app_multi', test_sig))
+...     mutation.simulate('app_multi', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, database='db_multi', app_label='app_multi') # RedundantAttrsChangeModel
+%(RedundantAttrsChangeModel)s
+
+# Change field type to another type with same internal_type
+>>> class MyIntegerField(models.IntegerField):
+...     def get_internal_type(self):
+...         return 'IntegerField'
+
+>>> class MinorFieldTypeChangeModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     alt_pk = models.IntegerField()
+...     int_field = models.IntegerField(db_column='custom_db_column')
+...     int_field1 = models.IntegerField(db_index=True)
+...     int_field2 = models.IntegerField(db_index=False)
+...     int_field3 = models.IntegerField(unique=True)
+...     int_field4 = MyIntegerField(unique=False)
+...     char_field = models.CharField(max_length=20)
+...     char_field1 = models.CharField(max_length=25, null=True)
+...     char_field2 = models.CharField(max_length=30, null=False)
+...     m2m_field1 = models.ManyToManyField(ChangeAnchor1, db_table='multi_db_non-default_m2m_table')
+
+>>> end = register_models_multi('app_multi', 'db_multi', ('TestModel', MinorFieldTypeChangeModel), *anchors)
+>>> end_sig = test_proj_sig_multi('app_multi', ('TestModel', MinorFieldTypeChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+
+>>> d.is_empty()
+True
+
+# Clean up after the applications that were installed
+>>> deregister_models('app_multi')
+
+""" % test_sql_mapping('multi_db', db_name='db_multi')
Index: django_evolution/tests/utils.py
===================================================================
--- django_evolution/tests/utils.py	(revision 183)
+++ django_evolution/tests/utils.py	(working copy)
@@ -1,5 +1,3 @@
-import copy
-
 from datetime import datetime
 from django.core.management import sql
 from django.core.management.color import no_style
@@ -8,12 +6,16 @@
 from django.db.models.loading import cache
 from django.utils.datastructures import SortedDict
 from django.utils.functional import curry
-
-from django_evolution import signature
+from django_evolution import signature, is_multi_db
 from django_evolution.tests import models as evo_test
 from django_evolution.utils import write_sql, execute_sql
+import copy
 
+if is_multi_db():
+    from django.db import connections
+    from django.db.utils import DEFAULT_DB_ALIAS
 
+
 DEFAULT_TEST_ATTRIBUTE_VALUES = {
     models.CharField: 'TestCharField',
     models.IntegerField: '123',
@@ -23,28 +25,35 @@
 }
 
 
-def wrap_sql_func(func, evo_test, style):
-    try:
-        from django.db import connections
-
-        # Django >= 1.2
-        return func(evo_test, style, connections['default'])
-    except ImportError:
-        # Django < 1.2
+def wrap_sql_func(func, evo_test, style, db_name=None):
+    my_connection = connection
+    if is_multi_db():
+        if not db_name:
+            db_name = DEFAULT_DB_ALIAS
+        my_connection = connections[db_name]
+    if is_multi_db():
+        return func(evo_test, style, my_connection)
+    else:
         return func(evo_test, style)
 
-
 # Wrap the sql.* functions to work with the multi-db support
 sql_create = curry(wrap_sql_func, sql.sql_create)
 sql_indexes = curry(wrap_sql_func, sql.sql_indexes)
 sql_delete = curry(wrap_sql_func, sql.sql_delete)
 
 
-def register_models(*models):
+
+def _register_models(app_label='tests', db_name='default', *models):
     app_cache = SortedDict()
 
-    max_name_length = connection.ops.max_name_length()
+    my_connection = connection
+    if is_multi_db():
+        if not db_name:
+            db_name = DEFAULT_DB_ALIAS
+        my_connection = connections[db_name]
 
+    max_name_length = my_connection.ops.max_name_length()
+
     for name, model in reversed(models):
         if model._meta.module_name in cache.app_models['django_evolution']:
             del cache.app_models['django_evolution'][model._meta.module_name]
@@ -58,15 +67,15 @@
                 max_name_length)
 
             if orig_db_table.startswith(generated_db_table):
-                model._meta.db_table = 'tests_%s' % name.lower()
+                model._meta.db_table = '%s_%s' % (app_label, name.lower())
 
             model._meta.db_table = truncate_name(model._meta.db_table,
                                                  max_name_length)
-            model._meta.app_label = 'tests'
+            model._meta.app_label = app_label
             model._meta.object_name = name
             model._meta.module_name = name.lower()
 
-            add_app_test_model(model)
+            add_app_test_model(model, app_label=app_label)
 
             for field in model._meta.local_many_to_many:
                 if not field.rel.through:
@@ -79,7 +88,7 @@
                     max_name_length)
 
                 if through._meta.db_table == generated_db_table:
-                    through._meta.app_label = 'tests'
+                    through._meta.app_label = app_label
 
                     # Transform the 'through' table information only
                     # if we've transformed the parent db_table.
@@ -113,52 +122,81 @@
                                 model._meta.module_name)
 
                 app_cache[through._meta.module_name] = through
-                add_app_test_model(through)
+                add_app_test_model(through, app_label=app_label)
 
         app_cache[model._meta.module_name] = model
 
     return app_cache
 
-def test_proj_sig(*models, **kwargs):
+def register_models(*models):
+    return _register_models('tests', 'default', *models)
+
+def register_models_multi(app_label, db_name, *models):
+    return _register_models(app_label, db_name, *models)
+
+
+def _test_proj_sig(app_label, *models, **kwargs):
     "Generate a dummy project signature based around a single model"
-    version = kwargs.get('version',1)
+    version = kwargs.get('version', 1)
     proj_sig = {
-        'tests': SortedDict(),
+        app_label: SortedDict(),
         '__version__': version,
     }
 
     # Compute the project siguature
-    for full_name,model in models:
+    for full_name, model in models:
         parts = full_name.split('.')
         if len(parts) == 1:
             name = parts[0]
-            app = 'tests'
+            app = app_label
         else:
-            app,name = parts
+            app, name = parts
         proj_sig.setdefault(app, SortedDict())[name] = signature.create_model_sig(model)
 
     return proj_sig
 
-def execute_transaction(sql, output=False):
+def test_proj_sig(*models, **kwargs):
+    return _test_proj_sig('tests', *models, **kwargs)
+
+def test_proj_sig_multi(app_label, *models, **kwargs):
+    return _test_proj_sig(app_label, *models, **kwargs)
+
+def execute_transaction(sql, output=False, database='default'):
     "A transaction wrapper for executing a list of SQL statements"
+    my_connection = connection
+    if is_multi_db():
+        if not database:
+            database = DEFAULT_DB_ALIAS
+        my_connection = connections[database]
     try:
         # Begin Transaction
-        transaction.enter_transaction_management()
-        transaction.managed(True)
-        cursor = connection.cursor()
+        if is_multi_db():
+            transaction.enter_transaction_management(using=database)
+            transaction.managed(True, using=database)
+        else:
+            transaction.enter_transaction_management()
+            transaction.managed(True)
+        cursor = my_connection.cursor()
 
         # Perform the SQL
         if output:
-            write_sql(sql)
+            write_sql(sql, database)
         execute_sql(cursor, sql)
 
-        transaction.commit()
-        transaction.leave_transaction_management()
+        if is_multi_db():
+            transaction.commit(using=database)
+            transaction.leave_transaction_management(using=database)
+        else:
+            transaction.commit()
+            transaction.leave_transaction_management()
     except Exception:
-        transaction.rollback()
+        if is_multi_db():
+            transaction.rollback(using=database)
+        else:
+            transaction.rollback()
         raise
 
-def execute_test_sql(start, end, sql, debug=False):
+def execute_test_sql(start, end, sql, debug=False, app_label='tests', database='default'):
     """
     Execute a test SQL sequence. This method also creates and destroys the
     database tables required by the models registered against the test application.
@@ -176,29 +214,29 @@
     SQL.
     """
     # Set up the initial state of the app cache
-    set_app_test_models(copy.deepcopy(start))
+    set_app_test_models(copy.deepcopy(start), app_label=app_label)
 
     # Install the initial tables and indicies
     style = no_style()
-    execute_transaction(sql_create(evo_test, style), output=debug)
-    execute_transaction(sql_indexes(evo_test, style), output=debug)
+    execute_transaction(sql_create(evo_test, style, database), output=debug, database=database)
+    execute_transaction(sql_indexes(evo_test, style, database), output=debug, database=database)
     create_test_data(models.get_models(evo_test))
 
     # Set the app cache to the end state
-    set_app_test_models(copy.deepcopy(end))
+    set_app_test_models(copy.deepcopy(end), app_label=app_label)
 
     try:
         # Execute the test sql
         if debug:
-            write_sql(sql)
+            write_sql(sql, database)
         else:
-            execute_transaction(sql, output=True)
+            execute_transaction(sql, output=True, database=database)
     finally:
         # Cleanup the apps.
         if debug:
-            print sql_delete(evo_test, style)
+            print sql_delete(evo_test, style, database)
         else:
-            execute_transaction(sql_delete(evo_test, style), output=debug)
+            execute_transaction(sql_delete(evo_test, style, database), output=debug, database=database)
 
 def create_test_data(app_models):
     deferred_models = []
@@ -251,14 +289,15 @@
                     getattr(model, field.name).add(related_instance)
             model.save()
 
-def test_sql_mapping(test_field_name):
-    sql_for_engine = __import__('django_evolution.tests.db.%s' % (settings.DATABASE_ENGINE), {}, {}, [''])
+def test_sql_mapping(test_field_name, db_name='default'):
+    engine = settings.DATABASES[db_name]['ENGINE'].split('.')[-1]
+    sql_for_engine = __import__('django_evolution.tests.db.%s' % (engine), {}, {}, [''])
     return getattr(sql_for_engine, test_field_name)
 
 
-def deregister_models():
+def deregister_models(app_label='tests'):
     "Clear the test section of the app cache"
-    del cache.app_models['tests']
+    del cache.app_models[app_label]
     clear_models_cache()
 
 
@@ -274,14 +313,14 @@
         cache._get_models_cache.clear()
 
 
-def set_app_test_models(models):
+def set_app_test_models(models, app_label='tests'):
     """Sets the list of models in the Django test models registry."""
-    cache.app_models['tests'] = models
+    cache.app_models[app_label] = models
     clear_models_cache()
 
 
-def add_app_test_model(model):
+def add_app_test_model(model, app_label='tests'):
     """Adds a model to the Django test models registry."""
     key = model._meta.object_name.lower()
-    cache.app_models.setdefault('tests', SortedDict())[key] = model
+    cache.app_models.setdefault(app_label, SortedDict())[key] = model
     clear_models_cache()
Index: django_evolution/utils.py
===================================================================
--- django_evolution/utils.py	(revision 183)
+++ django_evolution/utils.py	(working copy)
@@ -1,10 +1,10 @@
-from django_evolution.db import evolver
+from django_evolution.db import EvolutionOperationsMulti
 
-def write_sql(sql):
+def write_sql(sql, database):
     "Output a list of SQL statements, unrolling parameters as required"
     for statement in sql:
         if isinstance(statement, tuple):
-            print unicode(statement[0] % tuple(evolver.quote_sql_param(s) for s in statement[1]))
+            print unicode(statement[0] % tuple(EvolutionOperationsMulti(database).get_evolver().quote_sql_param(s) for s in statement[1]))
         else:
             print unicode(statement)
 
