[snowy] Add in reversion and django_evolution ftw



commit 1da67e2e7138a132262b6575c123f4f0e3fe86ad
Author: Brad Taylor <brad getcoded net>
Date:   Wed May 13 17:27:44 2009 -0400

    Add in reversion and django_evolution ftw
---
 lib/django_evolution/__init__.py                   |   15 +
 lib/django_evolution/admin.py                      |    5 +
 lib/django_evolution/db/__init__.py                |    9 +
 lib/django_evolution/db/common.py                  |  166 +++++
 lib/django_evolution/db/mysql.py                   |   89 +++
 lib/django_evolution/db/mysql_old.py               |    2 +
 lib/django_evolution/db/postgresql.py              |   14 +
 lib/django_evolution/db/postgresql_psycopg2.py     |    2 +
 lib/django_evolution/db/sqlite3.py                 |  200 ++++++
 lib/django_evolution/diff.py                       |  205 ++++++
 lib/django_evolution/evolve.py                     |   60 ++
 lib/django_evolution/management/__init__.py        |  100 +++
 .../management/commands}/__init__.py               |    0
 lib/django_evolution/management/commands/evolve.py |  225 +++++++
 lib/django_evolution/models.py                     |   27 +
 lib/django_evolution/mutations.py                  |  475 ++++++++++++++
 lib/django_evolution/signature.py                  |   94 +++
 lib/django_evolution/tests/__init__.py             |   27 +
 lib/django_evolution/tests/add_field.py            |  551 ++++++++++++++++
 lib/django_evolution/tests/change_field.py         |  687 ++++++++++++++++++++
 .../django_evolution/tests/db}/__init__.py         |    0
 lib/django_evolution/tests/db/mysql.py             |  254 ++++++++
 lib/django_evolution/tests/db/mysql_old.py         |    2 +
 lib/django_evolution/tests/db/postgresql.py        |  236 +++++++
 .../tests/db/postgresql_psycopg2.py                |    2 +
 lib/django_evolution/tests/db/sqlite3.py           |  540 +++++++++++++++
 lib/django_evolution/tests/delete_app.py           |   76 +++
 lib/django_evolution/tests/delete_field.py         |  268 ++++++++
 lib/django_evolution/tests/delete_model.py         |  131 ++++
 lib/django_evolution/tests/generics.py             |   71 ++
 lib/django_evolution/tests/inheritance.py          |   82 +++
 lib/django_evolution/tests/models.py               |    3 +
 lib/django_evolution/tests/ordering.py             |   49 ++
 lib/django_evolution/tests/rename_field.py         |  399 ++++++++++++
 lib/django_evolution/tests/signature.py            |  248 +++++++
 lib/django_evolution/tests/sql_mutation.py         |   93 +++
 lib/django_evolution/tests/utils.py                |  185 ++++++
 lib/django_evolution/utils.py                      |   22 +
 .../commands => lib/registration}/__init__.py      |    0
 {registration => lib/registration}/admin.py        |    0
 {registration => lib/registration}/forms.py        |    0
 .../registration}/locale/ar/LC_MESSAGES/django.mo  |  Bin 2135 -> 2135 bytes
 .../registration}/locale/ar/LC_MESSAGES/django.po  |    0
 .../registration}/locale/bg/LC_MESSAGES/django.mo  |  Bin 2302 -> 2302 bytes
 .../registration}/locale/bg/LC_MESSAGES/django.po  |    0
 .../registration}/locale/de/LC_MESSAGES/django.mo  |  Bin 1909 -> 1909 bytes
 .../registration}/locale/de/LC_MESSAGES/django.po  |    0
 .../registration}/locale/el/LC_MESSAGES/django.mo  |  Bin 2424 -> 2424 bytes
 .../registration}/locale/el/LC_MESSAGES/django.po  |    0
 .../registration}/locale/en/LC_MESSAGES/django.mo  |  Bin 367 -> 367 bytes
 .../registration}/locale/en/LC_MESSAGES/django.po  |    0
 .../registration}/locale/es/LC_MESSAGES/django.mo  |  Bin 1909 -> 1909 bytes
 .../registration}/locale/es/LC_MESSAGES/django.po  |    0
 .../locale/es_AR/LC_MESSAGES/django.mo             |  Bin 1849 -> 1849 bytes
 .../locale/es_AR/LC_MESSAGES/django.po             |    0
 .../registration}/locale/fr/LC_MESSAGES/django.mo  |  Bin 1883 -> 1883 bytes
 .../registration}/locale/fr/LC_MESSAGES/django.po  |    0
 .../registration}/locale/he/LC_MESSAGES/django.mo  |  Bin 1896 -> 1896 bytes
 .../registration}/locale/he/LC_MESSAGES/django.po  |    0
 .../registration}/locale/is/LC_MESSAGES/django.mo  |  Bin 1476 -> 1476 bytes
 .../registration}/locale/is/LC_MESSAGES/django.po  |    0
 .../registration}/locale/it/LC_MESSAGES/django.mo  |  Bin 1864 -> 1864 bytes
 .../registration}/locale/it/LC_MESSAGES/django.po  |    0
 .../registration}/locale/ja/LC_MESSAGES/django.mo  |  Bin 2035 -> 2035 bytes
 .../registration}/locale/ja/LC_MESSAGES/django.po  |    0
 .../registration}/locale/nl/LC_MESSAGES/django.mo  |  Bin 1898 -> 1898 bytes
 .../registration}/locale/nl/LC_MESSAGES/django.po  |    0
 .../registration}/locale/pl/LC_MESSAGES/django.mo  |  Bin 1769 -> 1769 bytes
 .../registration}/locale/pl/LC_MESSAGES/django.po  |    0
 .../locale/pt_BR/LC_MESSAGES/django.mo             |  Bin 1796 -> 1796 bytes
 .../locale/pt_BR/LC_MESSAGES/django.po             |    0
 .../registration}/locale/ru/LC_MESSAGES/django.mo  |  Bin 2360 -> 2360 bytes
 .../registration}/locale/ru/LC_MESSAGES/django.po  |    0
 .../registration}/locale/sr/LC_MESSAGES/django.mo  |  Bin 1966 -> 1966 bytes
 .../registration}/locale/sr/LC_MESSAGES/django.po  |    0
 .../registration}/locale/sv/LC_MESSAGES/django.mo  |  Bin 1687 -> 1687 bytes
 .../registration}/locale/sv/LC_MESSAGES/django.po  |    0
 .../locale/zh_CN/LC_MESSAGES/django.mo             |  Bin 1669 -> 1669 bytes
 .../locale/zh_CN/LC_MESSAGES/django.po             |    0
 .../locale/zh_TW/LC_MESSAGES/django.mo             |  Bin 1669 -> 1669 bytes
 .../locale/zh_TW/LC_MESSAGES/django.po             |    0
 .../registration/management}/__init__.py           |    0
 .../registration/management/commands}/__init__.py  |    0
 .../management/commands/cleanupregistration.py     |    0
 {registration => lib/registration}/models.py       |    0
 {registration => lib/registration}/signals.py      |    0
 {registration => lib/registration}/tests.py        |    0
 {registration => lib/registration}/urls.py         |    0
 {registration => lib/registration}/views.py        |    0
 lib/reversion/__init__.py                          |   11 +
 lib/reversion/admin.py                             |  264 ++++++++
 lib/reversion/helpers.py                           |  141 ++++
 lib/reversion/managers.py                          |   60 ++
 lib/reversion/middleware.py                        |   27 +
 lib/reversion/models.py                            |  111 ++++
 lib/reversion/registration.py                      |   59 ++
 lib/reversion/revisions.py                         |  188 ++++++
 lib/reversion/storage.py                           |   18 +
 lib/reversion/templates/reversion/change_list.html |   14 +
 .../templates/reversion/object_history.html        |   35 +
 .../templates/reversion/recover_form.html          |   25 +
 .../templates/reversion/recover_list.html          |   39 ++
 .../templates/reversion/revision_form.html         |   33 +
 settings.py                                        |   17 +-
 104 files changed, 6654 insertions(+), 2 deletions(-)

diff --git a/lib/django_evolution/__init__.py b/lib/django_evolution/__init__.py
new file mode 100644
index 0000000..323db75
--- /dev/null
+++ b/lib/django_evolution/__init__.py
@@ -0,0 +1,15 @@
+class EvolutionException(Exception):
+    def __init__(self,msg):
+        self.msg = msg
+
+    def __str__(self):
+        return str(self.msg)
+        
+class CannotSimulate(EvolutionException):
+    pass
+    
+class SimulationFailure(EvolutionException):
+    pass
+
+class EvolutionNotImplementedError(EvolutionException, NotImplementedError):
+    pass
\ No newline at end of file
diff --git a/lib/django_evolution/admin.py b/lib/django_evolution/admin.py
new file mode 100644
index 0000000..c41d472
--- /dev/null
+++ b/lib/django_evolution/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from django_evolution.models import Version, Evolution
+
+admin.site.register(Version)
+admin.site.register(Evolution)
diff --git a/lib/django_evolution/db/__init__.py b/lib/django_evolution/db/__init__.py
new file mode 100644
index 0000000..fcaf3c2
--- /dev/null
+++ b/lib/django_evolution/db/__init__.py
@@ -0,0 +1,9 @@
+# Establish the common EvolutionOperations instance, called evolver.
+
+from django.conf import settings
+
+module_name = ['django_evolution.db',settings.DATABASE_ENGINE]
+module = __import__('.'.join(module_name),{},{},[''])
+
+evolver = module.EvolutionOperations()
+
diff --git a/lib/django_evolution/db/common.py b/lib/django_evolution/db/common.py
new file mode 100644
index 0000000..ae332bf
--- /dev/null
+++ b/lib/django_evolution/db/common.py
@@ -0,0 +1,166 @@
+from django.core.management import color
+from django.db import connection, models
+import copy
+
+class BaseEvolutionOperations(object):
+    def quote_sql_param(self, param):
+        "Add protective quoting around an SQL string parameter"
+        if isinstance(param, basestring):
+            return u"'%s'" % unicode(param).replace(u"'",ur"\'")
+        else:
+            return param
+
+    def rename_table(self, old_db_tablename, db_tablename):
+        if old_db_tablename == db_tablename:
+            # No Operation
+            return []
+    
+        qn = connection.ops.quote_name
+        params = (qn(old_db_tablename), qn(db_tablename))
+        return ['ALTER TABLE %s RENAME TO %s;' % params]
+    
+    def delete_column(self, model, f):
+        qn = 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
+        return ['DROP TABLE %s;' % qn(table_name)]
+    
+    def add_m2m_table(self, model, f):
+        final_output = []
+        qn = connection.ops.quote_name
+        opts = model._meta
+        style = color.no_style()
+    
+        return connection.creation.sql_for_many_to_many_field(model, f, style)
+    
+    def add_column(self, model, f, initial):
+        qn = connection.ops.quote_name
+    
+        if f.rel:
+            # it is a foreign key field
+            # NOT NULL REFERENCES "django_evolution_addbasemodel" ("id") DEFERRABLE INITIALLY DEFERRED
+            # ALTER TABLE <tablename> ADD COLUMN <column name> NULL REFERENCES <tablename1> ("<colname>") DEFERRABLE INITIALLY DEFERRED
+            related_model = f.rel.to
+            related_table = related_model._meta.db_table
+            related_pk_col = related_model._meta.pk.name
+            constraints = ['%sNULL' % (not f.null and 'NOT ' or '')]
+            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())
+            output = ['ALTER TABLE %s ADD COLUMN %s %s %s REFERENCES %s (%s) %s;' % params]
+        else:
+            null_constraints = '%sNULL' % (not f.null and 'NOT ' or '')
+            if f.unique or f.primary_key:
+                unique_constraints = 'UNIQUE'
+            else:
+                unique_constraints = ''
+
+            # 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.
+            if initial is not None:
+                params = (qn(model._meta.db_table), qn(f.column), f.db_type(), unique_constraints)
+                output = ['ALTER TABLE %s ADD COLUMN %s %s %s;' % params]
+            
+                if callable(initial):
+                    params = (qn(model._meta.db_table), qn(f.column), initial(), qn(f.column))
+                    output.append('UPDATE %s SET %s = %s WHERE %s IS NULL;' % params)
+                else:
+                    params = (qn(model._meta.db_table), qn(f.column), qn(f.column))
+                    output.append(('UPDATE %s SET %s = %%s WHERE %s IS NULL;' % params, (initial,)))
+            
+                if not f.null:
+                    # Only put this sql statement if the column cannot be null.
+                    output.append(self.set_field_null(model, f, f.null))
+            else:
+                params = (qn(model._meta.db_table), qn(f.column), f.db_type(),' '.join([null_constraints, unique_constraints]))
+                output = ['ALTER TABLE %s ADD COLUMN %s %s %s;' % params]
+        return output
+
+    def set_field_null(self, model, f, null):
+        qn = 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
+        else:
+            return 'ALTER TABLE %s ALTER COLUMN %s SET NOT NULL;' % params 
+    
+    def create_index(self, model, f):
+        "Returns the CREATE INDEX SQL statements."
+        output = []
+        qn = connection.ops.quote_name
+        style = color.no_style()
+    
+        return connection.creation.sql_indexes_for_field(model, f, style)
+        
+    def drop_index(self, model, f):
+        qn = connection.ops.quote_name
+        return ['DROP INDEX %s;' % qn(self.get_index_name(model, f))]
+        
+    def get_index_name(self, model, f):
+        return '%s_%s' % (model._meta.db_table, f.column)
+        
+    def change_null(self, model, field_name, new_null_attr, initial=None):
+        qn = connection.ops.quote_name
+        opts = model._meta
+        f = opts.get_field(field_name)
+        output = []
+        if new_null_attr:
+            # Setting null to True
+            opts = model._meta
+            params = (qn(opts.db_table), qn(f.column),)
+            output.append(self.set_field_null(model, f, new_null_attr))
+        else:
+            if initial is not None:
+                output = []
+                if callable(initial):
+                    params = (qn(opts.db_table), qn(f.column), initial(), qn(f.column))
+                    output.append('UPDATE %s SET %s = %s WHERE %s IS NULL;' % params)
+                else:
+                    params = (qn(opts.db_table), qn(f.column), qn(f.column))
+                    output.append(('UPDATE %s SET %s = %%s WHERE %s IS NULL;' % params, (initial,)))
+            output.append(self.set_field_null(model, f, new_null_attr))
+            
+        return output
+        
+    def change_max_length(self, model, field_name, new_max_length, initial=None):
+        qn = connection.ops.quote_name
+        opts = model._meta
+        f = opts.get_field(field_name)
+        f.max_length = new_max_length
+        params = (qn(opts.db_table), qn(f.column), f.db_type(), qn(f.column), f.db_type())
+        return ['ALTER TABLE %s ALTER COLUMN %s TYPE %s USING CAST(%s as %s);' % params]
+
+    def change_db_column(self, model, field_name, new_db_column, initial=None):
+        opts = model._meta
+        old_field = opts.get_field(field_name)
+        new_field = copy.copy(old_field)
+        new_field.column = new_db_column
+        return self.rename_column(opts, old_field, new_field)
+
+    def change_db_table(self, old_db_tablename, new_db_tablename):
+        return self.rename_table(old_db_tablename, new_db_tablename)
+        
+    def change_db_index(self, model, field_name, new_db_index, initial=None):
+        f = model._meta.get_field(field_name)
+        f.db_index = new_db_index
+        if new_db_index:
+            return self.create_index(model, f)
+        else:
+            return self.drop_index(model, f)
+            
+    def change_unique(self, model, field_name, new_unique_value, initial=None):
+        qn = connection.ops.quote_name
+        opts = model._meta
+        f = opts.get_field(field_name)
+        constraint_name = '%s_%s_key' % (opts.db_table, f.column,)
+        if new_unique_value:
+            params = (qn(opts.db_table), constraint_name, qn(f.column),)
+            return ['ALTER TABLE %s ADD CONSTRAINT %s UNIQUE(%s);' % params]
+        else:
+            params = (qn(opts.db_table), constraint_name,)
+            return ['ALTER TABLE %s DROP CONSTRAINT %s;' % params]
diff --git a/lib/django_evolution/db/mysql.py b/lib/django_evolution/db/mysql.py
new file mode 100644
index 0000000..04d18c7
--- /dev/null
+++ b/lib/django_evolution/db/mysql.py
@@ -0,0 +1,89 @@
+from django.core.management import color
+from django.db import connection
+
+from common import BaseEvolutionOperations
+
+class EvolutionOperations(BaseEvolutionOperations):
+    def rename_column(self, opts, old_field, f):
+        if old_field.column == f.column:
+            # No Operation
+            return []
+    
+        qn = connection.ops.quote_name
+        style = color.no_style()
+    
+        ###
+        col_type = f.db_type()
+        tablespace = f.db_tablespace or opts.db_tablespace
+        if col_type is None:
+            # Skip ManyToManyFields, because they're not represented as
+            # database columns in this table.
+            return []
+        # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
+        field_output = [style.SQL_FIELD(qn(f.column)),
+            style.SQL_COLTYPE(col_type)]
+        field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
+        if f.primary_key:
+            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:
+            # 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))
+        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()
+            )
+        
+        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
+        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
+        else:
+            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
+        opts = model._meta
+        f = opts.get_field(field_name)
+        f.max_length = new_max_length
+        params = {
+            'table': qn(opts.db_table),
+            'column': qn(f.column), 
+            'length': f.max_length, 
+            'type': f.db_type()
+        }
+        return ['UPDATE %(table)s SET %(column)s=LEFT(%(column)s,%(length)d);' % params,
+                'ALTER TABLE %(table)s MODIFY COLUMN %(column)s %(type)s;' % params]
+
+    def drop_index(self, model, f):
+        qn = 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
+        opts = model._meta
+        f = opts.get_field(field_name)
+        constraint_name = '%s' % (f.column,)
+        if new_unique_value:
+            params = (constraint_name, qn(opts.db_table), qn(f.column),)
+            return ['CREATE UNIQUE INDEX %s ON %s(%s);' % params]
+        else:
+            params = (constraint_name, qn(opts.db_table))
+            return ['DROP INDEX %s ON %s;' % params]
+
+    def rename_table(self, old_db_tablename, db_tablename):
+        if old_db_tablename == db_tablename:
+            return []
+        
+        qn = connection.ops.quote_name
+        params = (qn(old_db_tablename), qn(db_tablename))
+        return ['RENAME TABLE %s TO %s;' % params]
diff --git a/lib/django_evolution/db/mysql_old.py b/lib/django_evolution/db/mysql_old.py
new file mode 100644
index 0000000..505a6b8
--- /dev/null
+++ b/lib/django_evolution/db/mysql_old.py
@@ -0,0 +1,2 @@
+# MySQL_old behaviour is identical to mysql base
+from mysql import *
\ No newline at end of file
diff --git a/lib/django_evolution/db/postgresql.py b/lib/django_evolution/db/postgresql.py
new file mode 100644
index 0000000..95bbf11
--- /dev/null
+++ b/lib/django_evolution/db/postgresql.py
@@ -0,0 +1,14 @@
+from django.db import connection
+
+from common import BaseEvolutionOperations
+
+class EvolutionOperations(BaseEvolutionOperations):
+    def rename_column(self, opts, old_field, new_field):
+        if old_field.column == new_field.column:
+            # No Operation
+            return []
+    
+        qn = connection.ops.quote_name
+        params = (qn(opts.db_table), qn(old_field.column), qn(new_field.column))
+        return ['ALTER TABLE %s RENAME COLUMN %s TO %s;' % params]
+    
\ No newline at end of file
diff --git a/lib/django_evolution/db/postgresql_psycopg2.py b/lib/django_evolution/db/postgresql_psycopg2.py
new file mode 100644
index 0000000..7557d22
--- /dev/null
+++ b/lib/django_evolution/db/postgresql_psycopg2.py
@@ -0,0 +1,2 @@
+# Psycopg2 behaviour is identical to Psycopg1
+from postgresql import *
\ No newline at end of file
diff --git a/lib/django_evolution/db/sqlite3.py b/lib/django_evolution/db/sqlite3.py
new file mode 100644
index 0000000..5a93080
--- /dev/null
+++ b/lib/django_evolution/db/sqlite3.py
@@ -0,0 +1,200 @@
+from django.core.management import color
+from django.db import connection, models
+
+from common import BaseEvolutionOperations
+
+TEMP_TABLE_NAME = 'TEMP_TABLE'
+
+class EvolutionOperations(BaseEvolutionOperations):
+    def delete_column(self, model, f):
+        qn = connection.ops.quote_name
+        output = []
+
+        field_list = [field for field in model._meta.local_fields
+                        if f.name != field.name # Remove the field to be deleted
+                        and field.db_type() is not None] # and any Generic fields
+        table_name = model._meta.db_table
+
+        output.extend(self.create_temp_table(field_list))
+        output.extend(self.copy_to_temp_table(table_name, field_list))
+        output.extend(self.delete_table(table_name))
+        output.extend(self.create_table(table_name, field_list))
+        output.extend(self.copy_from_temp_table(table_name, field_list))
+        output.extend(self.delete_table(TEMP_TABLE_NAME))
+
+        return output
+
+    def create_mock_model(self, model, fields):
+        field_sig_dict = {}
+        for f in fields:
+            field_sig_dict[f.name] = create_field_sig(field)
+
+        proj_sig = create_project_sig()
+        model_sig = create_model_sig(model)
+        model_sig['fields'] = field_sig_dict
+        mock_model = MockModel(proj_sig, model.app_name, model.model_name, model_sig, stub=False)
+
+    def copy_to_temp_table(self, source_table_name, field_list):
+        qn = connection.ops.quote_name
+        output = [self.create_temp_table(field_list)]
+        columns = []
+        for field in field_list:
+            if not models.ManyToManyField == field.__class__:
+                columns.append(qn(field.column))
+        column_names = ', '.join(columns)
+        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
+        columns = []
+        for field in field_list:
+            if not models.ManyToManyField == field.__class__:
+                columns.append(qn(field.column))
+        column_names = ', '.join(columns)
+        params = {
+            'dest_table_name': qn(dest_table_name),
+            'temp_table': qn(TEMP_TABLE_NAME),
+            'column_names': column_names,
+        }
+
+        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
+
+        # 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.
+        if initial is None:
+            return []
+
+        params = {
+            'table_name': qn(TEMP_TABLE_NAME),
+            'column_name': qn(field.column),
+        }
+
+        if callable(initial):
+            params['value'] = initial()
+            return ["UPDATE %(table_name)s SET %(column_name)s = %(value)s;" % params]
+        else:
+            return [("UPDATE %(table_name)s SET %(column_name)s = %%s;" % params, (initial,))]
+
+
+    def create_temp_table(self, field_list):
+        return self.create_table(TEMP_TABLE_NAME, field_list, True, False)
+
+    def create_indexes_for_table(self, table_name, field_list):
+        class FakeMeta(object):
+            def __init__(self, table_name, field_list):
+                self.db_table = table_name
+                self.local_fields = field_list
+                self.fields = field_list # Required for Pre QS-RF support
+                self.db_tablespace = None
+                self.managed = True
+
+        class FakeModel(object):
+            def __init__(self, table_name, field_list):
+                self._meta = FakeMeta(table_name, field_list)
+
+        style = color.no_style()
+        return 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
+        output = []
+
+        create = ['CREATE']
+        if temporary:
+            create.append('TEMPORARY')
+        create.append('TABLE %s' % qn(table_name))
+        output = [' '.join(create)]
+        output.append('(')
+        columns = []
+        for field in field_list:
+            if not models.ManyToManyField == field.__class__:
+                column_name = qn(field.column)
+                column_type = field.db_type()
+                params = [column_name, column_type]
+                if field.null:
+                    params.append('NULL')
+                else:
+                    params.append('NOT NULL')
+                if field.unique:
+                    params.append('UNIQUE')
+                if field.primary_key:
+                    params.append('PRIMARY KEY')
+                columns.append(' '.join(params))
+
+        output.append(', '.join(columns))
+        output.append(');')
+        output = [''.join(output)]
+
+        if create_index:
+            output.extend(self.create_indexes_for_table(table_name, field_list))
+
+        return output
+
+    def rename_column(self, opts, old_field, new_field):
+        if old_field.column == new_field.column:
+            # No Operation
+            return []
+
+        original_fields = opts.local_fields
+        new_fields = []
+        for f in original_fields:
+            if f.db_type() is not None: # Ignore Generic Fields
+                if f.name == old_field.name:
+                    new_fields.append(new_field)
+                else:
+                    new_fields.append(f)
+
+        table_name = opts.db_table
+        output = []
+        output.extend(self.create_temp_table(new_fields))
+        output.extend(self.copy_to_temp_table(table_name, original_fields))
+        output.extend(self.delete_table(table_name))
+        output.extend(self.create_table(table_name, new_fields))
+        output.extend(self.copy_from_temp_table(table_name, new_fields))
+        output.extend(self.delete_table(TEMP_TABLE_NAME))
+
+        return output
+
+    def add_column(self, model, f, initial):
+        output = []
+        table_name = model._meta.db_table
+        original_fields = [field for field in model._meta.local_fields if field.db_type() is not None]
+        new_fields = original_fields
+        new_fields.append(f)
+
+        output.extend(self.create_temp_table(new_fields))
+        output.extend(self.copy_to_temp_table(table_name, original_fields))
+        output.extend(self.insert_to_temp_table(f, initial))
+        output.extend(self.delete_table(table_name))
+        output.extend(self.create_table(table_name, new_fields, create_index=False))
+        output.extend(self.copy_from_temp_table(table_name, new_fields))
+        output.extend(self.delete_table(TEMP_TABLE_NAME))
+        return output
+
+    def change_null(self, model, field_name, new_null_attr, initial=None):
+        return self.change_attribute(model, field_name, 'null', new_null_attr, initial)
+
+    def change_max_length(self, model, field_name, new_max_length, initial=None):
+        return self.change_attribute(model, field_name, 'max_length', new_max_length, initial)
+
+    def change_unique(self, model, field_name, new_unique_value, initial=None):
+        return self.change_attribute(model, field_name, '_unique', new_unique_value, initial)
+
+    def change_attribute(self, model, field_name, attr_name, new_attr_value, initial=None):
+        output = []
+        opts = model._meta
+        table_name = opts.db_table
+        setattr(opts.get_field(field_name), attr_name, new_attr_value)
+        fields = [f for f in opts.local_fields if f.db_type() is not None]
+
+        output.extend(self.create_temp_table(fields))
+        output.extend(self.copy_to_temp_table(table_name, fields))
+        output.extend(self.insert_to_temp_table(opts.get_field(field_name), initial))
+        output.extend(self.delete_table(table_name))
+        output.extend(self.create_table(table_name, fields, create_index=False))
+        output.extend(self.copy_from_temp_table(table_name, fields))
+        output.extend(self.delete_table(TEMP_TABLE_NAME))
+        return output
diff --git a/lib/django_evolution/diff.py b/lib/django_evolution/diff.py
new file mode 100644
index 0000000..41d063b
--- /dev/null
+++ b/lib/django_evolution/diff.py
@@ -0,0 +1,205 @@
+from django.db import models
+from django.db.models.fields.related import *
+
+from django_evolution import EvolutionException
+from django_evolution.mutations import DeleteField, AddField, DeleteModel, ChangeField
+from django_evolution.signature import ATTRIBUTE_DEFAULTS
+
+try:
+    set
+except ImportError:
+    from sets import Set as set #Python 2.3 Fallback
+
+class NullFieldInitialCallback(object):
+    def __init__(self, app, model, field):
+        self.app = app
+        self.model = model
+        self.field = field
+
+    def __repr__(self):
+        return '<<USER VALUE REQUIRED>>'
+
+    def __call__(self):
+        raise EvolutionException("Cannot use hinted evolution: AddField or ChangeField mutation for '%s.%s' in '%s' requires user-specified initial value." % (
+                                    self.model, self.field, self.app))
+
+def get_initial_value(app_label, model_name, field_name):
+    """Derive an initial value for a field.
+
+    If a default has been provided on the field definition or the field allows
+    for an empty string, that value will be used. Otherwise, a placeholder
+    callable will be used. This callable cannot actually be used in an
+    evolution, but will indicate that user input is required.
+    """
+    model = models.get_model(app_label, model_name)
+    field = model._meta.get_field(field_name)
+    if field and (field.has_default() or (field.empty_strings_allowed and field.blank)):
+        return field.get_default()
+    return NullFieldInitialCallback(app_label, model_name, field_name)
+
+class Diff(object):
+    """
+    A diff between two model signatures.
+
+    The resulting diff is contained in two attributes:
+
+    self.changed = {
+        app_label: {
+            'changed': {
+                model_name : {
+                    'added': [ list of added field names ]
+                    'deleted': [ list of deleted field names ]
+                    'changed': {
+                        field: [ list of modified property names ]
+                    }
+                }
+            'deleted': [ list of deleted model names ]
+        }
+    }
+    self.deleted = {
+        app_label: [ list of models in deleted app ]
+    }
+    """
+    def __init__(self, original, current):
+        self.original_sig = original
+        self.current_sig = current
+
+        self.changed = {}
+        self.deleted = {}
+
+        if self.original_sig.get('__version__', 1) != 1:
+            raise EvolutionException("Unknown version identifier in original signature: %s",
+                                        self.original_sig['__version__'])
+        if self.current_sig.get('__version__', 1) != 1:
+            raise EvolutionException("Unknown version identifier in target signature: %s",
+                                        self.current_sig['__version__'])
+
+        for app_name, old_app_sig in original.items():
+            if app_name == '__version__':
+                # Ignore the __version__ tag
+                continue
+            new_app_sig = self.current_sig.get(app_name, None)
+            if new_app_sig is None:
+                # App has been deleted
+                self.deleted[app_name] = old_app_sig.keys()
+                continue
+            for model_name, old_model_sig in old_app_sig.items():
+                new_model_sig = new_app_sig.get(model_name, None)
+                if new_model_sig is None:
+                    # Model has been deleted
+                    self.changed.setdefault(app_name,
+                        {}).setdefault('deleted',
+                        []).append(model_name)
+                    continue
+                # Look for deleted or modified fields
+                for field_name,old_field_data in old_model_sig['fields'].items():
+                    new_field_data = new_model_sig['fields'].get(field_name,None)
+                    if new_field_data is None:
+                        # Field has been deleted
+                        self.changed.setdefault(app_name,
+                            {}).setdefault('changed',
+                            {}).setdefault(model_name,
+                            {}).setdefault('deleted',
+                            []).append(field_name)
+                        continue
+                    properties = set(old_field_data.keys())
+                    properties.update(new_field_data.keys())
+                    for prop in properties:
+                        old_value = old_field_data.get(prop,
+                            ATTRIBUTE_DEFAULTS.get(prop, None))
+                        new_value = new_field_data.get(prop,
+                            ATTRIBUTE_DEFAULTS.get(prop, None))
+                        if old_value != new_value:
+                            try:
+                                if (prop == 'field_type' and
+                                    (old_value().get_internal_type() ==
+                                     new_value().get_internal_type())):
+                                    continue
+                            except TypeError:
+                                pass
+
+                            # Field has been changed
+                            self.changed.setdefault(app_name,
+                                {}).setdefault('changed',
+                                {}).setdefault(model_name,
+                                {}).setdefault('changed',
+                                {}).setdefault(field_name,[]).append(prop)
+                # Look for added fields
+                for field_name,new_field_data in new_model_sig['fields'].items():
+                    old_field_data = old_model_sig['fields'].get(field_name,None)
+                    if old_field_data is None:
+                        self.changed.setdefault(app_name,
+                            {}).setdefault('changed',
+                            {}).setdefault(model_name,
+                            {}).setdefault('added',
+                            []).append(field_name)
+
+    def is_empty(self, ignore_apps=True):
+        """Is this an empty diff? i.e., is the source and target the same?
+
+        Set 'ignore_apps=False' if you wish to ignore changes caused by
+        deleted applications. This is used when you don't purge deleted
+        applications during an evolve.
+        """
+        if ignore_apps:
+            return not self.changed
+        else:
+            return not self.deleted and not self.changed
+
+    def __str__(self):
+        "Output an application signature diff in a human-readable format"
+        lines = []
+        for app_label in self.deleted:
+            lines.append('The application %s has been deleted' % app_label)
+        for app_label, app_changes in self.changed.items():
+            for model_name in app_changes.get('deleted', {}):
+                lines.append('The model %s.%s has been deleted' % (app_label, model_name))
+            for model_name, change in app_changes.get('changed', {}).items():
+                lines.append('In model %s.%s:' % (app_label, model_name))
+                for field_name in change.get('added',[]):
+                    lines.append("    Field '%s' has been added" % field_name)
+                for field_name in change.get('deleted',[]):
+                    lines.append("    Field '%s' has been deleted" % field_name)
+                for field_name,field_change in change.get('changed',{}).items():
+                    lines.append("    In field '%s':" % field_name)
+                    for prop in field_change:
+                        lines.append("        Property '%s' has changed" % prop)
+        return '\n'.join(lines)
+
+    def evolution(self):
+        "Generate an evolution that would neutralize the diff"
+        mutations = {}
+        for app_label, app_changes in self.changed.items():
+            for model_name, change in app_changes.get('changed',{}).items():
+                for field_name in change.get('added',{}):
+                    field_sig = self.current_sig[app_label][model_name]['fields'][field_name]
+                    add_params = [(key,field_sig[key])
+                                    for key in field_sig.keys()
+                                    if key in ATTRIBUTE_DEFAULTS.keys()]
+                    add_params.append(('field_type', field_sig['field_type']))
+
+                    if field_sig['field_type'] != models.ManyToManyField and not field_sig.get('null', ATTRIBUTE_DEFAULTS['null']):
+                        add_params.append(('initial', get_initial_value(app_label, model_name, field_name)))
+                    if 'related_model' in field_sig:
+                        add_params.append(('related_model', '%s' % field_sig['related_model']))
+                    mutations.setdefault(app_label,[]).append(
+                        AddField(model_name, field_name, **dict(add_params)))
+                for field_name in change.get('deleted',[]):
+                    mutations.setdefault(app_label,[]).append(
+                        DeleteField(model_name, field_name))
+                for field_name,field_change in change.get('changed',{}).items():
+                    changed_attrs = {}
+                    current_field_sig = self.current_sig[app_label][model_name]['fields'][field_name]
+                    for prop in field_change:
+                        if prop == 'related_model':
+                            changed_attrs[prop] = current_field_sig[prop]
+                        else:
+                            changed_attrs[prop] = current_field_sig.get(prop, ATTRIBUTE_DEFAULTS[prop])
+                    if changed_attrs.has_key('null') and \
+                        current_field_sig['field_type'] != models.ManyToManyField and \
+                        not current_field_sig.get('null', ATTRIBUTE_DEFAULTS['null']):
+                        changed_attrs['initial'] = get_initial_value(app_label, model_name, field_name)
+                    mutations.setdefault(app_label,[]).append(ChangeField(model_name, field_name, **changed_attrs))
+            for model_name in app_changes.get('deleted',{}):
+                mutations.setdefault(app_label,[]).append(DeleteModel(model_name))
+        return mutations
diff --git a/lib/django_evolution/evolve.py b/lib/django_evolution/evolve.py
new file mode 100644
index 0000000..5bafe8d
--- /dev/null
+++ b/lib/django_evolution/evolve.py
@@ -0,0 +1,60 @@
+import os
+import sys
+import copy
+
+from django.core.management.color import color_style
+from django.db import transaction, connection
+from django.db.models import loading
+
+from django_evolution import EvolutionException, CannotSimulate, SimulationFailure
+from django_evolution.models import Evolution
+from django_evolution.diff import Diff
+from django_evolution.mutations import SQLMutation
+
+def get_evolution_sequence(app):
+    "Obtain the full evolution sequence for an application"
+    try:
+        app_name = '.'.join(app.__name__.split('.')[:-1])
+        evolution_module = __import__(app_name + '.evolutions',{},{},[''])
+        return evolution_module.SEQUENCE
+    except:
+        return []
+    
+def get_unapplied_evolutions(app):
+    "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)]
+    return [seq for seq in sequence if seq not in applied]
+    
+def get_mutations(app, evolution_labels):
+    """
+    Obtain the list of mutations described by the named evolutions.
+    """
+    # For each item in the evolution sequence. Check each item to see if it is
+    # a python file or an sql file.
+    try:
+        app_name = '.'.join(app.__name__.split('.')[:-1])
+        evolution_module = __import__(app_name + '.evolutions',{},{},[''])
+    except ImportError:
+        return []
+
+    mutations = []
+    for label in evolution_labels:
+        directory_name = os.path.dirname(evolution_module.__file__)
+        sql_file_name = os.path.join(directory_name, label+'.sql')
+        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))
+        else:
+            try:
+                module_name = [evolution_module.__name__,label]
+                module = __import__('.'.join(module_name),{},{},[module_name]);
+                mutations.extend(module.MUTATIONS)
+            except ImportError, e:
+                raise EvolutionException('Error: Failed to find an SQL or Python evolution named %s' % label)
+            
+    return mutations
diff --git a/lib/django_evolution/management/__init__.py b/lib/django_evolution/management/__init__.py
new file mode 100644
index 0000000..5e09c71
--- /dev/null
+++ b/lib/django_evolution/management/__init__.py
@@ -0,0 +1,100 @@
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle as pickle
+
+from django.dispatch import dispatcher
+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.evolve import get_evolution_sequence, get_unapplied_evolutions
+from django_evolution.signature import create_project_sig
+from django_evolution.diff import Diff
+
+style = color_style()
+    
+def evolution(app, created_models, verbosity=1, **kwargs):
+    """
+    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()
+    signature = pickle.dumps(proj_sig)
+
+    try:
+        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()
+
+        for a in get_apps():
+            app_label = a.__name__.split('.')[-2]
+            sequence = get_evolution_sequence(a)
+            if sequence:
+                if verbosity > 0:
+                    print 'Evolutions in %s baseline:' % app_label,', '.join(sequence)
+            for evo_label in sequence:
+                evolution = django_evolution.Evolution(app_label=app_label, 
+                                                       label=evo_label,
+                                                       version=latest_version)
+                evolution.save()
+
+    unapplied = get_unapplied_evolutions(app)
+    if unapplied:
+        print style.NOTICE('There are unapplied evolutions for %s.' % app.__name__.split('.')[-2])
+        
+    # Evolutions are checked over the entire project, so we only need to 
+    # check once. We do this check when Django Evolutions itself is synchronized.
+    if app == django_evolution:        
+        old_proj_sig = pickle.loads(str(latest_version.signature))
+        
+        # If any models have been added, a baseline must be set 
+        # for those new models
+        changed = False
+        for app_name, new_app_sig in proj_sig.items():
+            if app_name == '__version__':
+                # Ignore the __version__ tag
+                continue
+            old_app_sig = old_proj_sig.get(app_name, None)
+            if old_app_sig is None:
+                # App has been added
+                old_proj_sig[app_name] = proj_sig[app_name]
+                changed = True
+                continue
+            for model_name, new_model_sig in new_app_sig.items():
+                old_model_sig = old_app_sig.get(model_name, None)
+                if old_model_sig is None:
+                    # Model has been added
+                    old_proj_sig[app_name][model_name] = proj_sig[app_name][model_name]
+                    changed = True
+        
+        if changed:
+            if verbosity > 0:
+                print "Adding baseline version for new models"
+            latest_version = django_evolution.Version(signature=pickle.dumps(old_proj_sig))
+            latest_version.save()
+
+        # TODO: Model introspection step goes here. 
+        # # If the current database state doesn't match the last 
+        # # saved signature (as reported by latest_version),
+        # # then we need to update the Evolution table.
+        # actual_sig = introspect_project_sig()
+        # acutal = pickle.dumps(actual_sig)
+        # if actual != latest_version.signature:
+        #     nudge = Version(signature=actual)
+        #     nudge.save()
+        #     latest_version = nudge
+        
+        diff = Diff(old_proj_sig, proj_sig)
+        if not diff.is_empty():
+            print style.NOTICE('Project signature has changed - an evolution is required')
+            if verbosity > 1:
+                old_proj_sig = pickle.loads(str(latest_version.signature))
+                print diff
+                
+signals.post_syncdb.connect(evolution)
diff --git a/registration/__init__.py b/lib/django_evolution/management/commands/__init__.py
similarity index 100%
copy from registration/__init__.py
copy to lib/django_evolution/management/commands/__init__.py
diff --git a/lib/django_evolution/management/commands/evolve.py b/lib/django_evolution/management/commands/evolve.py
new file mode 100644
index 0000000..b2e4bd0
--- /dev/null
+++ b/lib/django_evolution/management/commands/evolve.py
@@ -0,0 +1,225 @@
+from optparse import make_option
+import sys
+import copy
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle as pickle
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.management.base import BaseCommand, CommandError
+from django.db.models import get_apps, get_app, signals
+from django.db import connection, transaction
+
+from django_evolution import CannotSimulate, SimulationFailure, EvolutionException
+from django_evolution.diff import Diff
+from django_evolution.evolve import get_unapplied_evolutions, get_mutations
+from django_evolution.models import Version, Evolution
+from django_evolution.mutations import DeleteApplication
+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,
+            help='Tells Django to NOT prompt the user for input of any kind.'),
+        make_option('--hint', action='store_true', dest='hint', default=False,
+            help='Generate an evolution script that would update the app.'),
+        make_option('--purge', action='store_true', dest='purge', default=False,
+            help='Generate evolutions to delete stale applications.'),
+        make_option('--sql', action='store_true', dest='compile_sql', default=False,
+            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.'),
+    )
+    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',
+            type='choice', choices=['0', '1', '2'],
+            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
+
+    help = 'Evolve the models in a Django project.'
+    args = '<appname appname ...>'
+
+    requires_model_validation = False
+
+    def handle(self, *app_labels, **options):
+        verbosity = int(options['verbosity'])
+        interactive = options['interactive']
+        execute = options['execute']
+        compile_sql = options['compile_sql']
+        hint = options['hint']
+        purge = options['purge']
+
+        # Use the list of all apps, unless app labels are specified.
+        if app_labels:
+            if execute:
+                raise CommandError('Cannot specify an application name when executing evolutions.')
+            try:
+                app_list = [get_app(app_label) for app_label in app_labels]
+            except (ImproperlyConfigured, ImportError), e:
+                raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
+        else:
+            app_list = get_apps()
+
+        # Iterate over all applications running the mutations
+        evolution_required = False
+        simulated = True
+        sql = []
+        new_evolutions = []
+
+        current_proj_sig = create_project_sig()
+        current_signature = pickle.dumps(current_proj_sig)
+
+        try:
+            latest_version = Version.objects.latest('when')
+            database_sig = pickle.loads(str(latest_version.signature))
+            diff = Diff(database_sig, current_proj_sig)
+        except Evolution.DoesNotExist:
+            print self.style.ERROR("Can't evolve yet. Need to set an evolution baseline.")
+            sys.exit(1)
+
+        try:
+            for app in app_list:
+                app_label = app.__name__.split('.')[-2]
+                if hint:
+                    evolutions = []
+                    hinted_evolution = diff.evolution()
+                    mutations = hinted_evolution.get(app_label,[])
+                else:
+                    evolutions = get_unapplied_evolutions(app)
+                    mutations = get_mutations(app, evolutions)
+
+                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))
+
+                        # Now run the simulation, which will modify the signatures
+                        try:
+                            mutation.simulate(app_label, database_sig)
+                        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)
+                        else:
+                            print '#----- Evolution for %s' % app_label
+                            print 'from django_evolution.mutations import *'
+                            print 'from django.db import models'
+                            print
+                            print 'MUTATIONS = ['
+                            print '   ',
+                            print ',\n    '.join(unicode(m) for m in mutations)
+                            print ']'
+                            print '#----------------------'
+
+                    sql.extend(app_sql)
+                else:
+                    if verbosity > 1:
+                        print 'Application %s is up to date' % app_label
+
+            # Process the purged applications if requested to do so.
+            if purge:
+                if diff.deleted:
+                    evolution_required = True
+                    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 not execute:
+                        if compile_sql:
+                            write_sql(purge_sql)
+                        else:
+                            print 'The following application(s) can be purged:'
+                            for app_label in diff.deleted:
+                                print '    ', app_label
+                            print
+                    sql.extend(purge_sql)
+                else:
+                    if verbosity > 1:
+                        print 'No applications need to be purged.'
+
+        except EvolutionException, e:
+            print self.style.ERROR(e)
+            sys.exit(1)
+
+        if simulated:
+            diff = Diff(database_sig, current_proj_sig)
+            if not diff.is_empty(not purge):
+                if hint:
+                    print self.style.ERROR('Your models contain changes that Django Evolution cannot resolve automatically.')
+                    print 'This is probably due to a currently unimplemented mutation type.'
+                    print 'You will need to manually construct a mutation to resolve the remaining changes.'
+                else:
+                    print self.style.ERROR('The stored evolutions do not completely resolve all model changes.')
+                    print 'Run `./manage.py evolve --hint` to see a suggestion for the changes required.'
+                print
+                print 'The following are the changes that could not be resolved:'
+                print diff
+                sys.exit(1)
+        else:
+            print self.style.NOTICE('Evolution could not be simulated, possibly due to raw SQL mutations')
+
+        if evolution_required:
+            if execute:
+                # Now that we've worked out the mutations required,
+                # and we know they simulate OK, run the evolutions
+                if interactive:
+                    confirm = raw_input("""
+You have requested a database evolution. This will alter tables
+and data currently in the %r database, and may result in
+IRREVERSABLE DATA LOSS. Evolutions should be *thoroughly* reviewed
+prior to execution.
+
+Are you sure you want to execute the evolutions?
+
+Type 'yes' to continue, or 'no' to cancel: """ % settings.DATABASE_NAME)
+                else:
+                    confirm = 'yes'
+
+                if confirm.lower() == 'yes':
+                    # Begin Transaction
+                    transaction.enter_transaction_management()
+                    transaction.managed(True)
+                    cursor = connection.cursor()
+                    try:
+                        # Perform the SQL
+                        execute_sql(cursor, sql)
+
+                        # Now update the evolution table
+                        version = Version(signature=current_signature)
+                        version.save()
+                        for evolution in new_evolutions:
+                            evolution.version = version
+                            evolution.save()
+
+                        transaction.commit()
+                    except Exception, ex:
+                        transaction.rollback()
+                        print self.style.ERROR('Error applying evolution: %s' % str(ex))
+                        sys.exit(1)
+                    transaction.leave_transaction_management()
+
+                    if verbosity > 0:
+                        print 'Evolution successful.'
+                else:
+                    print self.style.ERROR('Evolution cancelled.')
+            elif not compile_sql:
+                if verbosity > 0:
+                    if simulated:
+                        print "Trial evolution successful."
+                        print "Run './manage.py evolve %s--execute' to apply evolution." % (hint and '--hint ' or '')
+        else:
+            if verbosity > 0:
+                print 'No evolution required.'
diff --git a/lib/django_evolution/models.py b/lib/django_evolution/models.py
new file mode 100644
index 0000000..f2d8337
--- /dev/null
+++ b/lib/django_evolution/models.py
@@ -0,0 +1,27 @@
+from datetime import datetime
+
+from django.db import models
+
+class Version(models.Model):
+    signature = models.TextField()
+    when = models.DateTimeField(default=datetime.now)
+
+    class Meta:
+        ordering = ('-when',)
+        db_table = 'django_project_version'
+
+    def __unicode__(self):
+        if not self.evolutions.count():
+            return u'Hinted version, updated on %s' % self.when
+        return u'Stored version, updated on %s' % self.when
+
+class Evolution(models.Model):
+    version = models.ForeignKey(Version, related_name='evolutions')
+    app_label = models.CharField(max_length=200)
+    label = models.CharField(max_length=100)
+
+    class Meta:
+        db_table = 'django_evolution'
+        
+    def __unicode__(self):
+        return u"Evolution %s, applied to %s" % (self.label, self.app_label)
diff --git a/lib/django_evolution/mutations.py b/lib/django_evolution/mutations.py
new file mode 100644
index 0000000..7335abd
--- /dev/null
+++ b/lib/django_evolution/mutations.py
@@ -0,0 +1,475 @@
+import copy
+
+from django.contrib.contenttypes import generic
+from django.db.models.fields import *
+from django.db.models.fields.related import *
+from django.db import models
+from django.utils.functional import curry
+
+from django_evolution.signature import ATTRIBUTE_DEFAULTS, create_field_sig
+from django_evolution import CannotSimulate, SimulationFailure, EvolutionNotImplementedError
+from django_evolution.db import evolver
+
+FK_INTEGER_TYPES = ['AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField']
+
+def create_field(proj_sig, field_name, field_type, field_attrs):
+    """
+    Create an instance of a field from a field signature. This is useful for
+    accessing all the database property mechanisms built into fields.
+    """
+    # related_model isn't a valid field attribute, so it must be removed
+    # prior to instantiating the field, but it must be restored
+    # to keep the signature consistent.
+    related_model = field_attrs.pop('related_model', None)
+    if related_model:
+        related_app_name, related_model_name = related_model.split('.')
+        related_model_sig = proj_sig[related_app_name][related_model_name]
+        to = MockModel(proj_sig, related_app_name, related_model_name, related_model_sig, stub=True)
+        field = field_type(to, name=field_name, **field_attrs)
+        field_attrs['related_model'] = related_model
+    else:
+        field = field_type(name=field_name, **field_attrs)
+    field.set_attributes_from_name(field_name)
+
+    return field
+
+class MockMeta(object):
+    """
+    A mockup of a models Options object, based on the model signature.
+
+    The stub argument is used to circumvent recursive relationships. If
+    'stub' is provided, the constructed model will only be a stub -
+    it will only have a primary key field.
+    """
+    def __init__(self, proj_sig, app_name, model_name, model_sig, stub=False):
+        self.object_name = model_name
+        self.app_label = app_name
+        self.meta = {
+            'order_with_respect_to': None,
+            'has_auto_field': None
+        }
+        self.meta.update(model_sig['meta'])
+        self._fields = {}
+        self._many_to_many = {}
+        self.abstract = False
+
+        for field_name,field_sig in model_sig['fields'].items():
+            if not stub or field_sig.get('primary_key', False):
+                field_type = field_sig.pop('field_type')
+                field = create_field(proj_sig, field_name, field_type, field_sig)
+
+                if AutoField == type(field):
+                    self.meta['has_auto_field'] = True
+                    self.meta['auto_field'] = field
+
+                field_sig['field_type'] = field_type
+
+                if ManyToManyField == type(field):
+                    self._many_to_many[field.name] = field
+                else:
+                    self._fields[field.name] = field
+
+                field.set_attributes_from_name(field_name)
+                if field_sig.get('primary_key', False):
+                    self.pk = field
+
+    def __getattr__(self, name):
+        return self.meta[name]
+
+    def get_field(self, name):
+        try:
+            return self._fields[name]
+        except KeyError:
+            try:
+                return self._many_to_many[name]
+            except KeyError:
+                raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, name))
+
+    def get_field_by_name(self, name):
+        return (self.get_field(name), None, True, None)
+
+    def get_fields(self):
+        return self._fields.values()
+
+    def get_many_to_many_fields(self):
+        return self._many_to_many.values()
+
+    local_fields = property(fget=get_fields)
+    local_many_to_many = property(fget=get_many_to_many_fields)
+
+class MockModel(object):
+    """
+    A mockup of a model object, providing sufficient detail
+    to derive database column and table names using the standard
+    Django fields.
+    """
+    def __init__(self, proj_sig, app_name, model_name, model_sig, stub=False):
+        self.app_name = app_name
+        self.model_name = model_name
+        self._meta = MockMeta(proj_sig, app_name, model_name, model_sig, stub)
+
+    def __eq__(self, other):
+        return self.app_name == other.app_name and self.model_name == other.model_name
+
+class MockRelated(object):
+    """
+    A mockup of django.db.models.related.RelatedObject, providing
+    sufficient detail to derive database column and table names using
+    the standard Django fields.
+    """
+    def __init__(self, related_model, model, field):
+        self.parent_model = related_model
+        self.model = model
+        self.field = field
+
+class BaseMutation:
+    def __init__(self):
+        pass
+
+    def mutate(self, app_label, proj_sig):
+        """
+        Performs the mutation on the database. Database changes will occur
+        after this function is invoked.
+        """
+        raise NotImplementedError()
+
+    def simulate(self, app_label, proj_sig):
+        """
+        Performs a simulation of the mutation to be performed. The purpose of
+        the simulate function is to ensure that after all mutations have occured
+        the database will emerge in a state consistent with the currently loaded
+        models file.
+        """
+        raise NotImplementedError()
+
+class SQLMutation(BaseMutation):
+    def __init__(self, tag, sql, update_func=None):
+        self.tag = tag
+        self.sql = sql
+        self.update_func = update_func
+
+    def __str__(self):
+        return "SQLMutation('%s')" % self.tag
+
+    def simulate(self, app_label, proj_sig):
+        "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):
+        "The mutation of an SQL mutation returns the raw SQL"
+        return self.sql
+
+class DeleteField(BaseMutation):
+    def __init__(self, model_name, field_name):
+
+        self.model_name = 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):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+
+        # If the field was used in the unique_together attribute, update it.
+        unique_together = model_sig['meta']['unique_together']
+        unique_together_list = []
+        for ut_index in range(0, len(unique_together), 1):
+            ut = unique_together[ut_index]
+            unique_together_fields = []
+            for field_name_index in range(0, len(ut), 1):
+                field_name = ut[field_name_index]
+                if not field_name == self.field_name:
+                    unique_together_fields.append(field_name)
+
+            unique_together_list.append(tuple(unique_together_fields))
+        model_sig['meta']['unique_together'] = tuple(unique_together_list)
+
+        if model_sig['fields'][self.field_name].get('primary_key',False):
+            raise SimulationFailure('Cannot delete a primary key.')
+
+        # Simulate the deletion of the field.
+        try:
+            field_sig = model_sig['fields'].pop(self.field_name)
+        except KeyError, ke:
+            raise SimulationFailure('Cannot find the field named "%s".' % self.field_name)
+
+    def mutate(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+        field_sig = model_sig['fields'][self.field_name]
+
+        model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+        # Temporarily remove field_type from the field signature
+        # so that we can create a field
+        field_type = field_sig.pop('field_type')
+        field = create_field(proj_sig, self.field_name, field_type, field_sig)
+        field_sig['field_type'] = field_type
+
+        if field_type == models.ManyToManyField:
+            sql_statements = evolver.delete_table(field._get_m2m_db_table(model._meta))
+        else:
+            sql_statements = evolver.delete_column(model, field)
+
+        return sql_statements
+
+class AddField(BaseMutation):
+    def __init__(self, model_name, field_name, field_type, initial=None, **kwargs):
+        self.model_name = model_name
+        self.field_name = field_name
+        self.field_type = field_type
+        self.field_attrs = kwargs
+        self.initial = initial
+
+    def __str__(self):
+        params = (self.model_name, self.field_name, self.field_type.__name__)
+        str_output = ["'%s', '%s', models.%s" % params]
+
+        if self.initial is not None:
+            str_output.append('initial=%s' % repr(self.initial))
+
+        for key,value in self.field_attrs.items():
+            str_output.append("%s=%s" % (key,repr(value)))
+        return 'AddField(' + ', '.join(str_output) + ')'
+
+    def simulate(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+
+        if self.field_name in model_sig['fields']:
+            raise SimulationFailure("Model '%s.%s' already has a field named '%s'" % (
+                        app_label, self.model_name, self.field_name))
+
+        if self.field_type != models.ManyToManyField and not self.field_attrs.get('null', ATTRIBUTE_DEFAULTS['null']):
+            if self.initial is None:
+                raise SimulationFailure("Cannot create new column '%s' on '%s.%s' without a non-null initial value." % (
+                        self.field_name, app_label, self.model_name))
+
+        model_sig['fields'][self.field_name] = {
+            'field_type': self.field_type,
+        }
+        model_sig['fields'][self.field_name].update(self.field_attrs)
+
+    def mutate(self, app_label, proj_sig):
+        if self.field_type == models.ManyToManyField:
+            return self.add_m2m_table(app_label, proj_sig)
+        else:
+            return self.add_column(app_label, proj_sig)
+
+    def add_column(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+
+        model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+        field = create_field(proj_sig, self.field_name, self.field_type, self.field_attrs)
+
+        sql_statements = evolver.add_column(model, field, self.initial)
+
+        # Create SQL index if necessary
+        sql_statements.extend(evolver.create_index(model, field))
+
+        return sql_statements
+
+    def add_m2m_table(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+
+        model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+        field = create_field(proj_sig, self.field_name, self.field_type, self.field_attrs)
+        field.m2m_db_table = curry(field._get_m2m_db_table, model._meta)
+
+        related_app_label, related_model_name = self.field_attrs['related_model'].split('.')
+        related_sig = proj_sig[related_app_label][related_model_name]
+        related_model = MockModel(proj_sig, related_app_label, related_model_name, related_sig)
+        related = MockRelated(related_model, model, field)
+
+        field.m2m_column_name = curry(field._get_m2m_column_name, related)
+        field.m2m_reverse_name = curry(field._get_m2m_reverse_name, related)
+
+        sql_statements = evolver.add_m2m_table(model, field)
+
+        return sql_statements
+
+class RenameField(BaseMutation):
+    def __init__(self, model_name, old_field_name, new_field_name,
+                 db_column=None, db_table=None):
+        self.model_name = model_name
+        self.old_field_name = old_field_name
+        self.new_field_name = new_field_name
+        self.db_column = db_column
+        self.db_table = db_table
+
+    def __str__(self):
+        params = "'%s', '%s', '%s'" % (self.model_name, self.old_field_name, self.new_field_name)
+
+        if self.db_column:
+            params = params + ", db_column='%s'" % (self.db_column)
+        if self.db_table:
+            params = params + ", db_table='%s'" % (self.db_table)
+
+        return "RenameField(%s)" % params
+
+    def simulate(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+        field_dict = model_sig['fields']
+        field_sig = field_dict[self.old_field_name]
+
+        if models.ManyToManyField == field_sig['field_type']:
+            if self.db_table:
+                field_sig['db_table'] = self.db_table
+            else:
+                field_sig.pop('db_table',None)
+        elif self.db_column:
+            field_sig['db_column'] = self.db_column
+        else:
+            # db_column and db_table were not specified (or not specified for the
+            # appropriate field types). Clear the old value if one was set. This
+            # amounts to resetting the column or table name to the Django default name
+            field_sig.pop('db_column',None)
+
+        field_dict[self.new_field_name] = field_dict.pop(self.old_field_name)
+
+    def mutate(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+        old_field_sig = model_sig['fields'][self.old_field_name]
+
+        # Temporarily remove the field type so that we can create mock field instances
+        field_type = old_field_sig.pop('field_type')
+        # Duplicate the old field sig, and apply the table/column changes
+        new_field_sig = copy.copy(old_field_sig)
+        if models.ManyToManyField == field_type:
+            if self.db_table:
+                new_field_sig['db_table'] = self.db_table
+            else:
+                new_field_sig.pop('db_table', None)
+        elif self.db_column:
+            new_field_sig['db_column'] = self.db_column
+        else:
+            new_field_sig.pop('db_column', None)
+
+        # Create the mock field instances.
+        old_field = create_field(proj_sig, self.old_field_name, field_type, old_field_sig)
+        new_field = create_field(proj_sig, self.new_field_name, field_type, new_field_sig)
+
+        # Restore the field type to the signature
+        old_field_sig['field_type'] = field_type
+
+        opts = MockMeta(proj_sig, app_label, self.model_name, model_sig)
+        if models.ManyToManyField == field_type:
+            old_m2m_table = old_field._get_m2m_db_table(opts)
+            new_m2m_table = new_field._get_m2m_db_table(opts)
+
+            return evolver.rename_table(old_m2m_table, new_m2m_table)
+        else:
+            return evolver.rename_column(opts, old_field, new_field)
+
+class ChangeField(BaseMutation):
+    def __init__(self, model_name, field_name, initial=None, **kwargs):
+        self.model_name = model_name
+        self.field_name = field_name
+        self.field_attrs = kwargs
+        self.initial = initial
+
+    def __str__(self):
+        params = (self.model_name, self.field_name)
+        str_output = ["'%s', '%s'" % params]
+
+        str_output.append('initial=%s' % repr(self.initial))
+
+        for attr_name, attr_value in self.field_attrs.items():
+            if str == type(attr_value):
+                str_attr_value = "'%s'" % attr_value
+            else:
+                str_attr_value = str(attr_value)
+            str_output.append('%s=%s' % (attr_name, str_attr_value,))
+
+        return 'ChangeField(' + ', '.join(str_output) + ')'
+        
+        
+    def simulate(self, app_label, proj_sig):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+        field_sig = model_sig['fields'][self.field_name]
+
+        # Catch for no-op changes.
+        for field_attr, attr_value in self.field_attrs.items():
+            field_sig[field_attr] = attr_value
+
+        if self.field_attrs.has_key('null'):
+            if field_sig['field_type'] != models.ManyToManyField and not self.field_attrs['null']:
+                if self.initial is None:
+                    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):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+        old_field_sig = model_sig['fields'][self.field_name]
+        model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+
+        sql_statements = []
+        for field_attr, attr_value in self.field_attrs.items():
+            old_field_attr = old_field_sig.get(field_attr, ATTRIBUTE_DEFAULTS[field_attr])
+            # Avoid useless SQL commands if nothing has changed.
+            if not old_field_attr == attr_value:
+                try:
+                    evolver_func = getattr(evolver, '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':
+                        sql_statements.extend(evolver_func(old_field_attr, attr_value))
+                    else:
+                        sql_statements.extend(evolver_func(model, self.field_name, attr_value))
+                except AttributeError, ae:
+                    raise EvolutionNotImplementedError("ChangeField does not support modifying the '%s' attribute on '%s.%s'." % (field_attr, self.model_name, self.field_name))
+
+        return sql_statements
+
+class DeleteModel(BaseMutation):
+    def __init__(self, model_name):
+        self.model_name = model_name
+
+    def __str__(self):
+        return "DeleteModel(%r)" % self.model_name
+
+    def simulate(self, app_label, proj_sig):
+        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):
+        app_sig = proj_sig[app_label]
+        model_sig = app_sig[self.model_name]
+
+        sql_statements = []
+        model = MockModel(proj_sig, app_label, self.model_name, model_sig)
+        # Remove any many to many tables.
+        for field_name, field_sig in model_sig['fields'].items():
+            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)
+        # Remove the table itself.
+        sql_statements += evolver.delete_table(model._meta.db_table)
+
+        return sql_statements
+
+class DeleteApplication(BaseMutation):
+    def __str__(self):
+        return 'DeleteApplication()'
+
+    def simulate(self, app_label, proj_sig):
+        del proj_sig[app_label]
+
+    def mutate(self, app_label, proj_sig):
+        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))
+        return sql_statements
diff --git a/lib/django_evolution/signature.py b/lib/django_evolution/signature.py
new file mode 100644
index 0000000..2c275c9
--- /dev/null
+++ b/lib/django_evolution/signature.py
@@ -0,0 +1,94 @@
+from django.db.models import get_apps, get_models
+from django.db.models.fields.related import *
+from django.conf import global_settings
+from django.contrib.contenttypes import generic
+
+ATTRIBUTE_DEFAULTS = {
+    # Common to all fields
+    'primary_key': False,
+    'max_length' : None,
+    'unique' : False,
+    'null' : False,
+    'db_index' : False,
+    'db_column' : None,
+    'db_tablespace' : global_settings.DEFAULT_TABLESPACE,
+    'rel': None,
+    # Decimal Field
+    'max_digits' : None,
+    'decimal_places' : None,
+    # ManyToManyField
+    'db_table': None
+}
+
+# r7790 modified the unique attribute of the meta model to be
+# a property that combined an underlying _unique attribute with
+# the primary key attribute. We need the underlying property, 
+# but we don't want to affect old signatures (plus the
+# underscore is ugly :-).
+ATTRIBUTE_ALIASES = {
+    'unique': '_unique'
+}
+
+def create_field_sig(field):
+    field_sig = {
+        'field_type': field.__class__,
+    }
+        
+    for attrib in ATTRIBUTE_DEFAULTS.keys():
+        alias = ATTRIBUTE_ALIASES.get(attrib, attrib)
+        if hasattr(field,alias):
+            value = getattr(field,alias)
+            if isinstance(field, ForeignKey):
+                if attrib == 'db_index':
+                    default = True
+                else:
+                    default = ATTRIBUTE_DEFAULTS[attrib]
+            else:
+                default = ATTRIBUTE_DEFAULTS[attrib]
+            # only store non-default values
+            if default != value:
+                field_sig[attrib] = value
+                
+    rel = field_sig.pop('rel', None)
+    if rel:
+        field_sig['related_model'] = '.'.join([rel.to._meta.app_label, rel.to._meta.object_name])
+    return field_sig
+    
+def create_model_sig(model):
+    model_sig = {
+        'meta': {
+            'unique_together': model._meta.unique_together,
+            'db_tablespace': model._meta.db_tablespace,
+            'db_table': model._meta.db_table,
+            'pk_column': model._meta.pk.column,
+        },
+        'fields': {},
+    }
+
+    for field in model._meta.local_fields + model._meta.local_many_to_many:
+        # Special case - don't generate a signature for generic relations
+        if not isinstance(field, generic.GenericRelation):
+            model_sig['fields'][field.name] = create_field_sig(field)
+    return model_sig
+    
+def create_app_sig(app):
+    """
+    Creates a dictionary representation of the models in a given app.
+    Only those attributes that are interesting from a schema-evolution
+    perspective are included.
+    """
+    app_sig = {}
+    for model in get_models(app):
+        app_sig[model._meta.object_name] = create_model_sig(model)
+    return app_sig    
+
+def create_project_sig():
+    """
+    Create a dictionary representation of the apps in a given project.
+    """
+    proj_sig = {
+        '__version__': 1,
+    }
+    for app in get_apps():
+        proj_sig[app.__name__.split('.')[-2]] = create_app_sig(app)
+    return proj_sig
diff --git a/lib/django_evolution/tests/__init__.py b/lib/django_evolution/tests/__init__.py
new file mode 100644
index 0000000..7d843b9
--- /dev/null
+++ b/lib/django_evolution/tests/__init__.py
@@ -0,0 +1,27 @@
+import unittest
+
+from signature import tests as signature_tests
+from add_field import tests as add_field_tests
+from delete_field import tests as delete_field_tests
+from delete_model import tests as delete_model_tests
+from delete_app import tests as delete_app_tests
+from rename_field import tests as rename_field_tests
+from change_field import tests as change_field_tests
+from sql_mutation import tests as sql_mutation_tests
+from ordering import tests as ordering_tests
+from generics import tests as generics_tests
+from inheritance import tests as inheritance_tests
+# Define doctests
+__test__ = {
+    'signature': signature_tests,
+    'add_field': add_field_tests,
+    'delete_field': delete_field_tests,
+    'delete_model': delete_model_tests,
+    'delete_app': delete_app_tests,
+    'rename_field': rename_field_tests,
+    'change_field': change_field_tests,
+    'sql_mutation': sql_mutation_tests,
+    'ordering': ordering_tests,
+    'generics': generics_tests,
+    'inheritance': inheritance_tests
+}
diff --git a/lib/django_evolution/tests/add_field.py b/lib/django_evolution/tests/add_field.py
new file mode 100644
index 0000000..9988dd1
--- /dev/null
+++ b/lib/django_evolution/tests/add_field.py
@@ -0,0 +1,551 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+# The AddField tests will aim to test the following usecases:
+# Field resulting in a new database column.
+# Field resulting in a new database column with a non-default name.
+# Field resulting in a new database column in a table with a non-default name.
+# Primary key field.
+# Indexed field
+# Unique field.
+# Null field
+# 
+# Foreign Key field.
+# M2M field between models with default table names.
+# M2M field between models with non-default table names.
+# M2M field between self
+>>> from datetime import datetime
+
+>>> from django.db import models
+
+>>> from django_evolution.mutations import AddField, DeleteField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django_evolution import signature
+>>> from django_evolution import models as test_app
+
+>>> import copy
+
+>>> class AddSequenceFieldInitial(object):
+...     def __init__(self, suffix):
+...         self.suffix = suffix
+...
+...     def __call__(self):
+...         from django.db import connection
+...         qn = connection.ops.quote_name
+...         return qn('int_field')
+
+>>> class AddAnchor1(models.Model):
+...     value = models.IntegerField()
+
+>>> class AddAnchor2(models.Model):
+...     value = models.IntegerField()
+...     class Meta:
+...         db_table = 'custom_add_anchor_table'
+
+>>> class AddBaseModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+
+>>> class CustomTableModel(models.Model):
+...     value = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'custom_table_name'
+
+# Store the base signatures
+>>> anchors = (
+...     ('AddAnchor1', AddAnchor1),
+...     ('AddAnchor2', AddAnchor2)
+... )
+
+>>> custom_model = ('CustomTableModel', CustomTableModel)
+>>> custom = register_models(custom_model)
+>>> custom_table_sig = test_proj_sig(custom_model)
+
+>>> test_model = ('TestModel', AddBaseModel)
+>>> start = register_models(*anchors)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(test_model, *anchors)
+
+# Add non-null field with non-callable initial value
+>>> class AddNonNullColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField()
+
+>>> end = register_models(('TestModel', AddNonNullColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddNonNullColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] #AddNonNullColumnModel
+["AddField('TestModel', 'added_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>)"]
+
+# Evolution won't run as-is
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+EvolutionException: Cannot use hinted evolution: AddField or ChangeField mutation for 'TestModel.added_field' in 'tests' requires user-specified initial value.
+
+# First try without an initial value. This will fail
+>>> evolution = [AddField('TestModel', 'added_field', models.IntegerField)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot create new column 'added_field' on 'tests.TestModel' without a non-null initial value.
+
+# Now try with an explicitly null initial value. This will also fail
+>>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=None)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot create new column 'added_field' on 'tests.TestModel' without a non-null initial value.
+
+# Now try with a good initial value
+>>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=1)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddNonNullNonCallableColumnModel
+%(AddNonNullNonCallableColumnModel)s
+
+# Now try with a good callable initial value
+>>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=AddSequenceFieldInitial('AddNonNullCallableColumnModel'))]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddNonNullCallableColumnModel
+%(AddNonNullCallableColumnModel)s
+
+# Add nullable column with initial data
+>>> class AddNullColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(null=True)
+
+>>> end = register_models(('TestModel',AddNullColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddNullColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] #AddNullColumnModel
+["AddField('TestModel', 'added_field', models.IntegerField, null=True)"]
+
+>>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=1, null=True)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddNullColumnWithInitialColumnModel
+%(AddNullColumnWithInitialColumnModel)s
+
+# Add a field that requires string-form initial data
+>>> class AddStringColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.CharField(max_length=10)
+
+>>> end = register_models(('TestModel',AddStringColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddStringColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] # AddStringColumnModel
+["AddField('TestModel', 'added_field', models.CharField, initial=<<USER VALUE REQUIRED>>, max_length=10)"]
+
+>>> evolution = [AddField('TestModel', 'added_field', models.CharField, initial="abc's xyz", max_length=10)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddStringColumnModel
+%(AddStringColumnModel)s
+
+# Add a string field that allows empty strings as initial values
+>>> class AddBlankStringColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.CharField(max_length=10, blank=True)
+
+>>> end = register_models(('TestModel',AddBlankStringColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddBlankStringColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] # AddBlankStringColumnModel
+["AddField('TestModel', 'added_field', models.CharField, initial='', max_length=10)"]
+
+# Add a field that requires date-form initial data
+>>> class AddDateColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.DateTimeField()
+
+>>> end = register_models(('TestModel',AddDateColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddDateColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] # AddDateColumnModel
+["AddField('TestModel', 'added_field', models.DateTimeField, initial=<<USER VALUE REQUIRED>>)"]
+
+>>> new_date = datetime(2007,12,13,16,42,0)
+>>> evolution = [AddField('TestModel', 'added_field', models.DateTimeField, initial=new_date)]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddDateColumnModel
+%(AddDateColumnModel)s
+
+# Add column with default value
+>>> class AddDefaultColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(default=42)
+
+>>> end = register_models(('TestModel',AddDefaultColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddDefaultColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] #AddDefaultColumnModel
+["AddField('TestModel', 'added_field', models.IntegerField, initial=42)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddDefaultColumnModel
+%(AddDefaultColumnModel)s
+
+# Add column with an empty string as the default value
+>>> class AddEmptyStringDefaultColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.CharField(max_length=20, default='')
+
+>>> end = register_models(('TestModel',AddEmptyStringDefaultColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddEmptyStringDefaultColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] #AddEmptyStringDefaultColumnModel
+["AddField('TestModel', 'added_field', models.CharField, initial=u'', max_length=20)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddEmptyStringDefaultColumnModel
+%(AddEmptyStringDefaultColumnModel)s
+
+
+# Null field
+>>> class AddNullColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(null=True)
+
+>>> end = register_models(('TestModel', AddNullColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', AddNullColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] #AddNullColumnModel
+["AddField('TestModel', 'added_field', models.IntegerField, null=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddNullColumnModel
+%(AddNullColumnModel)s
+
+# Field resulting in a new database column with a non-default name.
+>>> class NonDefaultColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     add_field = models.IntegerField(db_column='non-default_column', null=True)
+
+>>> end = register_models(('TestModel',NonDefaultColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',NonDefaultColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'add_field', models.IntegerField, null=True, db_column='non-default_column')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #NonDefaultColumnModel
+%(NonDefaultColumnModel)s
+
+# Field resulting in a new database column in a table with a non-default name.
+>>> class AddColumnCustomTableModel(models.Model):
+...     value = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     added_field = models.IntegerField(null=True)
+...     class Meta:
+...         db_table = 'custom_table_name'
+
+>>> end = register_models(('CustomTableModel',AddColumnCustomTableModel))
+>>> end_sig = test_proj_sig(('CustomTableModel',AddColumnCustomTableModel))
+>>> d = Diff(custom_table_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('CustomTableModel', 'added_field', models.IntegerField, null=True)"]
+
+>>> test_sig = copy.deepcopy(custom_table_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(custom, end, test_sql) #AddColumnCustomTableModel
+%(AddColumnCustomTableModel)s
+
+# Add Primary key field.
+# Delete of old Primary Key is prohibited.
+>>> class AddPrimaryKeyModel(models.Model):
+...     my_primary_key = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+
+>>> end = register_models(('TestModel', AddPrimaryKeyModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddPrimaryKeyModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'my_primary_key', models.AutoField, initial=<<USER VALUE REQUIRED>>, primary_key=True)", "DeleteField('TestModel', 'id')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+
+>>> for mutation in [AddField('TestModel', 'my_primary_key', models.AutoField, initial=AddSequenceFieldInitial('AddPrimaryKeyModel'), primary_key=True), DeleteField('TestModel', 'id')]:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot delete a primary key.
+
+# Indexed field
+>>> class AddIndexedColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     add_field = models.IntegerField(db_index=True, null=True)
+
+>>> end = register_models(('TestModel',AddIndexedColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddIndexedColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'add_field', models.IntegerField, null=True, db_index=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql, debug=False) #AddIndexedColumnModel
+%(AddIndexedColumnModel)s
+
+# Unique field.
+>>> class AddUniqueColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(unique=True, null=True)
+
+>>> end = register_models(('TestModel',AddUniqueColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddUniqueColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.IntegerField, unique=True, null=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddUniqueColumnModel
+%(AddUniqueColumnModel)s
+
+# Unique indexed field.
+>>> class AddUniqueIndexedModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(unique=True, db_index=True, null=True)
+
+>>> end = register_models(('TestModel',AddUniqueIndexedModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddUniqueIndexedModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.IntegerField, unique=True, null=True, db_index=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddUniqueIndexedModel
+%(AddUniqueIndexedModel)s
+
+Foreign Key field.
+>>> class AddForeignKeyModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.ForeignKey(AddAnchor1, null=True)
+
+>>> end = register_models(('TestModel',AddForeignKeyModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddForeignKeyModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.ForeignKey, null=True, related_model='tests.AddAnchor1')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddForeignKeyModel
+%(AddForeignKeyModel)s
+
+# M2M field between models with default table names.
+>>> class AddM2MDatabaseTableModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.ManyToManyField(AddAnchor1)
+
+>>> end = register_models(('TestModel',AddM2MDatabaseTableModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',AddM2MDatabaseTableModel), *anchors)
+>>> end_sig['tests'][AddAnchor1.__name__] = signature.create_model_sig(AddAnchor1)
+>>> anchor_sig = copy.deepcopy(start_sig)
+>>> anchor_sig['tests'][AddAnchor1.__name__] = signature.create_model_sig(AddAnchor1)
+>>> d = Diff(anchor_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.AddAnchor1')"]
+
+>>> test_sig = copy.deepcopy(anchor_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddManyToManyDatabaseTableModel
+%(AddManyToManyDatabaseTableModel)s
+
+# M2M field between models with non-default table names.
+>>> class AddM2MNonDefaultDatabaseTableModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.ManyToManyField(AddAnchor2)
+
+>>> end = register_models(('TestModel', AddM2MNonDefaultDatabaseTableModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', AddM2MNonDefaultDatabaseTableModel), *anchors)
+>>> end_sig['tests'][AddAnchor2.__name__] = signature.create_model_sig(AddAnchor2)
+>>> anchor_sig = copy.deepcopy(start_sig)
+>>> anchor_sig['tests'][AddAnchor2.__name__] = signature.create_model_sig(AddAnchor2)
+>>> d = Diff(anchor_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.AddAnchor2')"]
+
+>>> test_sig = copy.deepcopy(anchor_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddManyToManyNonDefaultDatabaseTableModel
+%(AddManyToManyNonDefaultDatabaseTableModel)s
+
+# M2M field between self
+# Need to find a better way to do this.
+>>> end_sig = copy.deepcopy(start_sig)
+>>> end_sig['tests']['TestModel']['fields']['added_field'] = {'field_type': models.ManyToManyField,'related_model': 'tests.TestModel'}
+
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.TestModel')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #AddManyToManySelf
+%(AddManyToManySelf)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('add_field')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/change_field.py b/lib/django_evolution/tests/change_field.py
new file mode 100644
index 0000000..68b9c20
--- /dev/null
+++ b/lib/django_evolution/tests/change_field.py
@@ -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, execute_test_sql, register_models, 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 connection
+...         qn = connection.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='change_field_non-default_m2m_table')
+
+# Store the base signatures
+>>> anchors = [('ChangeAnchor1', ChangeAnchor1)]
+>>> test_model = ('TestModel', ChangeBaseModel)
+
+>>> start = register_models(*anchors)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', SetNotNullChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', SetNotNullChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'char_field1':
+        Property 'null' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # 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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot change column 'char_field1' on 'tests.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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+SimulationFailure: Cannot change column 'char_field1' on 'tests.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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql)
+%(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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql)
+%(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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', SetNullChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', SetNullChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'char_field2':
+        Property 'null' has changed
+    
+>>> print [str(e) for e in d.evolution()['tests']] # SetNullChangeModel
+["ChangeField('TestModel', 'char_field2', initial=None, null=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', NoOpChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', IncreasingMaxLengthChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', IncreasingMaxLengthChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'char_field':
+        Property 'max_length' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # IncreasingMaxLengthChangeModel
+["ChangeField('TestModel', 'char_field', initial=None, max_length=45)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', DecreasingMaxLengthChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', DecreasingMaxLengthChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'char_field':
+        Property 'max_length' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # DecreasingMaxLengthChangeModel
+["ChangeField('TestModel', 'char_field', initial=None, max_length=1)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', DBColumnChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', DBColumnChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'int_field':
+        Property 'db_column' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # 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()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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(('TestModel', M2MDBTableChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', M2MDBTableChangeModel), *anchors)
+
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'm2m_field1':
+        Property 'db_table' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # 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()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', AddDBIndexChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', AddDBIndexChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'int_field2':
+        Property 'db_index' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # AddDBIndexChangeModel
+["ChangeField('TestModel', 'int_field2', initial=None, db_index=True)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', RemoveDBIndexChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', RemoveDBIndexChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'int_field1':
+        Property 'db_index' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # RemoveDBIndexChangeModel
+["ChangeField('TestModel', 'int_field1', initial=None, db_index=False)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', AddUniqueChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', AddUniqueChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'int_field4':
+        Property 'unique' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # AddUniqueChangeModel
+["ChangeField('TestModel', 'int_field4', initial=None, unique=True)"]
+ 
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+ 
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', RemoveUniqueChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', RemoveUniqueChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'int_field3':
+        Property 'unique' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # RemoveUniqueChangeModel
+["ChangeField('TestModel', 'int_field3', initial=None, unique=False)"]
+ 
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+ 
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', MultiAttrChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', MultiAttrChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.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()['tests']] # 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()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+ 
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', MultiAttrSingleFieldChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', MultiAttrSingleFieldChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print d
+In model tests.TestModel:
+    In field 'char_field2':
+        Property 'max_length' has changed
+        Property 'null' has changed
+
+>>> print [str(e) for e in d.evolution()['tests']] # 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()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+ 
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', RedundantAttrsChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('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('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+ 
+>>> execute_test_sql(start, end, test_sql) # 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='change_field_non-default_m2m_table')
+
+>>> end = register_models(('TestModel', MinorFieldTypeChangeModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', MinorFieldTypeChangeModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+
+>>> d.is_empty()
+True
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('change_field')
diff --git a/registration/management/__init__.py b/lib/django_evolution/tests/db/__init__.py
similarity index 100%
rename from registration/management/__init__.py
rename to lib/django_evolution/tests/db/__init__.py
diff --git a/lib/django_evolution/tests/db/mysql.py b/lib/django_evolution/tests/db/mysql.py
new file mode 100644
index 0000000..8ae2069
--- /dev/null
+++ b/lib/django_evolution/tests/db/mysql.py
@@ -0,0 +1,254 @@
+add_field = {
+    'AddNonNullNonCallableColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer ;',
+            'UPDATE `tests_testmodel` SET `added_field` = 1 WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` integer NOT NULL;',
+        ]),
+    'AddNonNullCallableColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer ;',
+            'UPDATE `tests_testmodel` SET `added_field` = `int_field` WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` integer NOT NULL;',
+        ]),
+    'AddNullColumnWithInitialColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer ;',
+            'UPDATE `tests_testmodel` SET `added_field` = 1 WHERE `added_field` IS NULL;',
+        ]),
+    'AddStringColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` varchar(10) ;',
+            'UPDATE `tests_testmodel` SET `added_field` = \'abc\\\'s xyz\' WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` varchar(10) NOT NULL;',
+        ]),
+    'AddDateColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` datetime ;',
+            'UPDATE `tests_testmodel` SET `added_field` = 2007-12-13 16:42:00 WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` datetime NOT NULL;',
+        ]),    
+    'AddDefaultColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer ;',
+            'UPDATE `tests_testmodel` SET `added_field` = 42 WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` integer NOT NULL;',
+        ]),
+    'AddEmptyStringDefaultColumnModel':
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` varchar(20) ;',
+            'UPDATE `tests_testmodel` SET `added_field` = \'\' WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `added_field` varchar(20) NOT NULL;',
+        ]),
+    'AddNullColumnModel': 
+        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer NULL ;',
+    'NonDefaultColumnModel': 
+        'ALTER TABLE `tests_testmodel` ADD COLUMN `non-default_column` integer NULL ;',
+    'AddColumnCustomTableModel':  
+        'ALTER TABLE `custom_table_name` ADD COLUMN `added_field` integer NULL ;',
+    'AddIndexedColumnModel': 
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `add_field` integer NULL ;',
+            'CREATE INDEX `tests_testmodel_add_field` ON `tests_testmodel` (`add_field`);'
+        ]),
+    'AddUniqueColumnModel': 
+        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer NULL UNIQUE;',
+    'AddUniqueIndexedModel': 
+        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field` integer NULL UNIQUE;',
+    'AddForeignKeyModel': 
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field_id` integer NULL REFERENCES `tests_addanchor1` (`id`) ;',
+            'CREATE INDEX `tests_testmodel_added_field_id` ON `tests_testmodel` (`added_field_id`);'
+        ]),
+    'AddManyToManyDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `testmodel_id` integer NOT NULL,',
+            '    `addanchor1_id` integer NOT NULL,',
+            '    UNIQUE (`testmodel_id`, `addanchor1_id`)',
+            ')',
+            ';',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT testmodel_id_refs_id_12ea61cd FOREIGN KEY (`testmodel_id`) REFERENCES `tests_testmodel` (`id`);',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT addanchor1_id_refs_id_7efbb240 FOREIGN KEY (`addanchor1_id`) REFERENCES `tests_addanchor1` (`id`);'            
+        ]),
+     'AddManyToManyNonDefaultDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `testmodel_id` integer NOT NULL,',
+            '    `addanchor2_id` integer NOT NULL,',
+            '    UNIQUE (`testmodel_id`, `addanchor2_id`)',
+            ')',
+            ';',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT testmodel_id_refs_id_12ea61cd FOREIGN KEY (`testmodel_id`) REFERENCES `tests_testmodel` (`id`);',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT addanchor2_id_refs_id_13c1da78 FOREIGN KEY (`addanchor2_id`) REFERENCES `custom_add_anchor_table` (`id`);'
+        ]),
+     'AddManyToManySelf': 
+        '\n'.join([
+            'CREATE TABLE `tests_testmodel_added_field` (',
+            '    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,',
+            '    `from_testmodel_id` integer NOT NULL,',
+            '    `to_testmodel_id` integer NOT NULL,',
+            '    UNIQUE (`from_testmodel_id`, `to_testmodel_id`)',
+            ')',
+            ';',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT from_testmodel_id_refs_id_12ea61cd FOREIGN KEY (`from_testmodel_id`) REFERENCES `tests_testmodel` (`id`);',
+            'ALTER TABLE `tests_testmodel_added_field` ADD CONSTRAINT to_testmodel_id_refs_id_12ea61cd FOREIGN KEY (`to_testmodel_id`) REFERENCES `tests_testmodel` (`id`);'
+        ]),
+}
+
+delete_field = {
+    'DefaultNamedColumnModel': 
+        'ALTER TABLE `tests_testmodel` DROP COLUMN `int_field` CASCADE;',
+    'NonDefaultNamedColumnModel': 
+        'ALTER TABLE `tests_testmodel` DROP COLUMN `non-default_db_column` CASCADE;',
+    'ConstrainedColumnModel': 
+        'ALTER TABLE `tests_testmodel` DROP COLUMN `int_field3` CASCADE;',
+    'DefaultManyToManyModel': 
+        'DROP TABLE `tests_testmodel_m2m_field1`;',
+    'NonDefaultManyToManyModel': 
+        'DROP TABLE `non-default_m2m_table`;',
+    'DeleteForeignKeyModel': 
+        'ALTER TABLE `tests_testmodel` DROP COLUMN `fk_field1_id` CASCADE;',
+    'DeleteColumnCustomTableModel': 
+        'ALTER TABLE `custom_table_name` DROP COLUMN `value` CASCADE;',
+}
+
+change_field = {
+    "SetNotNullChangeModelWithConstant":
+        '\n'.join([
+            'UPDATE `tests_testmodel` SET `char_field1` = \'abc\\\'s xyz\' WHERE `char_field1` IS NULL;',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field1` varchar(25) NOT NULL;',
+        ]),
+    "SetNotNullChangeModelWithCallable":
+            '\n'.join([
+                'UPDATE `tests_testmodel` SET `char_field1` = `char_field` WHERE `char_field1` IS NULL;',
+                'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field1` varchar(25) NOT NULL;',
+            ]),
+    "SetNullChangeModel": 'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field2` varchar(30) DEFAULT NULL;',
+    "NoOpChangeModel": '',
+    'IncreasingMaxLengthChangeModel':
+            '\n'.join([
+                'UPDATE `tests_testmodel` SET `char_field`=LEFT(`char_field`,45);',
+                'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field` varchar(45);',
+            ]),
+    'DecreasingMaxLengthChangeModel':  
+            '\n'.join([
+                'UPDATE `tests_testmodel` SET `char_field`=LEFT(`char_field`,1);',
+                'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field` varchar(1);',
+            ]),
+    "DBColumnChangeModel": 'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_column` `customised_db_column` integer NOT NULL;',
+    "M2MDBTableChangeModel": 'RENAME TABLE `change_field_non-default_m2m_table` TO `custom_m2m_db_table_name`;',
+    "AddDBIndexChangeModel": 'CREATE INDEX `tests_testmodel_int_field2` ON `tests_testmodel` (`int_field2`);',
+    "RemoveDBIndexChangeModel": 'DROP INDEX `tests_testmodel_int_field1` ON `tests_testmodel`;',
+    "AddUniqueChangeModel": 'CREATE UNIQUE INDEX int_field4 ON `tests_testmodel`(`int_field4`);',
+    "RemoveUniqueChangeModel": 'DROP INDEX int_field3 ON `tests_testmodel`;',
+    "MultiAttrChangeModel": 
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field2` varchar(30) DEFAULT NULL;',
+            'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_column` `custom_db_column2` integer NOT NULL;',
+            'UPDATE `tests_testmodel` SET `char_field`=LEFT(`char_field`,35);',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field` varchar(35);',
+        ]),
+    "MultiAttrSingleFieldChangeModel": 
+        '\n'.join([
+            'UPDATE `tests_testmodel` SET `char_field2`=LEFT(`char_field2`,35);',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field2` varchar(35);',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field2` varchar(35) DEFAULT NULL;',
+        ]),
+    "RedundantAttrsChangeModel":
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field2` varchar(30) DEFAULT NULL;',
+            'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_column` `custom_db_column3` integer NOT NULL;',
+            'UPDATE `tests_testmodel` SET `char_field`=LEFT(`char_field`,35);',
+            'ALTER TABLE `tests_testmodel` MODIFY COLUMN `char_field` varchar(35);',
+        ]),
+}
+
+delete_model = {
+    'BasicModel': 
+        'DROP TABLE `tests_basicmodel`;',
+    'BasicWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE `tests_basicwithm2mmodel_m2m`;',
+            'DROP TABLE `tests_basicwithm2mmodel`;'
+        ]),
+    'CustomTableModel': 
+        'DROP TABLE `custom_table_name`;',
+    'CustomTableWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE `another_custom_table_name_m2m`;',
+            'DROP TABLE `another_custom_table_name`;'
+        ]),
+}
+
+delete_application = {
+    'DeleteApplication':
+        '\n'.join([
+            'DROP TABLE `tests_appdeleteanchor1`;',
+            'DROP TABLE `tests_testmodel_anchor_m2m`;',
+            'DROP TABLE `tests_testmodel`;',
+            'DROP TABLE `app_delete_custom_add_anchor_table`;',
+            'DROP TABLE `app_delete_custom_table_name`;',
+        ]),
+}
+
+rename_field = {
+    'RenameColumnModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `int_field` `renamed_field` integer NOT NULL;',
+    'RenameColumnWithTableNameModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `int_field` `renamed_field` integer NOT NULL;',
+    'RenamePrimaryKeyColumnModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `id` `my_pk_id`;',
+    'RenameForeignKeyColumnModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `fk_field_id` `renamed_field_id` integer NOT NULL;',
+    'RenameNonDefaultColumnNameModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_col_name` `renamed_field` integer NOT NULL;',
+    'RenameNonDefaultColumnNameToNonDefaultNameModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_col_name` `non-default_column_name` integer NOT NULL;',
+    'RenameNonDefaultColumnNameToNonDefaultNameAndTableModel': 
+        'ALTER TABLE `tests_testmodel` CHANGE COLUMN `custom_db_col_name` `non-default_column_name2` integer NOT NULL;',
+    'RenameColumnCustomTableModel': 
+        'ALTER TABLE `custom_rename_table_name` CHANGE COLUMN `value` `renamed_field` integer NOT NULL;',
+    'RenameManyToManyTableModel': 
+        'ALTER TABLE `tests_testmodel_m2m_field` RENAME TO `tests_testmodel_renamed_field`;',
+    'RenameManyToManyTableWithColumnNameModel': 
+        'ALTER TABLE `tests_testmodel_m2m_field` RENAME TO `tests_testmodel_renamed_field`;',
+    'RenameNonDefaultManyToManyTableModel': 
+        'ALTER TABLE `non-default_db_table` RENAME TO `tests_testmodel_renamed_field`;',
+}
+
+
+sql_mutation = {
+    'SQLMutationSequence': """[
+...    SQLMutation('first-two-fields', [
+...        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field1` integer NULL;',
+...        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field2` integer NULL;'
+...    ], update_first_two),
+...    SQLMutation('third-field', [
+...        'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field3` integer NULL;',
+...    ], update_third)]
+""",
+    'SQLMutationOutput': 
+        '\n'.join([
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field1` integer NULL;',
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field2` integer NULL;',
+            'ALTER TABLE `tests_testmodel` ADD COLUMN `added_field3` integer NULL;',
+        ]),
+}
+
+generics = {
+    'DeleteColumnModel': "ALTER TABLE `tests_testmodel` DROP COLUMN `char_field` CASCADE;"    
+}
+
+inheritance = {
+    'AddToChildModel': 
+        '\n'.join([
+            'ALTER TABLE `tests_childmodel` ADD COLUMN `added_field` integer ;',
+            'UPDATE `tests_childmodel` SET `added_field` = 42 WHERE `added_field` IS NULL;',
+            'ALTER TABLE `tests_childmodel` MODIFY COLUMN `added_field` integer NOT NULL;',
+        ]),
+    'DeleteFromChildModel': 
+        'ALTER TABLE `tests_childmodel` DROP COLUMN `int_field` CASCADE;',
+}
diff --git a/lib/django_evolution/tests/db/mysql_old.py b/lib/django_evolution/tests/db/mysql_old.py
new file mode 100644
index 0000000..505a6b8
--- /dev/null
+++ b/lib/django_evolution/tests/db/mysql_old.py
@@ -0,0 +1,2 @@
+# MySQL_old behaviour is identical to mysql base
+from mysql import *
\ No newline at end of file
diff --git a/lib/django_evolution/tests/db/postgresql.py b/lib/django_evolution/tests/db/postgresql.py
new file mode 100644
index 0000000..4e6e30f
--- /dev/null
+++ b/lib/django_evolution/tests/db/postgresql.py
@@ -0,0 +1,236 @@
+add_field = {
+    'AddNonNullNonCallableColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer ;',
+            'UPDATE "tests_testmodel" SET "added_field" = 1 WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddNonNullCallableColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer ;',
+            'UPDATE "tests_testmodel" SET "added_field" = "int_field" WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddNullColumnWithInitialColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer ;',
+            'UPDATE "tests_testmodel" SET "added_field" = 1 WHERE "added_field" IS NULL;',
+        ]),
+    'AddStringColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" varchar(10) ;',
+            'UPDATE "tests_testmodel" SET "added_field" = \'abc\\\'s xyz\' WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddDateColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" timestamp with time zone ;',
+            'UPDATE "tests_testmodel" SET "added_field" = 2007-12-13 16:42:00 WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddDefaultColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer ;',
+            'UPDATE "tests_testmodel" SET "added_field" = 42 WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddEmptyStringDefaultColumnModel':
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" varchar(20) ;',
+            'UPDATE "tests_testmodel" SET "added_field" = \'\' WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'AddNullColumnModel': 
+        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer NULL ;',
+    'NonDefaultColumnModel': 
+        'ALTER TABLE "tests_testmodel" ADD COLUMN "non-default_column" integer NULL ;',
+    'AddColumnCustomTableModel': 
+        'ALTER TABLE "custom_table_name" ADD COLUMN "added_field" integer NULL ;',
+    'AddIndexedColumnModel': 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "add_field" integer NULL ;',
+            'CREATE INDEX "tests_testmodel_add_field" ON "tests_testmodel" ("add_field");'
+        ]),
+    'AddUniqueColumnModel': 
+        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer NULL UNIQUE;',
+    'AddUniqueIndexedModel': 
+        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field" integer NULL UNIQUE;',
+    'AddForeignKeyModel': 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field_id" integer NULL REFERENCES "tests_addanchor1" ("id")  DEFERRABLE INITIALLY DEFERRED;',
+            'CREATE INDEX "tests_testmodel_added_field_id" ON "tests_testmodel" ("added_field_id");'
+        ]),
+    'AddManyToManyDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    "addanchor1_id" integer NOT NULL REFERENCES "tests_addanchor1" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    UNIQUE ("testmodel_id", "addanchor1_id")',
+            ')',
+            ';'
+        ]),
+     'AddManyToManyNonDefaultDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    "addanchor2_id" integer NOT NULL REFERENCES "custom_add_anchor_table" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    UNIQUE ("testmodel_id", "addanchor2_id")',
+            ')',
+            ';'
+        ]),
+     'AddManyToManySelf': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" serial NOT NULL PRIMARY KEY,',
+            '    "from_testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    "to_testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id") DEFERRABLE INITIALLY DEFERRED,',
+            '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
+            ')',
+            ';'
+        ]),
+}
+
+delete_field = {
+    'DefaultNamedColumnModel': 
+        'ALTER TABLE "tests_testmodel" DROP COLUMN "int_field" CASCADE;',
+    'NonDefaultNamedColumnModel': 
+        'ALTER TABLE "tests_testmodel" DROP COLUMN "non-default_db_column" CASCADE;',
+    'ConstrainedColumnModel': 
+        'ALTER TABLE "tests_testmodel" DROP COLUMN "int_field3" CASCADE;',
+    'DefaultManyToManyModel': 
+        'DROP TABLE "tests_testmodel_m2m_field1";',
+    'NonDefaultManyToManyModel': 
+        'DROP TABLE "non-default_m2m_table";',
+    'DeleteForeignKeyModel': 
+        'ALTER TABLE "tests_testmodel" DROP COLUMN "fk_field1_id" CASCADE;',
+    'DeleteColumnCustomTableModel': 
+        'ALTER TABLE "custom_table_name" DROP COLUMN "value" CASCADE;',
+}
+
+change_field = {
+    "SetNotNullChangeModelWithConstant":
+        '\n'.join([
+            'UPDATE "tests_testmodel" SET "char_field1" = \'abc\\\'s xyz\' WHERE "char_field1" IS NULL;',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field1" SET NOT NULL;',
+        ]),
+    "SetNotNullChangeModelWithCallable":
+            '\n'.join([
+                'UPDATE "tests_testmodel" SET "char_field1" = "char_field" WHERE "char_field1" IS NULL;',
+                'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field1" SET NOT NULL;',
+            ]),
+    "SetNullChangeModel": 'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field2" DROP NOT NULL;',
+    "NoOpChangeModel": '',
+    "IncreasingMaxLengthChangeModel": 'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field" TYPE varchar(45) USING CAST("char_field" as varchar(45));',
+    "DecreasingMaxLengthChangeModel": 'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field" TYPE varchar(1) USING CAST("char_field" as varchar(1));',
+    "DBColumnChangeModel": 'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_column" TO "customised_db_column";',
+    "M2MDBTableChangeModel": 'ALTER TABLE "change_field_non-default_m2m_table" RENAME TO "custom_m2m_db_table_name";',
+    "AddDBIndexChangeModel": 'CREATE INDEX "tests_testmodel_int_field2" ON "tests_testmodel" ("int_field2");',
+    "RemoveDBIndexChangeModel": 'DROP INDEX "tests_testmodel_int_field1";',
+    "AddUniqueChangeModel": 'ALTER TABLE "tests_testmodel" ADD CONSTRAINT tests_testmodel_int_field4_key UNIQUE("int_field4");',
+    "RemoveUniqueChangeModel": 'ALTER TABLE "tests_testmodel" DROP CONSTRAINT tests_testmodel_int_field3_key;',
+    "MultiAttrChangeModel": 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field2" DROP NOT NULL;',
+            'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_column" TO "custom_db_column2";',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field" TYPE varchar(35) USING CAST("char_field" as varchar(35));',
+        ]),
+    "MultiAttrSingleFieldChangeModel": 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field2" TYPE varchar(35) USING CAST("char_field2" as varchar(35));',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field2" DROP NOT NULL;',
+        ]),
+    "RedundantAttrsChangeModel":
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field2" DROP NOT NULL;',
+            'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_column" TO "custom_db_column3";',
+            'ALTER TABLE "tests_testmodel" ALTER COLUMN "char_field" TYPE varchar(35) USING CAST("char_field" as varchar(35));',
+        ]),
+}
+
+delete_model = {
+    'BasicModel': 
+        'DROP TABLE "tests_basicmodel";',
+    'BasicWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE "tests_basicwithm2mmodel_m2m";',
+            'DROP TABLE "tests_basicwithm2mmodel";'
+        ]),
+    'CustomTableModel': 
+        'DROP TABLE "custom_table_name";',
+    'CustomTableWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE "another_custom_table_name_m2m";',
+            'DROP TABLE "another_custom_table_name";'
+        ]),
+}
+
+delete_application = {
+    'DeleteApplication':
+        '\n'.join([
+            'DROP TABLE "tests_appdeleteanchor1";',
+            'DROP TABLE "tests_testmodel_anchor_m2m";',
+            'DROP TABLE "tests_testmodel";',
+            'DROP TABLE "app_delete_custom_add_anchor_table";',
+            'DROP TABLE "app_delete_custom_table_name";',
+        ]),
+}
+
+rename_field = {
+    'RenameColumnModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "int_field" TO "renamed_field";',
+    'RenameColumnWithTableNameModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "int_field" TO "renamed_field";',
+    'RenamePrimaryKeyColumnModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "id" TO "my_pk_id";',
+    'RenameForeignKeyColumnModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "fk_field_id" TO "renamed_field_id";',
+    'RenameNonDefaultColumnNameModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_col_name" TO "renamed_field";',
+    'RenameNonDefaultColumnNameToNonDefaultNameModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_col_name" TO "non-default_column_name";',
+    'RenameNonDefaultColumnNameToNonDefaultNameAndTableModel': 
+        'ALTER TABLE "tests_testmodel" RENAME COLUMN "custom_db_col_name" TO "non-default_column_name2";',
+    'RenameColumnCustomTableModel': 
+        'ALTER TABLE "custom_rename_table_name" RENAME COLUMN "value" TO "renamed_field";',
+    'RenameManyToManyTableModel': 
+        'ALTER TABLE "tests_testmodel_m2m_field" RENAME TO "tests_testmodel_renamed_field";',
+    'RenameManyToManyTableWithColumnNameModel': 
+        'ALTER TABLE "tests_testmodel_m2m_field" RENAME TO "tests_testmodel_renamed_field";',
+    'RenameNonDefaultManyToManyTableModel': 
+        'ALTER TABLE "non-default_db_table" RENAME TO "tests_testmodel_renamed_field";',
+}
+
+sql_mutation = {
+    'SQLMutationSequence': """[
+...    SQLMutation('first-two-fields', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field1" integer NULL;',
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field2" integer NULL;'
+...    ], update_first_two),
+...    SQLMutation('third-field', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field3" integer NULL;',
+...    ], update_third)]
+""",
+    'SQLMutationOutput': 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field1" integer NULL;',
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field2" integer NULL;',
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field3" integer NULL;',
+        ]),
+}
+
+generics = {
+    'DeleteColumnModel': 'ALTER TABLE "tests_testmodel" DROP COLUMN "char_field" CASCADE;'    
+}
+
+inheritance = {
+    'AddToChildModel': 
+        '\n'.join([
+            'ALTER TABLE "tests_childmodel" ADD COLUMN "added_field" integer ;',
+            'UPDATE "tests_childmodel" SET "added_field" = 42 WHERE "added_field" IS NULL;',
+            'ALTER TABLE "tests_childmodel" ALTER COLUMN "added_field" SET NOT NULL;',
+        ]),
+    'DeleteFromChildModel': 
+        'ALTER TABLE "tests_childmodel" DROP COLUMN "int_field" CASCADE;',
+}
diff --git a/lib/django_evolution/tests/db/postgresql_psycopg2.py b/lib/django_evolution/tests/db/postgresql_psycopg2.py
new file mode 100644
index 0000000..7557d22
--- /dev/null
+++ b/lib/django_evolution/tests/db/postgresql_psycopg2.py
@@ -0,0 +1,2 @@
+# Psycopg2 behaviour is identical to Psycopg1
+from postgresql import *
\ No newline at end of file
diff --git a/lib/django_evolution/tests/db/sqlite3.py b/lib/django_evolution/tests/db/sqlite3.py
new file mode 100644
index 0000000..0edeeb1
--- /dev/null
+++ b/lib/django_evolution/tests/db/sqlite3.py
@@ -0,0 +1,540 @@
+add_field = {
+    'AddNonNullNonCallableColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = 1;',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddNonNullCallableColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = "int_field";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddNullColumnWithInitialColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = 1;',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddStringColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" varchar(10) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = \'abc\\\'s xyz\';',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" varchar(10) NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddDateColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" datetime NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = 2007-12-13 16:42:00;',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" datetime NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddDefaultColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = 42;',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddEmptyStringDefaultColumnModel':
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" varchar(20) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = \'\';',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" varchar(20) NOT NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddNullColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'NonDefaultColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "non-default_column" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "non-default_column" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "non-default_column" integer NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "non-default_column") SELECT "int_field", "id", "char_field", "non-default_column" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddColumnCustomTableModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("id" integer NOT NULL UNIQUE PRIMARY KEY, "value" integer NOT NULL, "alt_value" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "id", "value", "alt_value", "added_field" FROM "custom_table_name";',
+            'DROP TABLE "custom_table_name";',
+            'CREATE TABLE "custom_table_name"("id" integer NOT NULL UNIQUE PRIMARY KEY, "value" integer NOT NULL, "alt_value" varchar(20) NOT NULL, "added_field" integer NULL);',
+            'INSERT INTO "custom_table_name" ("id", "value", "alt_value", "added_field") SELECT "id", "value", "alt_value", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddIndexedColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "add_field" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "add_field" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "add_field" integer NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "add_field") SELECT "int_field", "id", "char_field", "add_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE INDEX "tests_testmodel_add_field" ON "tests_testmodel" ("add_field");',
+        ]),
+    'AddUniqueColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL UNIQUE);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL UNIQUE);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddUniqueIndexedModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL UNIQUE);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NULL UNIQUE);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'AddForeignKeyModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field_id" integer NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field_id" integer NULL);',
+            'INSERT INTO "tests_testmodel" ("int_field", "id", "char_field", "added_field_id") SELECT "int_field", "id", "char_field", "added_field_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+            'CREATE INDEX "tests_testmodel_added_field_id" ON "tests_testmodel" ("added_field_id");',
+        ]),
+    'AddManyToManyDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id"),',
+            '    "addanchor1_id" integer NOT NULL REFERENCES "tests_addanchor1" ("id"),',
+            '    UNIQUE ("testmodel_id", "addanchor1_id")',
+            ')',
+            ';',
+        ]),
+     'AddManyToManyNonDefaultDatabaseTableModel': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY,',
+            '    "testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id"),',
+            '    "addanchor2_id" integer NOT NULL REFERENCES "custom_add_anchor_table" ("id"),',
+            '    UNIQUE ("testmodel_id", "addanchor2_id")',
+            ')',
+            ';',
+        ]),
+     'AddManyToManySelf': 
+        '\n'.join([
+            'CREATE TABLE "tests_testmodel_added_field" (',
+            '    "id" integer NOT NULL PRIMARY KEY,',
+            '    "from_testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id"),',
+            '    "to_testmodel_id" integer NOT NULL REFERENCES "tests_testmodel" ("id"),',
+            '    UNIQUE ("from_testmodel_id", "to_testmodel_id")',
+            ')',
+            ';',
+        ]),
+}
+
+delete_field = {
+    'DefaultNamedColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("non-default_db_column" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "non-default_db_column", "int_field3", "fk_field1_id", "char_field", "my_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("non-default_db_column" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_fk_field1_id" ON "tests_testmodel" ("fk_field1_id");',
+            'INSERT INTO "tests_testmodel" ("non-default_db_column", "int_field3", "fk_field1_id", "char_field", "my_id") SELECT "non-default_db_column", "int_field3", "fk_field1_id", "char_field", "my_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'NonDefaultNamedColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "int_field3", "fk_field1_id", "char_field", "my_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_fk_field1_id" ON "tests_testmodel" ("fk_field1_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "int_field3", "fk_field1_id", "char_field", "my_id") SELECT "int_field", "int_field3", "fk_field1_id", "char_field", "my_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'ConstrainedColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "non-default_db_column" integer NOT NULL, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "non-default_db_column", "fk_field1_id", "char_field", "my_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "non-default_db_column" integer NOT NULL, "fk_field1_id" integer NOT NULL, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_fk_field1_id" ON "tests_testmodel" ("fk_field1_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "non-default_db_column", "fk_field1_id", "char_field", "my_id") SELECT "int_field", "non-default_db_column", "fk_field1_id", "char_field", "my_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'DefaultManyToManyModel': 
+        'DROP TABLE "tests_testmodel_m2m_field1";',
+    'NonDefaultManyToManyModel': 
+        'DROP TABLE "non-default_m2m_table";',
+    'DeleteForeignKeyModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "non-default_db_column" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "non-default_db_column", "int_field3", "char_field", "my_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "non-default_db_column" integer NOT NULL, "int_field3" integer NOT NULL UNIQUE, "char_field" varchar(20) NOT NULL, "my_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "tests_testmodel" ("int_field", "non-default_db_column", "int_field3", "char_field", "my_id") SELECT "int_field", "non-default_db_column", "int_field3", "char_field", "my_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'DeleteColumnCustomTableModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("id" integer NOT NULL UNIQUE PRIMARY KEY, "alt_value" varchar(20) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "id", "alt_value" FROM "custom_table_name";',
+            'DROP TABLE "custom_table_name";',
+            'CREATE TABLE "custom_table_name"("id" integer NOT NULL UNIQUE PRIMARY KEY, "alt_value" varchar(20) NOT NULL);',
+            'INSERT INTO "custom_table_name" ("id", "alt_value") SELECT "id", "alt_value" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+}
+
+change_field = {
+    "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 "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "char_field1" = \'abc\\\'s xyz\';',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'UPDATE "TEMP_TABLE" SET "char_field1" = "char_field";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_testmodel_int_field1" ON "tests_testmodel" ("int_field1");',
+            'INSERT INTO "tests_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 "change_field_non-default_m2m_table" RENAME TO "custom_m2m_db_table_name";',
+    "AddDBIndexChangeModel": 'CREATE INDEX "tests_testmodel_int_field2" ON "tests_testmodel" ("int_field2");',
+    "RemoveDBIndexChangeModel": 'DROP INDEX "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_testmodel_int_field1" ON "tests_testmodel" ("int_field1");',
+            'INSERT INTO "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_testmodel_int_field1" ON "tests_testmodel" ("int_field1");',
+            'INSERT INTO "tests_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 "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_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 "tests_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";',
+    'BasicWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE "tests_basicwithm2mmodel_m2m";',
+            'DROP TABLE "tests_basicwithm2mmodel";'
+        ]),
+    'CustomTableModel': 
+        'DROP TABLE "custom_table_name";',
+    'CustomTableWithM2MModel': 
+        '\n'.join([
+            'DROP TABLE "another_custom_table_name_m2m";',
+            'DROP TABLE "another_custom_table_name";'
+        ]),
+}
+
+delete_application = {
+    'DeleteApplication':
+        '\n'.join([
+            'DROP TABLE "tests_appdeleteanchor1";',
+            'DROP TABLE "tests_testmodel_anchor_m2m";',
+            'DROP TABLE "tests_testmodel";',
+            'DROP TABLE "app_delete_custom_add_anchor_table";',
+            'DROP TABLE "app_delete_custom_table_name";',
+        ]),
+}
+
+rename_field = {
+    'RenameColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "custom_db_col_name", "char_field", "int_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("custom_db_col_name", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id") SELECT "custom_db_col_name", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameColumnWithTableNameModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "custom_db_col_name", "char_field", "int_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("custom_db_col_name", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id") SELECT "custom_db_col_name", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenamePrimaryKeyColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "int_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "my_pk_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "custom_db_col_name", "char_field", "int_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("custom_db_col_name" integer NOT NULL, "char_field" varchar(20) NOT NULL, "int_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "my_pk_id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("custom_db_col_name", "char_field", "int_field", "custom_db_col_name_indexed", "fk_field_id", "my_pk_id") SELECT "custom_db_col_name", "char_field", "int_field", "custom_db_col_name_indexed", "fk_field_id", "my_pk_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),        
+    'RenameForeignKeyColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "custom_db_col_name" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "renamed_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "custom_db_col_name" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "renamed_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_renamed_field_id" ON "tests_testmodel" ("renamed_field_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "renamed_field_id", "id") SELECT "int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "renamed_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameNonDefaultColumnNameModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "renamed_field" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id") SELECT "int_field", "char_field", "renamed_field", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameNonDefaultColumnNameToNonDefaultNameModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "non-default_column_name" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "non-default_column_name" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "char_field", "non-default_column_name", "custom_db_col_name_indexed", "fk_field_id", "id") SELECT "int_field", "char_field", "non-default_column_name", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameNonDefaultColumnNameToNonDefaultNameAndTableModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "non-default_column_name2" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "char_field", "custom_db_col_name", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "char_field" varchar(20) NOT NULL, "non-default_column_name2" integer NOT NULL, "custom_db_col_name_indexed" integer NOT NULL, "fk_field_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY);',
+            'CREATE INDEX "tests_testmodel_custom_db_col_name_indexed" ON "tests_testmodel" ("custom_db_col_name_indexed");',
+            'CREATE INDEX "tests_testmodel_fk_field_id" ON "tests_testmodel" ("fk_field_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "char_field", "non-default_column_name2", "custom_db_col_name_indexed", "fk_field_id", "id") SELECT "int_field", "char_field", "non-default_column_name2", "custom_db_col_name_indexed", "fk_field_id", "id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameColumnCustomTableModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("id" integer NOT NULL UNIQUE PRIMARY KEY, "renamed_field" integer NOT NULL, "alt_value" varchar(20) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "id", "value", "alt_value" FROM "custom_rename_table_name";',
+            'DROP TABLE "custom_rename_table_name";',
+            'CREATE TABLE "custom_rename_table_name"("id" integer NOT NULL UNIQUE PRIMARY KEY, "renamed_field" integer NOT NULL, "alt_value" varchar(20) NOT NULL);',
+            'INSERT INTO "custom_rename_table_name" ("id", "renamed_field", "alt_value") SELECT "id", "renamed_field", "alt_value" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'RenameManyToManyTableModel': 
+        'ALTER TABLE "tests_testmodel_m2m_field" RENAME TO "tests_testmodel_renamed_field";',
+    'RenameManyToManyTableWithColumnNameModel': 
+        'ALTER TABLE "tests_testmodel_m2m_field" RENAME TO "tests_testmodel_renamed_field";',
+    'RenameNonDefaultManyToManyTableModel': 
+        'ALTER TABLE "non-default_db_table" RENAME TO "tests_testmodel_renamed_field";',
+}
+
+sql_mutation = {
+    'SQLMutationSequence': """[
+...    SQLMutation('first-two-fields', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field1" integer NULL;',
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field2" integer NULL;'
+...    ], update_first_two),
+...    SQLMutation('third-field', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field3" integer NULL;',
+...    ], update_third)]
+""",
+    'SQLMutationOutput': 
+        '\n'.join([
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field1" integer NULL;',
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field2" integer NULL;',
+            'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field3" integer NULL;',
+        ]),
+}
+
+generics = {
+    'DeleteColumnModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "content_type_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "object_id" integer unsigned NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "content_type_id", "id", "object_id" FROM "tests_testmodel";',
+            'DROP TABLE "tests_testmodel";',
+            'CREATE TABLE "tests_testmodel"("int_field" integer NOT NULL, "content_type_id" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "object_id" integer unsigned NOT NULL);',
+            'CREATE INDEX "tests_testmodel_content_type_id" ON "tests_testmodel" ("content_type_id");',
+            'CREATE INDEX "tests_testmodel_object_id" ON "tests_testmodel" ("object_id");',
+            'INSERT INTO "tests_testmodel" ("int_field", "content_type_id", "id", "object_id") SELECT "int_field", "content_type_id", "id", "object_id" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ])
+}
+
+inheritance = {
+    'AddToChildModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "int_field", "id", "char_field", "added_field" FROM "tests_childmodel";',
+            'UPDATE "TEMP_TABLE" SET "added_field" = 42;',
+            'DROP TABLE "tests_childmodel";',
+            'CREATE TABLE "tests_childmodel"("int_field" integer NOT NULL, "id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL, "added_field" integer NOT NULL);',
+            'INSERT INTO "tests_childmodel" ("int_field", "id", "char_field", "added_field") SELECT "int_field", "id", "char_field", "added_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";',
+        ]),
+    'DeleteFromChildModel': 
+        '\n'.join([
+            'CREATE TEMPORARY TABLE "TEMP_TABLE"("id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL);',
+            'INSERT INTO "TEMP_TABLE" SELECT "id", "char_field" FROM "tests_childmodel";',
+            'DROP TABLE "tests_childmodel";',
+            'CREATE TABLE "tests_childmodel"("id" integer NOT NULL UNIQUE PRIMARY KEY, "char_field" varchar(20) NOT NULL);',
+            'INSERT INTO "tests_childmodel" ("id", "char_field") SELECT "id", "char_field" FROM "TEMP_TABLE";',
+            'DROP TABLE "TEMP_TABLE";'
+        ])
+}
\ No newline at end of file
diff --git a/lib/django_evolution/tests/delete_app.py b/lib/django_evolution/tests/delete_app.py
new file mode 100644
index 0000000..dddbd70
--- /dev/null
+++ b/lib/django_evolution/tests/delete_app.py
@@ -0,0 +1,76 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from datetime import datetime
+>>> from pprint import PrettyPrinter
+
+>>> from django.db import models
+
+>>> from django_evolution.mutations import AddField, DeleteField, DeleteApplication
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django_evolution import signature
+>>> from django_evolution import models as test_app
+
+>>> import copy
+
+>>> class AppDeleteAnchor1(models.Model):
+...     value = models.IntegerField()
+
+>>> class AppDeleteAnchor2(models.Model):
+...     value = models.IntegerField()
+...     class Meta:
+...         db_table = 'app_delete_custom_add_anchor_table'
+
+>>> class AppDeleteBaseModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     anchor_fk = models.ForeignKey(AppDeleteAnchor1)
+...     anchor_m2m = models.ManyToManyField(AppDeleteAnchor2)
+
+>>> class AppDeleteCustomTableModel(models.Model):
+...     value = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'app_delete_custom_table_name'
+
+# Store the base signatures, and populate the app cache
+
+>>> anchors = [('AppDeleteAnchor1', AppDeleteAnchor1), ('AppDeleteAnchor2',AppDeleteAnchor2)]
+>>> test_model = [('TestModel', AppDeleteBaseModel)]
+>>> custom_model = [('CustomTestModel', AppDeleteCustomTableModel)]
+>>> all_models = []
+>>> all_models.extend(anchors)
+>>> all_models.extend(test_model)
+>>> all_models.extend(custom_model)
+>>> start = register_models(*all_models)
+>>> start_sig = test_proj_sig(*all_models)
+
+# Copy the base signature, and delete the tests app.
+>>> deleted_app_sig = copy.deepcopy(start_sig)
+>>> deleted_app_sig = deleted_app_sig.pop('tests')
+
+>>> d = Diff(start_sig, deleted_app_sig)
+>>> print d.deleted
+{'tests': ['AppDeleteAnchor1', 'TestModel', 'AppDeleteAnchor2', 'CustomTestModel']}
+
+>>> test_sig = copy.deepcopy(start_sig)
+
+>>> test_sql = []
+>>> delete_app = DeleteApplication()
+>>> for app_label in d.deleted.keys():
+...     test_sql.append(delete_app.mutate(app_label, test_sig))
+...     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
+>>> deregister_models()
+
+""" % test_sql_mapping('delete_application')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/delete_field.py b/lib/django_evolution/tests/delete_field.py
new file mode 100644
index 0000000..ba80653
--- /dev/null
+++ b/lib/django_evolution/tests/delete_field.py
@@ -0,0 +1,268 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+
+>>> from django_evolution.mutations import DeleteField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+
+>>> import copy
+ 
+# All Fields
+# db index (ignored for now)
+# db tablespace (ignored for now)
+# db column
+# primary key
+# unique
+
+# M2M Fields
+# to field
+# db table
+
+# Model Meta
+# db table
+# db tablespace (ignored for now)
+# unique together (ignored for now)
+
+# Now, a useful test model we can use for evaluating diffs
+>>> class DeleteAnchor1(models.Model):
+...     value = models.IntegerField()
+>>> class DeleteAnchor2(models.Model):
+...     value = models.IntegerField()
+>>> class DeleteAnchor3(models.Model):
+...     value = models.IntegerField()
+>>> class DeleteAnchor4(models.Model):
+...     value = models.IntegerField()
+
+>>> class DeleteBaseModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     int_field3 = models.IntegerField(unique=True)
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> class CustomTableModel(models.Model):
+...     value = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'custom_table_name'
+
+# Store the base signatures
+>>> anchors = (
+...     ('DeleteAnchor1', DeleteAnchor1), 
+...     ('DeleteAnchor2', DeleteAnchor2), 
+...     ('DeleteAnchor3', DeleteAnchor3), 
+...     ('DeleteAnchor4', DeleteAnchor4), 
+... )
+
+>>> custom_model = ('CustomTableModel', CustomTableModel)
+>>> custom = register_models(custom_model)
+>>> custom_sig = test_proj_sig(custom_model)
+
+>>> test_model = ('TestModel', DeleteBaseModel)
+>>> start = register_models(*anchors)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(test_model, *anchors)
+
+# Deleting a default named column
+>>> class DefaultNamedColumnModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     int_field3 = models.IntegerField(unique=True)
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> end = register_models(('TestModel', DefaultNamedColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', DefaultNamedColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'int_field')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #DefaultNamedColumnModel
+%(DefaultNamedColumnModel)s
+
+# Deleting a non-default named column
+>>> class NonDefaultNamedColumnModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field3 = models.IntegerField(unique=True)
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> end = register_models(('TestModel', NonDefaultNamedColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', NonDefaultNamedColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'int_field2')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #NonDefaultNamedColumnModel
+%(NonDefaultNamedColumnModel)s
+
+# Deleting a column with database constraints (unique)
+# TODO: Verify that the produced SQL is actually correct
+# -- BK
+>>> class ConstrainedColumnModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> end = register_models(('TestModel', ConstrainedColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', ConstrainedColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'int_field3')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #ConstrainedColumnModel
+%(ConstrainedColumnModel)s
+
+# Deleting a default m2m
+>>> class DefaultM2MModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     int_field3 = models.IntegerField(unique=True)
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> end = register_models(('TestModel', DefaultM2MModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', DefaultM2MModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'm2m_field1')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #DefaultManyToManyModel
+%(DefaultManyToManyModel)s
+
+# Deleting a m2m stored in a non-default table
+>>> class NonDefaultM2MModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     int_field3 = models.IntegerField(unique=True)
+...     fk_field1 = models.ForeignKey(DeleteAnchor1)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+
+>>> end = register_models(('TestModel', NonDefaultM2MModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', NonDefaultM2MModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'm2m_field2')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #NonDefaultManyToManyModel
+%(NonDefaultManyToManyModel)s
+
+# Delete a foreign key
+>>> class DeleteForeignKeyModel(models.Model):
+...     my_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field2 = models.IntegerField(db_column='non-default_db_column')
+...     int_field3 = models.IntegerField(unique=True)
+...     m2m_field1 = models.ManyToManyField(DeleteAnchor3)
+...     m2m_field2 = models.ManyToManyField(DeleteAnchor4, db_table='non-default_m2m_table')
+
+>>> end = register_models(('TestModel', DeleteForeignKeyModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', DeleteForeignKeyModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'fk_field1')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #DeleteForeignKeyModel
+%(DeleteForeignKeyModel)s
+
+# Deleting a column from a non-default table
+>>> class DeleteColumnCustomTableModel(models.Model):
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'custom_table_name'
+
+>>> end = register_models(('CustomTableModel', DeleteColumnCustomTableModel))
+>>> end_sig = test_proj_sig(('CustomTableModel', DeleteColumnCustomTableModel))
+>>> d = Diff(custom_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('CustomTableModel', 'value')"]
+
+>>> test_sig = copy.deepcopy(custom_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(custom, end, test_sql) #DeleteColumnCustomTableModel
+%(DeleteColumnCustomTableModel)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('delete_field')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/delete_model.py b/lib/django_evolution/tests/delete_model.py
new file mode 100644
index 0000000..73d1739
--- /dev/null
+++ b/lib/django_evolution/tests/delete_model.py
@@ -0,0 +1,131 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+
+>>> from django_evolution.mutations import DeleteModel
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+
+>>> import copy
+ 
+# Now, a useful test model we can use for evaluating diffs
+>>> class DeleteModelAnchor(models.Model):
+...     value = models.IntegerField()
+>>> class BasicModel(models.Model):
+...     value = models.IntegerField()
+>>> class BasicWithM2MModel(models.Model):
+...     value = models.IntegerField()
+...     m2m = models.ManyToManyField(DeleteModelAnchor)
+>>> class CustomTableModel(models.Model):
+...     value = models.IntegerField()
+...     class Meta:
+...         db_table = 'custom_table_name'
+>>> class CustomTableWithM2MModel(models.Model):
+...     value = models.IntegerField()
+...     m2m = models.ManyToManyField(DeleteModelAnchor)
+...     class Meta:
+...         db_table = 'another_custom_table_name'
+
+# Store the base signature
+>>> base_models = (
+...     ('DeleteModelAnchor', DeleteModelAnchor),
+...     ('BasicModel', BasicModel),
+...     ('BasicWithM2MModel', BasicWithM2MModel),
+...     ('CustomTableModel', CustomTableModel),
+...     ('CustomTableWithM2MModel', CustomTableWithM2MModel),
+... )
+
+>>> start = register_models(*base_models)
+>>> start_sig = test_proj_sig(*base_models)
+
+# Delete a Model
+>>> end_sig = copy.deepcopy(start_sig)
+>>> _ = end_sig['tests'].pop('BasicModel')
+>>> end = copy.deepcopy(start)
+>>> _ = end.pop('basicmodel')
+
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteModel('BasicModel')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #BasicModel
+%(BasicModel)s
+
+# Delete a model with an m2m field
+>>> end_sig = copy.deepcopy(start_sig)
+>>> _ = end_sig['tests'].pop('BasicWithM2MModel')
+>>> end = copy.deepcopy(start)
+>>> _ = end.pop('basicwithm2mmodel')
+
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteModel('BasicWithM2MModel')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # BasicWithM2MModels
+%(BasicWithM2MModel)s
+
+# Delete a model with a custom table name
+>>> end_sig = copy.deepcopy(start_sig)
+>>> _ = end_sig['tests'].pop('CustomTableModel')
+>>> end = copy.deepcopy(start)
+>>> _ = end.pop('customtablemodel')
+
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteModel('CustomTableModel')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #CustomTableModel
+%(CustomTableModel)s
+
+# Delete a model with a custom table name and an m2m field
+>>> end_sig = copy.deepcopy(start_sig)
+>>> _ = end_sig['tests'].pop('CustomTableWithM2MModel')
+
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteModel('CustomTableWithM2MModel')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #CustomTableWithM2MModel
+%(CustomTableWithM2MModel)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('delete_model')
diff --git a/lib/django_evolution/tests/generics.py b/lib/django_evolution/tests/generics.py
new file mode 100644
index 0000000..dd0987b
--- /dev/null
+++ b/lib/django_evolution/tests/generics.py
@@ -0,0 +1,71 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+
+>>> from django_evolution.mutations import DeleteField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django.contrib.contenttypes import generic
+>>> from django.contrib.contenttypes.models import ContentType
+
+>>> import copy
+ 
+# Now, a useful test model we can use for evaluating diffs
+>>> class GenericAnchor(models.Model):
+...     value = models.IntegerField()
+...     # Host a generic key here, too
+...     content_type = models.ForeignKey(ContentType)
+...     object_id = models.PositiveIntegerField(db_index=True)
+...     content_object = generic.GenericForeignKey('content_type','object_id')
+
+>>> class GenericBaseModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     # Plus a generic foreign key - the Generic itself should be ignored
+...     content_type = models.ForeignKey(ContentType)
+...     object_id = models.PositiveIntegerField(db_index=True)
+...     content_object = generic.GenericForeignKey('content_type','object_id')
+...     # Plus a generic relation, which should be ignored
+...     generic = generic.GenericRelation(GenericAnchor)
+
+# Store the base signatures
+>>> anchor = ('Anchor', GenericAnchor)
+>>> content_type = ('contenttypes.ContentType', ContentType)
+>>> test_model = ('TestModel', GenericBaseModel)
+>>> start = register_models(anchor)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(test_model, content_type, anchor)
+
+# Delete a column
+>>> class DeleteColumnModel(models.Model):
+...     int_field = models.IntegerField()
+...     # Plus a generic foreign key - the Generic itself should be ignored
+...     content_type = models.ForeignKey(ContentType)
+...     object_id = models.PositiveIntegerField(db_index=True)
+...     content_object = generic.GenericForeignKey('content_type','object_id')
+...     # Plus a generic relation, which should be ignored
+...     generic = generic.GenericRelation(GenericAnchor)
+
+>>> end = register_models(('TestModel', DeleteColumnModel), anchor)
+>>> end_sig = test_proj_sig(('TestModel', DeleteColumnModel), content_type, anchor)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'char_field')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #DeleteColumnModel
+%(DeleteColumnModel)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('generics')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/inheritance.py b/lib/django_evolution/tests/inheritance.py
new file mode 100644
index 0000000..55b140d
--- /dev/null
+++ b/lib/django_evolution/tests/inheritance.py
@@ -0,0 +1,82 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from datetime import datetime
+
+>>> from django.db import models
+
+>>> from django_evolution.mutations import AddField, DeleteField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django_evolution import signature
+>>> from django_evolution import models as test_app
+
+>>> import copy
+
+>>> class ParentModel(models.Model):
+...     parent_field = models.CharField(max_length=20)
+...     other_field = models.IntegerField()
+
+>>> class ChildModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+
+# Store the base signatures
+>>> parent_model = ('ParentModel', ParentModel)
+>>> parent = register_models(parent_model)
+>>> parent_table_sig = test_proj_sig(parent_model)
+
+>>> test_model = ('ChildModel', ChildModel)
+>>> start = register_models(test_model)
+>>> start_sig = test_proj_sig(test_model, parent_model)
+
+# Add field to child model
+>>> class AddToChildModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field = models.IntegerField(default=42)
+
+>>> end = register_models(('ChildModel', AddToChildModel), parent_model)
+>>> end_sig = test_proj_sig(('ChildModel',AddToChildModel), parent_model)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] # AddToChildModel
+["AddField('ChildModel', 'added_field', models.IntegerField, initial=42)"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # AddToChildModel
+%(AddToChildModel)s
+
+# Delete field from child model
+>>> class AddToChildModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+
+>>> end = register_models(('ChildModel', AddToChildModel), parent_model)
+>>> end_sig = test_proj_sig(('ChildModel',AddToChildModel), parent_model)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']] # DeleteFromChildModel
+["DeleteField('ChildModel', 'int_field')"]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in d.evolution()['tests']:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) # DeleteFromChildModel
+%(DeleteFromChildModel)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('inheritance')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/models.py b/lib/django_evolution/tests/models.py
new file mode 100644
index 0000000..8f0c2e7
--- /dev/null
+++ b/lib/django_evolution/tests/models.py
@@ -0,0 +1,3 @@
+# This module is used as a placeholder for the registration of test models. 
+# It is intentionally empty; individual tests create and register models
+# that will appear to Django as if they are in this module.
\ No newline at end of file
diff --git a/lib/django_evolution/tests/ordering.py b/lib/django_evolution/tests/ordering.py
new file mode 100644
index 0000000..f7a91e9
--- /dev/null
+++ b/lib/django_evolution/tests/ordering.py
@@ -0,0 +1,49 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+
+>>> from django_evolution.tests.utils import test_proj_sig, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+
+>>> import copy
+
+>>> class Case41Anchor(models.Model):
+...     value = models.IntegerField()
+
+>>> class Case41Model(models.Model):
+...     value = models.IntegerField()
+...     ref = models.ForeignKey(Case41Anchor)
+
+# Store the base signatures
+>>> anchors = (
+...     ('Case41Anchor', Case41Anchor),
+... )
+
+>>> test_model = ('TestModel', Case41Model)
+>>> start = register_models(*anchors)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(test_model, *anchors)
+
+# Regression case 41: If deleteing a model and a foreign key to that model,
+# The key deletion needs to happen before the model deletion. 
+
+# Delete the foreign key...
+>>> class UpdatedCase41Model(models.Model):
+...     value = models.IntegerField()
+
+>>> end = register_models(('TestModel', UpdatedCase41Model), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',UpdatedCase41Model), *anchors)
+
+# ... And also delete the model that was being referenced
+>>> _ = end_sig['tests'].pop('Case41Anchor')
+
+# The evolution sequence needs
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["DeleteField('TestModel', 'ref')", "DeleteModel('Case41Anchor')"]
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+"""
\ No newline at end of file
diff --git a/lib/django_evolution/tests/rename_field.py b/lib/django_evolution/tests/rename_field.py
new file mode 100644
index 0000000..b83f520
--- /dev/null
+++ b/lib/django_evolution/tests/rename_field.py
@@ -0,0 +1,399 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+# Rename a database column (done)
+# RenameField with a specified db table for a field other than a M2MField is allowed (but will be ignored) (done)
+# Rename a primary key database column (done)
+# Rename a foreign key database column (done)
+
+# Rename a database column with a non-default name to a default name (done)
+# Rename a database column with a non-default name to a different non-default name (done)
+# RenameField with a specified db column and db table is allowed (but one will be ignored) (done)
+
+# Rename a database column in a non-default table (done)
+
+# Rename an indexed database column (Redundant, Not explicitly tested)
+# Rename a database column with null constraints (Redundant, Not explicitly tested)
+
+# Rename a M2M database table (done)
+# RenameField with a specified db column for a M2MField is allowed (but will be ignored) (done)
+# Rename a M2M non-default database table to a default name (done)
+
+>>> from django.db import models
+>>> from django_evolution.mutations import RenameField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django_evolution import signature
+>>> from django_evolution import models as test_app
+
+>>> import copy
+
+>>> class RenameAnchor1(models.Model):
+...     value = models.IntegerField()
+
+>>> class RenameAnchor2(models.Model):
+...     value = models.IntegerField()
+...     class Meta:
+...         db_table = 'custom_rename_anchor_table'
+
+>>> class RenameAnchor3(models.Model):
+...     value = models.IntegerField()
+
+>>> class RenameBaseModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> class CustomRenameTableModel(models.Model):
+...     value = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'custom_rename_table_name'
+
+# Store the base signatures
+>>> anchors = [
+...     ('RenameAnchor1', RenameAnchor1), 
+...     ('RenameAnchor2', RenameAnchor2), 
+...     ('RenameAnchor3',RenameAnchor3)
+... ]
+>>> test_model = ('TestModel', RenameBaseModel)
+>>> custom_model = ('CustomTableModel', CustomRenameTableModel)
+
+>>> custom = register_models(custom_model)
+>>> custom_table_sig = test_proj_sig(custom_model)
+
+>>> start = register_models(*anchors)
+>>> start.update(register_models(test_model))
+>>> start_sig = test_proj_sig(test_model, *anchors)
+
+# Rename a database column
+>>> class RenameColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     renamed_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel', RenameColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>)", "DeleteField('TestModel', 'int_field')"]
+
+>>> evolution = [RenameField('TestModel', 'int_field', 'renamed_field')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameColumnModel
+%(RenameColumnModel)s
+
+# RenameField with a specified db table for a field other than a M2MField is allowed (but will be ignored) (done)
+>>> class RenameColumnWithTableNameModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     renamed_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameColumnWithTableNameModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameColumnWithTableNameModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>)", "DeleteField('TestModel', 'int_field')"]
+
+>>> evolution = [RenameField('TestModel', 'int_field', 'renamed_field', db_table='ignored_db-table')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameColumnWithTableNameModel
+%(RenameColumnWithTableNameModel)s
+
+# Rename a primary key database column
+>>> class RenamePrimaryKeyColumnModel(models.Model):
+...     my_pk_id = models.AutoField(primary_key=True)
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenamePrimaryKeyColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenamePrimaryKeyColumnModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'my_pk_id', models.AutoField, initial=<<USER VALUE REQUIRED>>, primary_key=True)", "DeleteField('TestModel', 'id')"]
+
+>>> evolution = [RenameField('TestModel', 'id', 'my_pk_id')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenamePrimaryKeyColumnModel
+%(RenamePrimaryKeyColumnModel)s
+
+# Rename a foreign key database column 
+>>> class RenameForeignKeyColumnModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     renamed_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameForeignKeyColumnModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameForeignKeyColumnModel), *anchors)
+>>> start_sig = copy.deepcopy(start_sig)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.ForeignKey, initial=<<USER VALUE REQUIRED>>, related_model='tests.RenameAnchor1')", "DeleteField('TestModel', 'fk_field')"]
+
+>>> evolution = [RenameField('TestModel', 'fk_field', 'renamed_field')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+# FIXME!! This test doesn't work on Postgres
+#>>> execute_test_sql(start, end, test_sql) #RenameForeignKeyColumnModel
+#%(RenameForeignKeyColumnModel)s
+
+# Rename a database column with a non-default name
+>>> class RenameNonDefaultColumnNameModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     renamed_field = models.IntegerField()
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameNonDefaultColumnNameModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameNonDefaultColumnNameModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>)", "DeleteField('TestModel', 'int_field_named')"]
+
+>>> evolution = [RenameField('TestModel', 'int_field_named', 'renamed_field')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameNonDefaultColumnNameModel
+%(RenameNonDefaultColumnNameModel)s
+
+# Rename a database column with a non-default name to a different non-default name
+>>> class RenameNonDefaultColumnNameToNonDefaultNameModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     renamed_field = models.IntegerField(db_column='non-default_column_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameNonDefaultColumnNameToNonDefaultNameModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameNonDefaultColumnNameToNonDefaultNameModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>, db_column='non-default_column_name')", "DeleteField('TestModel', 'int_field_named')"]
+
+>>> evolution = [RenameField('TestModel', 'int_field_named', 'renamed_field', db_column='non-default_column_name')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameNonDefaultColumnNameToNonDefaultNameModel
+%(RenameNonDefaultColumnNameToNonDefaultNameModel)s
+ 
+# RenameField with a specified db column and db table is allowed (but one will be ignored)
+>>> class RenameNonDefaultColumnNameToNonDefaultNameAndTableModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     renamed_field = models.IntegerField(db_column='non-default_column_name2')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameNonDefaultColumnNameToNonDefaultNameAndTableModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameNonDefaultColumnNameToNonDefaultNameAndTableModel), *anchors)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>, db_column='non-default_column_name2')", "DeleteField('TestModel', 'int_field_named')"]
+
+>>> evolution = [RenameField('TestModel', 'int_field_named', 'renamed_field', db_column='non-default_column_name2', db_table='custom_ignored_db-table')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameNonDefaultColumnNameToNonDefaultNameAndTableModel
+%(RenameNonDefaultColumnNameToNonDefaultNameAndTableModel)s
+
+# Rename a database column in a non-default table
+# Rename a database column
+>>> class RenameColumnCustomTableModel(models.Model):
+...     renamed_field = models.IntegerField()
+...     alt_value = models.CharField(max_length=20)
+...     class Meta:
+...         db_table = 'custom_rename_table_name'
+
+>>> end = register_models(('CustomTableModel', RenameColumnCustomTableModel))
+>>> end_sig = test_proj_sig(('CustomTableModel',RenameColumnCustomTableModel))
+>>> d = Diff(custom_table_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('CustomTableModel', 'renamed_field', models.IntegerField, initial=<<USER VALUE REQUIRED>>)", "DeleteField('CustomTableModel', 'value')"]
+
+>>> evolution = [RenameField('CustomTableModel', 'value', 'renamed_field')]
+>>> test_sig = copy.deepcopy(custom_table_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(custom, end, test_sql) #RenameColumnCustomTableModel
+%(RenameColumnCustomTableModel)s
+
+# Rename a M2M database table
+>>> class RenameM2MTableModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     renamed_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameM2MTableModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameM2MTableModel), *anchors)
+>>> start_sig = copy.deepcopy(start_sig)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.ManyToManyField, related_model='tests.RenameAnchor2')", "DeleteField('TestModel', 'm2m_field')"]
+
+>>> evolution = [RenameField('TestModel', 'm2m_field', 'renamed_field')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+>>> execute_test_sql(start, end, test_sql) #RenameManyToManyTableModel
+%(RenameManyToManyTableModel)s
+
+# RenameField with a specified db column for a M2MField is allowed (but will be ignored)
+>>> class RenameM2MTableWithColumnNameModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     renamed_field = models.ManyToManyField(RenameAnchor2)
+...     m2m_field_named = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameM2MTableWithColumnNameModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameM2MTableWithColumnNameModel), *anchors)
+>>> start_sig = copy.deepcopy(start_sig)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.ManyToManyField, related_model='tests.RenameAnchor2')", "DeleteField('TestModel', 'm2m_field')"]
+
+>>> evolution = [RenameField('TestModel', 'm2m_field', 'renamed_field', db_column='ignored_db-column')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #RenameManyToManyTableWithColumnNameModel
+%(RenameManyToManyTableWithColumnNameModel)s
+
+# Rename a M2M non-default database table to a default name
+>>> class RenameNonDefaultM2MTableModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     int_field_named = models.IntegerField(db_column='custom_db_col_name')
+...     int_field_named_indexed = models.IntegerField(db_column='custom_db_col_name_indexed', db_index=True)
+...     fk_field = models.ForeignKey(RenameAnchor1)
+...     m2m_field = models.ManyToManyField(RenameAnchor2)
+...     renamed_field = models.ManyToManyField(RenameAnchor3, db_table='non-default_db_table')
+
+>>> end = register_models(('TestModel', RenameNonDefaultM2MTableModel), *anchors)
+>>> end_sig = test_proj_sig(('TestModel',RenameNonDefaultM2MTableModel), *anchors)
+>>> start_sig = copy.deepcopy(start_sig)
+>>> d = Diff(start_sig, end_sig)
+>>> print [str(e) for e in d.evolution()['tests']]
+["AddField('TestModel', 'renamed_field', models.ManyToManyField, db_table='non-default_db_table', related_model='tests.RenameAnchor3')", "DeleteField('TestModel', 'm2m_field_named')"]
+
+>>> evolution = [RenameField('TestModel', 'm2m_field_named', 'renamed_field')]
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in evolution:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+False
+
+# FIXME!! This test fails under Postgres
+#>>> execute_test_sql(start, end, test_sql) #RenameNonDefaultManyToManyTableModel
+#%(RenameNonDefaultManyToManyTableModel)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('rename_field')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/signature.py b/lib/django_evolution/tests/signature.py
new file mode 100644
index 0000000..9dde07a
--- /dev/null
+++ b/lib/django_evolution/tests/signature.py
@@ -0,0 +1,248 @@
+
+tests = r"""
+>>> from django.db import models
+>>> from django_evolution import signature
+>>> from django_evolution.diff import Diff
+>>> from django_evolution.tests.utils import test_proj_sig, register_models, deregister_models
+>>> from pprint import pprint
+>>> from django.contrib.contenttypes import generic
+>>> from django.contrib.contenttypes.models import ContentType
+
+# First, a model that has one of everything so we can validate all cases for a signature
+>>> class Anchor1(models.Model):
+...     value = models.IntegerField()
+>>> class Anchor2(models.Model):
+...     value = models.IntegerField()
+>>> class Anchor3(models.Model):
+...     value = models.IntegerField()
+...     # Host a generic key here, too
+...     content_type = models.ForeignKey(ContentType)
+...     object_id = models.PositiveIntegerField(db_index=True)
+...     content_object = generic.GenericForeignKey('content_type','object_id')
+
+>>> anchors = [('Anchor1', Anchor1),('Anchor2', Anchor2),('Anchor3', Anchor3)]
+
+>>> class SigModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     null_field = models.IntegerField(null=True, db_column='size_column')
+...     id_card = models.IntegerField(unique=True, db_index=True)
+...     dec_field = models.DecimalField(max_digits=10, decimal_places=4)
+...     ref1 = models.ForeignKey(Anchor1)
+...     ref2 = models.ForeignKey(Anchor1, related_name='other_sigmodel')
+...     ref3 = models.ForeignKey(Anchor2, db_column='value', db_index=True)
+...     ref4 = models.ForeignKey('self')
+...     ref5 = models.ManyToManyField(Anchor3)
+...     ref6 = models.ManyToManyField(Anchor3, related_name='other_sigmodel')
+...     ref7 = models.ManyToManyField('self')
+...     # Plus a generic foreign key - the Generic itself should be ignored
+...     content_type = models.ForeignKey(ContentType)
+...     object_id = models.PositiveIntegerField(db_index=True)
+...     content_object = generic.GenericForeignKey('content_type','object_id')
+...     # Plus a generic relation, which should be ignored
+...     generic = generic.GenericRelation(Anchor3)
+
+>>> class ParentModel(models.Model):
+...     parent_field = models.CharField(max_length=20)
+
+>>> class ChildModel(ParentModel):
+...     child_field = models.CharField(max_length=20)
+
+# Store the base signatures
+>>> base_cache = register_models(('Anchor1', Anchor1), ('Anchor2', Anchor2), ('Anchor3', Anchor3), ('TestModel', SigModel), ('ParentModel',ParentModel), ('ChildModel',ChildModel))
+
+# You can create a model signature for a model
+>>> pprint(signature.create_model_sig(SigModel))
+{'fields': {'char_field': {'field_type': <class 'django.db.models.fields.CharField'>,
+                           'max_length': 20},
+            'content_type': {'field_type': <class 'django.db.models.fields.related.ForeignKey'>,
+                             'related_model': 'contenttypes.ContentType'},
+            'dec_field': {'decimal_places': 4,
+                          'field_type': <class 'django.db.models.fields.DecimalField'>,
+                          'max_digits': 10},
+            'id': {'field_type': <class 'django.db.models.fields.AutoField'>,
+                   'primary_key': True},
+            'id_card': {'db_index': True,
+                        'field_type': <class 'django.db.models.fields.IntegerField'>,
+                        'unique': True},
+            'int_field': {'field_type': <class 'django.db.models.fields.IntegerField'>},
+            'null_field': {'db_column': 'size_column',
+                           'field_type': <class 'django.db.models.fields.IntegerField'>,
+                           'null': True},
+            'object_id': {'db_index': True,
+                          'field_type': <class 'django.db.models.fields.PositiveIntegerField'>},
+            'ref1': {'field_type': <class 'django.db.models.fields.related.ForeignKey'>,
+                     'related_model': 'tests.Anchor1'},
+            'ref2': {'field_type': <class 'django.db.models.fields.related.ForeignKey'>,
+                     'related_model': 'tests.Anchor1'},
+            'ref3': {'db_column': 'value',
+                     'field_type': <class 'django.db.models.fields.related.ForeignKey'>,
+                     'related_model': 'tests.Anchor2'},
+            'ref4': {'field_type': <class 'django.db.models.fields.related.ForeignKey'>,
+                     'related_model': 'tests.TestModel'},
+            'ref5': {'field_type': <class 'django.db.models.fields.related.ManyToManyField'>,
+                     'related_model': 'tests.Anchor3'},
+            'ref6': {'field_type': <class 'django.db.models.fields.related.ManyToManyField'>,
+                     'related_model': 'tests.Anchor3'},
+            'ref7': {'field_type': <class 'django.db.models.fields.related.ManyToManyField'>,
+                     'related_model': 'tests.TestModel'}},
+ 'meta': {'db_table': 'tests_testmodel',
+          'db_tablespace': '',
+          'pk_column': 'id',
+          'unique_together': []}}
+
+>>> pprint(signature.create_model_sig(ChildModel))
+{'fields': {'child_field': {'field_type': <class 'django.db.models.fields.CharField'>,
+                            'max_length': 20},
+            'parentmodel_ptr': {'field_type': <class 'django.db.models.fields.related.OneToOneField'>,
+                                'primary_key': True,
+                                'related_model': 'tests.ParentModel',
+                                'unique': True}},
+ 'meta': {'db_table': 'tests_childmodel',
+          'db_tablespace': '',
+          'pk_column': 'parentmodel_ptr_id',
+          'unique_together': []}}
+
+# Now, a useful test model we can use for evaluating diffs
+>>> class BaseModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor1)
+>>> start = register_models(('TestModel', BaseModel), *anchors)
+
+>>> start_sig = test_proj_sig(('TestModel', BaseModel), *anchors)
+
+# An identical model gives an empty Diff
+>>> class TestModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', TestModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',TestModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+True
+>>> d.evolution()
+{}
+
+# Adding a field gives a non-empty diff
+>>> class AddFieldModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor1)
+...     date_of_birth = models.DateField()
+
+>>> end = register_models(('TestModel', AddFieldModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',AddFieldModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+>>> print [str(e) for e in d.evolution()['tests']] # Add Field
+["AddField('TestModel', 'date_of_birth', models.DateField, initial=<<USER VALUE REQUIRED>>)"]
+
+# Deleting a field gives a non-empty diff
+>>> class DeleteFieldModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', DeleteFieldModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',DeleteFieldModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+>>> print [str(e) for e in d.evolution()['tests']] # Delete Field
+["DeleteField('TestModel', 'age')"]
+
+# Renaming a field is caught as 2 diffs
+# (For the moment - long term, this should hint as a Rename) 
+>>> class RenameFieldModel(models.Model):
+...     full_name = models.CharField(max_length=20)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', RenameFieldModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',RenameFieldModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+>>> print [str(e) for e in d.evolution()['tests']] # Rename Field
+["AddField('TestModel', 'full_name', models.CharField, initial=<<USER VALUE REQUIRED>>, max_length=20)", "DeleteField('TestModel', 'name')"]
+
+# Adding a property to a field which was not present in the original Model
+>>> class AddPropertyModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField(null=True)
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', AddPropertyModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',AddPropertyModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+
+>>> print [str(e) for e in d.evolution()['tests']] # Change Field - add property
+["ChangeField('TestModel', 'age', initial=None, null=True)"]
+
+# Since we can't check the evolutions, check the diff instead
+>>> print d
+In model tests.TestModel:
+    In field 'age':
+        Property 'null' has changed
+
+# Adding a property of a field which was not present in the original Model, but
+# is now set to the default for that property.
+>>> class AddDefaultPropertyModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField(null=False)
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', AddDefaultPropertyModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',AddDefaultPropertyModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+True
+>>> print d.evolution()
+{}
+
+# Changing a property of a field
+>>> class ChangePropertyModel(models.Model):
+...     name = models.CharField(max_length=30)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor1)
+
+>>> end = register_models(('TestModel', ChangePropertyModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',ChangePropertyModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+
+>>> print [str(e) for e in d.evolution()['tests']] # Change Field - change property
+["ChangeField('TestModel', 'name', initial=None, max_length=30)"]
+ 
+# Since we can't check the evolutions, check the diff instead
+>>> print d
+In model tests.TestModel:
+    In field 'name':
+        Property 'max_length' has changed
+
+# Changing the model that a ForeignKey references
+>>> class ChangeFKModel(models.Model):
+...     name = models.CharField(max_length=20)
+...     age = models.IntegerField()
+...     ref = models.ForeignKey(Anchor2)
+
+>>> end = register_models(('TestModel', ChangeFKModel), *anchors)
+>>> test_sig = test_proj_sig(('TestModel',ChangeFKModel), *anchors)
+>>> d = Diff(start_sig, test_sig)
+>>> d.is_empty()
+False
+
+>>> print [str(e) for e in d.evolution()['tests']] # Change Field - change property
+["ChangeField('TestModel', 'ref', initial=None, related_model='tests.Anchor2')"]
+ 
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+"""
+
diff --git a/lib/django_evolution/tests/sql_mutation.py b/lib/django_evolution/tests/sql_mutation.py
new file mode 100644
index 0000000..71d6785
--- /dev/null
+++ b/lib/django_evolution/tests/sql_mutation.py
@@ -0,0 +1,93 @@
+from django_evolution.tests.utils import test_sql_mapping
+
+tests = r"""
+>>> from django.db import models
+>>> from django_evolution.mutations import SQLMutation
+
+>>> from django.db import models
+
+>>> from django_evolution.mutations import AddField
+>>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models
+>>> from django_evolution.diff import Diff
+>>> from django_evolution import signature
+>>> from django_evolution import models as test_app
+
+>>> import copy
+
+>>> class SQLBaseModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+
+# Store the base signatures
+>>> start = register_models(('TestModel', SQLBaseModel))
+>>> start_sig = test_proj_sig(('TestModel', SQLBaseModel))
+
+# Add 3 Fields resulting in new database columns.
+>>> class SQLMutationModel(models.Model):
+...     char_field = models.CharField(max_length=20)
+...     int_field = models.IntegerField()
+...     added_field1 = models.IntegerField(null=True)
+...     added_field2 = models.IntegerField(null=True)
+...     added_field3 = models.IntegerField(null=True)
+>>> end = register_models(('TestModel', SQLMutationModel))
+>>> end_sig = test_proj_sig(('TestModel',SQLMutationModel))
+>>> d = Diff(start_sig, end_sig)
+
+# Add the fields using SQLMutations
+>>> sequence = [
+...    SQLMutation('first-two-fields', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field1" integer NULL;',
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field2" integer NULL;'
+...    ]),
+...    SQLMutation('third-field', [
+...        'ALTER TABLE "tests_testmodel" ADD COLUMN "added_field3" integer NULL;',
+...    ])]
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in sequence:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+Traceback (most recent call last):
+...
+CannotSimulate: Cannot simulate SQLMutations
+
+# Redefine the sequence with update functions. 
+>>> def update_first_two(app_label, proj_sig):
+...     app_sig = proj_sig[app_label]
+...     model_sig = app_sig['TestModel']
+...     model_sig['fields']['added_field1'] = {
+...         'field_type': models.IntegerField,
+...         'null': True
+...     }
+...     model_sig['fields']['added_field2'] = {
+...         'field_type': models.IntegerField,
+...         'null': True
+...     }
+
+>>> def update_third(app_label, proj_sig):
+...     app_sig = proj_sig[app_label]
+...     model_sig = app_sig['TestModel']
+...     model_sig['fields']['added_field3'] = {
+...         'field_type': models.IntegerField,
+...         'null': True
+...     }
+
+>>> sequence = %(SQLMutationSequence)s
+
+>>> test_sig = copy.deepcopy(start_sig)
+>>> test_sql = []
+>>> for mutation in sequence:
+...     test_sql.extend(mutation.mutate('tests', test_sig))
+...     mutation.simulate('tests', test_sig)
+
+>>> Diff(test_sig, end_sig).is_empty()
+True
+
+>>> execute_test_sql(start, end, test_sql) #SQLMutationOutput
+%(SQLMutationOutput)s
+
+# Clean up after the applications that were installed
+>>> deregister_models()
+
+""" % test_sql_mapping('sql_mutation')
\ No newline at end of file
diff --git a/lib/django_evolution/tests/utils.py b/lib/django_evolution/tests/utils.py
new file mode 100644
index 0000000..6a6f1e0
--- /dev/null
+++ b/lib/django_evolution/tests/utils.py
@@ -0,0 +1,185 @@
+import copy
+
+from datetime import datetime
+from django.core.management.color import no_style
+from django.core.management.sql import sql_create, sql_delete, sql_indexes
+from django.db import connection, transaction, settings, models
+from django.db.backends.util import truncate_name
+from django.db.models.loading import cache
+
+from django_evolution import signature
+from django_evolution.tests import models as evo_test
+from django_evolution.utils import write_sql, execute_sql
+
+from django.contrib.contenttypes import models as contenttypes
+
+DEFAULT_TEST_ATTRIBUTE_VALUES = {
+    models.CharField: 'TestCharField',
+    models.IntegerField: '123',
+    models.AutoField: None,
+    models.DateTimeField: datetime.now(),
+    models.PositiveIntegerField: '42'
+}
+
+def register_models(*models):
+    app_cache = {}
+    for name, model in models:
+        if model._meta.module_name in cache.app_models['django_evolution']:
+            del cache.app_models['django_evolution'][model._meta.module_name]
+        
+            if model._meta.db_table.startswith("%s_%s" % (model._meta.app_label, 
+                                                          model._meta.module_name)):
+                model._meta.db_table = 'tests_%s' % name.lower()
+            
+            model._meta.app_label = 'tests'
+            model._meta.object_name = name
+            model._meta.module_name = name.lower()
+    
+            cache.app_models.setdefault('tests', {})[name.lower()] = model
+        
+        app_cache[name.lower()] = model
+        
+    return app_cache
+        
+def test_proj_sig(*models, **kwargs):
+    "Generate a dummy project signature based around a single model"
+    version = kwargs.get('version',1)
+    proj_sig = {
+        'tests': {}, 
+        '__version__': version,
+    }
+
+    # Compute the project siguature
+    for full_name,model in models:
+        parts = full_name.split('.')
+        if len(parts) == 1:
+            name = parts[0]
+            app = 'tests'
+        else:
+            app,name = parts
+        proj_sig.setdefault(app,{})[name] = signature.create_model_sig(model)
+    
+    return proj_sig
+    
+def execute_transaction(sql, output=False):
+    "A transaction wrapper for executing a list of SQL statements"
+    try:
+        # Begin Transaction
+        transaction.enter_transaction_management()
+        transaction.managed(True)
+        cursor = connection.cursor()
+        
+        # Perform the SQL
+        if output:
+            write_sql(sql)
+        execute_sql(cursor, sql)
+        
+        transaction.commit()
+        transaction.leave_transaction_management()
+    except Exception, ex:
+        transaction.rollback()
+        raise ex
+
+def execute_test_sql(start, end, sql, debug=False):
+    """
+    Execute a test SQL sequence. This method also creates and destroys the 
+    database tables required by the models registered against the test application.
+    
+    start and end are the start- and end-point states of the application cache.
+    
+    sql is the list of sql statements to execute.
+    
+    cleanup is a list of extra sql statements required to clean up. This is
+    primarily for any extra m2m tables that were added during a test that won't 
+    be cleaned up by Django's sql_delete() implementation.
+    
+    debug is a helper flag. It displays the ALL the SQL that would be executed,
+    (including setup and teardown SQL), and executes the Django-derived setup/teardown
+    SQL.
+    """    
+    # Set up the initial state of the app cache
+    cache.app_models['tests'] = copy.deepcopy(start)
+        
+    # 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)
+    create_test_data(models.get_models(evo_test))
+            
+    # Set the app cache to the end state
+    cache.app_models['tests'] = copy.deepcopy(end)
+    
+    try:
+        # Execute the test sql
+        if debug:
+            write_sql(sql)
+        else:
+            execute_transaction(sql, output=True)
+    finally:
+        # Cleanup the apps.
+        if debug:
+            print sql_delete(evo_test, style)
+        else:
+            execute_transaction(sql_delete(evo_test, style), output=debug)
+    
+def create_test_data(app_models):
+    deferred_models = []
+    deferred_fields = {}
+    for model in app_models:
+        params = {}
+        deferred = False
+        for field in model._meta.fields:
+            if not deferred:
+                if type(field) == models.ForeignKey or type(field) == models.ManyToManyField:
+                    related_model = field.rel.to
+                    if related_model.objects.count():
+                        related_instance = related_model.objects.all()[0]
+                    else:
+                        if field.null == False:
+                            # Field cannot be null yet the related object hasn't been created yet
+                            # Defer the creation of this model
+                            deferred = True
+                            deferred_models.append(model)
+                        else:
+                            # Field cannot be set yet but null is acceptable for the moment
+                            deferred_fields[type(model)] = deferred_fields.get(type(model), []).append(field)
+                            related_instance = None
+                    if not deferred:
+                        if type(field) == models.ForeignKey:
+                            params[field.name] = related_instance
+                        else:
+                            params[field.name] = [related_instance]
+                else:
+                    params[field.name] = DEFAULT_TEST_ATTRIBUTE_VALUES[type(field)]
+
+        if not deferred:
+            model(**params).save()
+    
+    # Create all deferred models.
+    if deferred_models:
+        create_test_data(deferred_models)
+        
+    # All models should be created (Not all deferred fields have been populated yet)
+    # Populate deferred fields that we know about.
+    # Here lies untested code!
+    if deferred_fields:
+        for model, field_list in deferred_fields.items():
+            for field in field_list:
+                related_model = field.rel.to
+                related_instance = related_model.objects.all()[0]
+                if type(field) == models.ForeignKey:
+                    setattr(model, field.name, related_instance) 
+                else:
+                    getattr(model, field.name).add(related_instance)
+            model.save()
+    
+def test_sql_mapping(test_field_name):
+    engine = settings.DATABASE_ENGINE
+    sql_for_engine = __import__('django_evolution.tests.db.%s' % (settings.DATABASE_ENGINE), {}, {}, [''])
+    return getattr(sql_for_engine, test_field_name)
+
+
+def deregister_models():
+    "Clear the test section of the app cache"
+    del cache.app_models['tests']
+    
\ No newline at end of file
diff --git a/lib/django_evolution/utils.py b/lib/django_evolution/utils.py
new file mode 100644
index 0000000..e210898
--- /dev/null
+++ b/lib/django_evolution/utils.py
@@ -0,0 +1,22 @@
+from django_evolution.db import evolver
+
+def write_sql(sql):
+    "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]))
+        else:
+            print unicode(statement)
+
+def execute_sql(cursor, sql):
+    """
+    Execute a list of SQL statements on the provided cursor, unrolling 
+    parameters as required
+    """
+    for statement in sql:
+        if isinstance(statement, tuple):
+            if not statement[0].startswith('--'):
+                cursor.execute(*statement)
+        else:
+            if not statement.startswith('--'):
+                cursor.execute(statement)  
diff --git a/registration/management/commands/__init__.py b/lib/registration/__init__.py
similarity index 100%
rename from registration/management/commands/__init__.py
rename to lib/registration/__init__.py
diff --git a/registration/admin.py b/lib/registration/admin.py
similarity index 100%
rename from registration/admin.py
rename to lib/registration/admin.py
diff --git a/registration/forms.py b/lib/registration/forms.py
similarity index 100%
rename from registration/forms.py
rename to lib/registration/forms.py
diff --git a/registration/locale/ar/LC_MESSAGES/django.mo b/lib/registration/locale/ar/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/ar/LC_MESSAGES/django.mo
rename to lib/registration/locale/ar/LC_MESSAGES/django.mo
diff --git a/registration/locale/ar/LC_MESSAGES/django.po b/lib/registration/locale/ar/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/ar/LC_MESSAGES/django.po
rename to lib/registration/locale/ar/LC_MESSAGES/django.po
diff --git a/registration/locale/bg/LC_MESSAGES/django.mo b/lib/registration/locale/bg/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/bg/LC_MESSAGES/django.mo
rename to lib/registration/locale/bg/LC_MESSAGES/django.mo
diff --git a/registration/locale/bg/LC_MESSAGES/django.po b/lib/registration/locale/bg/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/bg/LC_MESSAGES/django.po
rename to lib/registration/locale/bg/LC_MESSAGES/django.po
diff --git a/registration/locale/de/LC_MESSAGES/django.mo b/lib/registration/locale/de/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/de/LC_MESSAGES/django.mo
rename to lib/registration/locale/de/LC_MESSAGES/django.mo
diff --git a/registration/locale/de/LC_MESSAGES/django.po b/lib/registration/locale/de/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/de/LC_MESSAGES/django.po
rename to lib/registration/locale/de/LC_MESSAGES/django.po
diff --git a/registration/locale/el/LC_MESSAGES/django.mo b/lib/registration/locale/el/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/el/LC_MESSAGES/django.mo
rename to lib/registration/locale/el/LC_MESSAGES/django.mo
diff --git a/registration/locale/el/LC_MESSAGES/django.po b/lib/registration/locale/el/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/el/LC_MESSAGES/django.po
rename to lib/registration/locale/el/LC_MESSAGES/django.po
diff --git a/registration/locale/en/LC_MESSAGES/django.mo b/lib/registration/locale/en/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/en/LC_MESSAGES/django.mo
rename to lib/registration/locale/en/LC_MESSAGES/django.mo
diff --git a/registration/locale/en/LC_MESSAGES/django.po b/lib/registration/locale/en/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/en/LC_MESSAGES/django.po
rename to lib/registration/locale/en/LC_MESSAGES/django.po
diff --git a/registration/locale/es/LC_MESSAGES/django.mo b/lib/registration/locale/es/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/es/LC_MESSAGES/django.mo
rename to lib/registration/locale/es/LC_MESSAGES/django.mo
diff --git a/registration/locale/es/LC_MESSAGES/django.po b/lib/registration/locale/es/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/es/LC_MESSAGES/django.po
rename to lib/registration/locale/es/LC_MESSAGES/django.po
diff --git a/registration/locale/es_AR/LC_MESSAGES/django.mo b/lib/registration/locale/es_AR/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/es_AR/LC_MESSAGES/django.mo
rename to lib/registration/locale/es_AR/LC_MESSAGES/django.mo
diff --git a/registration/locale/es_AR/LC_MESSAGES/django.po b/lib/registration/locale/es_AR/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/es_AR/LC_MESSAGES/django.po
rename to lib/registration/locale/es_AR/LC_MESSAGES/django.po
diff --git a/registration/locale/fr/LC_MESSAGES/django.mo b/lib/registration/locale/fr/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/fr/LC_MESSAGES/django.mo
rename to lib/registration/locale/fr/LC_MESSAGES/django.mo
diff --git a/registration/locale/fr/LC_MESSAGES/django.po b/lib/registration/locale/fr/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/fr/LC_MESSAGES/django.po
rename to lib/registration/locale/fr/LC_MESSAGES/django.po
diff --git a/registration/locale/he/LC_MESSAGES/django.mo b/lib/registration/locale/he/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/he/LC_MESSAGES/django.mo
rename to lib/registration/locale/he/LC_MESSAGES/django.mo
diff --git a/registration/locale/he/LC_MESSAGES/django.po b/lib/registration/locale/he/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/he/LC_MESSAGES/django.po
rename to lib/registration/locale/he/LC_MESSAGES/django.po
diff --git a/registration/locale/is/LC_MESSAGES/django.mo b/lib/registration/locale/is/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/is/LC_MESSAGES/django.mo
rename to lib/registration/locale/is/LC_MESSAGES/django.mo
diff --git a/registration/locale/is/LC_MESSAGES/django.po b/lib/registration/locale/is/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/is/LC_MESSAGES/django.po
rename to lib/registration/locale/is/LC_MESSAGES/django.po
diff --git a/registration/locale/it/LC_MESSAGES/django.mo b/lib/registration/locale/it/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/it/LC_MESSAGES/django.mo
rename to lib/registration/locale/it/LC_MESSAGES/django.mo
diff --git a/registration/locale/it/LC_MESSAGES/django.po b/lib/registration/locale/it/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/it/LC_MESSAGES/django.po
rename to lib/registration/locale/it/LC_MESSAGES/django.po
diff --git a/registration/locale/ja/LC_MESSAGES/django.mo b/lib/registration/locale/ja/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/ja/LC_MESSAGES/django.mo
rename to lib/registration/locale/ja/LC_MESSAGES/django.mo
diff --git a/registration/locale/ja/LC_MESSAGES/django.po b/lib/registration/locale/ja/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/ja/LC_MESSAGES/django.po
rename to lib/registration/locale/ja/LC_MESSAGES/django.po
diff --git a/registration/locale/nl/LC_MESSAGES/django.mo b/lib/registration/locale/nl/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/nl/LC_MESSAGES/django.mo
rename to lib/registration/locale/nl/LC_MESSAGES/django.mo
diff --git a/registration/locale/nl/LC_MESSAGES/django.po b/lib/registration/locale/nl/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/nl/LC_MESSAGES/django.po
rename to lib/registration/locale/nl/LC_MESSAGES/django.po
diff --git a/registration/locale/pl/LC_MESSAGES/django.mo b/lib/registration/locale/pl/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/pl/LC_MESSAGES/django.mo
rename to lib/registration/locale/pl/LC_MESSAGES/django.mo
diff --git a/registration/locale/pl/LC_MESSAGES/django.po b/lib/registration/locale/pl/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/pl/LC_MESSAGES/django.po
rename to lib/registration/locale/pl/LC_MESSAGES/django.po
diff --git a/registration/locale/pt_BR/LC_MESSAGES/django.mo b/lib/registration/locale/pt_BR/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/pt_BR/LC_MESSAGES/django.mo
rename to lib/registration/locale/pt_BR/LC_MESSAGES/django.mo
diff --git a/registration/locale/pt_BR/LC_MESSAGES/django.po b/lib/registration/locale/pt_BR/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/pt_BR/LC_MESSAGES/django.po
rename to lib/registration/locale/pt_BR/LC_MESSAGES/django.po
diff --git a/registration/locale/ru/LC_MESSAGES/django.mo b/lib/registration/locale/ru/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/ru/LC_MESSAGES/django.mo
rename to lib/registration/locale/ru/LC_MESSAGES/django.mo
diff --git a/registration/locale/ru/LC_MESSAGES/django.po b/lib/registration/locale/ru/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/ru/LC_MESSAGES/django.po
rename to lib/registration/locale/ru/LC_MESSAGES/django.po
diff --git a/registration/locale/sr/LC_MESSAGES/django.mo b/lib/registration/locale/sr/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/sr/LC_MESSAGES/django.mo
rename to lib/registration/locale/sr/LC_MESSAGES/django.mo
diff --git a/registration/locale/sr/LC_MESSAGES/django.po b/lib/registration/locale/sr/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/sr/LC_MESSAGES/django.po
rename to lib/registration/locale/sr/LC_MESSAGES/django.po
diff --git a/registration/locale/sv/LC_MESSAGES/django.mo b/lib/registration/locale/sv/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/sv/LC_MESSAGES/django.mo
rename to lib/registration/locale/sv/LC_MESSAGES/django.mo
diff --git a/registration/locale/sv/LC_MESSAGES/django.po b/lib/registration/locale/sv/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/sv/LC_MESSAGES/django.po
rename to lib/registration/locale/sv/LC_MESSAGES/django.po
diff --git a/registration/locale/zh_CN/LC_MESSAGES/django.mo b/lib/registration/locale/zh_CN/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/zh_CN/LC_MESSAGES/django.mo
rename to lib/registration/locale/zh_CN/LC_MESSAGES/django.mo
diff --git a/registration/locale/zh_CN/LC_MESSAGES/django.po b/lib/registration/locale/zh_CN/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/zh_CN/LC_MESSAGES/django.po
rename to lib/registration/locale/zh_CN/LC_MESSAGES/django.po
diff --git a/registration/locale/zh_TW/LC_MESSAGES/django.mo b/lib/registration/locale/zh_TW/LC_MESSAGES/django.mo
similarity index 100%
rename from registration/locale/zh_TW/LC_MESSAGES/django.mo
rename to lib/registration/locale/zh_TW/LC_MESSAGES/django.mo
diff --git a/registration/locale/zh_TW/LC_MESSAGES/django.po b/lib/registration/locale/zh_TW/LC_MESSAGES/django.po
similarity index 100%
rename from registration/locale/zh_TW/LC_MESSAGES/django.po
rename to lib/registration/locale/zh_TW/LC_MESSAGES/django.po
diff --git a/registration/__init__.py b/lib/registration/management/__init__.py
similarity index 100%
copy from registration/__init__.py
copy to lib/registration/management/__init__.py
diff --git a/registration/__init__.py b/lib/registration/management/commands/__init__.py
similarity index 100%
rename from registration/__init__.py
rename to lib/registration/management/commands/__init__.py
diff --git a/registration/management/commands/cleanupregistration.py b/lib/registration/management/commands/cleanupregistration.py
similarity index 100%
rename from registration/management/commands/cleanupregistration.py
rename to lib/registration/management/commands/cleanupregistration.py
diff --git a/registration/models.py b/lib/registration/models.py
similarity index 100%
rename from registration/models.py
rename to lib/registration/models.py
diff --git a/registration/signals.py b/lib/registration/signals.py
similarity index 100%
rename from registration/signals.py
rename to lib/registration/signals.py
diff --git a/registration/tests.py b/lib/registration/tests.py
similarity index 100%
rename from registration/tests.py
rename to lib/registration/tests.py
diff --git a/registration/urls.py b/lib/registration/urls.py
similarity index 100%
rename from registration/urls.py
rename to lib/registration/urls.py
diff --git a/registration/views.py b/lib/registration/views.py
similarity index 100%
rename from registration/views.py
rename to lib/registration/views.py
diff --git a/lib/reversion/__init__.py b/lib/reversion/__init__.py
new file mode 100644
index 0000000..bb6a02b
--- /dev/null
+++ b/lib/reversion/__init__.py
@@ -0,0 +1,11 @@
+"""
+Transactional version control for Django models.
+
+Project sponsored by Etianen.com
+
+<http://www.etianen.com/>
+"""
+
+
+from reversion.registration import register, unregister, is_registered
+from reversion.revisions import revision
\ No newline at end of file
diff --git a/lib/reversion/admin.py b/lib/reversion/admin.py
new file mode 100644
index 0000000..046a775
--- /dev/null
+++ b/lib/reversion/admin.py
@@ -0,0 +1,264 @@
+"""Admin extensions for Reversion."""
+
+
+from django.db import models, transaction
+from django.conf.urls.defaults import *
+from django.conf import settings
+from django.contrib import admin
+from django.contrib.admin.models import LogEntry, DELETION
+from django.contrib.contenttypes.generic import GenericInlineModelAdmin, GenericRelation
+from django.contrib.contenttypes.models import ContentType
+from django.forms.formsets import all_valid
+from django.http import Http404, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.utils.dateformat import format
+from django.utils.encoding import force_unicode
+from django.utils.html import mark_safe
+from django.utils.text import capfirst
+from django.utils.translation import ugettext as _
+
+from reversion.helpers import deserialized_model_to_dict
+from reversion.registration import is_registered, register
+from reversion.revisions import revision
+from reversion.models import Version
+
+
+class VersionAdmin(admin.ModelAdmin):
+    
+    """Abstract admin class for handling version controlled models."""
+
+    revision_form_template = "reversion/revision_form.html"
+    object_history_template = "reversion/object_history.html"
+    change_list_template = "reversion/change_list.html"
+    recover_list_template = "reversion/recover_list.html"
+    recover_form_template = "reversion/recover_form.html"
+    
+    def _autoregister(self, model, follow=None):
+        """Registers a model with reversion, if required."""
+        if not is_registered(model):
+            follow = follow or []
+            for parent_cls, field in model._meta.parents.items():
+                follow.append(field.name)
+                self._autoregister(parent_cls)
+            register(model, follow=follow)
+    
+    def __init__(self, *args, **kwargs):
+        """Initializes the VersionAdmin"""
+        super(VersionAdmin, self).__init__(*args, **kwargs)
+        # Automatically register models if required.
+        if not is_registered(self.model):
+            inline_fields = []
+            for inline in self.inlines:
+                inline_model = inline.model
+                self._autoregister(inline_model)
+                if issubclass(inline, (admin.TabularInline, admin.StackedInline)):
+                    fk_name = inline.fk_name
+                    if not fk_name:
+                        for field in inline_model._meta.fields:
+                            if isinstance(field, models.ForeignKey) and issubclass(self.model, field.rel.to):
+                                fk_name = field.name
+                    accessor = inline_model._meta.get_field(fk_name).rel.related_name or inline_model.__name__.lower() + "_set"
+                    inline_fields.append(accessor)
+                elif issubclass(inline, GenericInlineModelAdmin):
+                    ct_field = inline.ct_field
+                    ct_fk_field = inline.ct_fk_field
+                    for field in self.model._meta.many_to_many:
+                        if isinstance(field, GenericRelation) and field.object_id_field_name == ct_fk_field and field.content_type_field_name == ct_field:
+                            inline_fields.append(field.name)
+            self._autoregister(self.model, inline_fields)
+    
+    # TODO: This is deprecated in Django 1.1
+    def __call__(self, request, url):
+        """Adds additional functionality to the admin class."""
+        path = url or ""
+        parts = path.strip("/").split("/")
+        if len(parts) == 3 and parts[1] == "history":
+            object_id = parts[0]
+            version_id = parts[2]
+            return self.revision_view(request, object_id, version_id)
+        elif len(parts) == 1 and parts[0] == "recover":
+            return self.recover_list_view(request)
+        elif len(parts) == 2 and parts[0] == "recover":
+            return self.recover_view(request, parts[1])
+        else:
+            return super(VersionAdmin, self).__call__(request, url)
+    
+    def get_urls(self):
+        """Returns the additional urls used by the Reversion admin."""
+        urls = super(VersionAdmin, self).get_urls()
+        reversion_urls = patterns("",
+                                  url("^recover/$", self.recover_list_view),
+                                  url("^recover/([^/]+)/$", self.recover_view),
+                                  url("^([^/]+)/history/([^/]+)/$", self.revision_view),)
+        return reversion_urls + urls
+    
+    def log_addition(self, request, object):
+        """Sets the version meta information."""
+        super(VersionAdmin, self).log_addition(request, object)
+        revision.user = request.user
+        
+    def log_change(self, request, object, message):
+        """Sets the version meta information."""
+        super(VersionAdmin, self).log_change(request, object, message)
+        revision.user = request.user
+        revision.comment = message
+    
+    def recover_list_view(self, request, extra_context=None):
+        """Displays a deleted model to allow recovery."""
+        model = self.model
+        opts = model._meta
+        app_label = opts.app_label
+        alive_ids = [unicode(id) for id, in model._default_manager.all().values_list("pk")]
+        deleted = Version.objects.get_deleted(self.model)
+        context = {"opts": opts,
+                   "app_label": app_label,
+                   "module_name": capfirst(opts.verbose_name),
+                   "title": _("Recover deleted %(name)s") % {"name": opts.verbose_name_plural},
+                   "deleted": deleted}
+        extra_context = extra_context or {}
+        context.update(extra_context)
+        return render_to_response(self.recover_list_template, context, RequestContext(request))
+        
+    def render_revision_form(self, request, obj, version, revision, context, template, redirect_url):
+        """Renders the object revision form."""
+        model = self.model
+        opts = model._meta
+        object_id = obj.pk
+        ordered_objects = opts.get_ordered_objects()
+        app_label = opts.app_label
+        object_version = version.object_version
+        ModelForm = self.get_form(request, obj)
+        formsets = []
+        if request.method == "POST":
+            form = ModelForm(request.POST, request.FILES, instance=obj)
+            if form.is_valid():
+                form_validated = True
+                new_object = self.save_form(request, form, change=True)
+            else:
+                form_validated = False
+                new_object = obj
+            for FormSet in self.get_formsets(request, new_object):
+                formset = FormSet(request.POST, request.FILES,
+                                  instance=new_object)
+                formsets.append(formset)
+            if all_valid(formsets) and form_validated:
+                self.save_model(request, new_object, form, change=True)
+                form.save_m2m()
+                for formset in formsets:
+                    self.save_formset(request, form, formset, change=True)
+                change_message = _(u"Reverted to previous version, saved on %(datetime)s") % {"datetime": format(version.revision.date_created, _(settings.DATETIME_FORMAT))}
+                self.log_change(request, new_object, change_message)
+                self.message_user(request, _(u'The %(model)s "%(name)s" was reverted successfully. You may edit it again below.') % {"model": opts.verbose_name, "name": unicode(obj)})
+                return HttpResponseRedirect(redirect_url)
+        else:
+            initial = deserialized_model_to_dict(object_version, revision)
+            form = ModelForm(instance=obj, initial=initial)
+            for FormSet in self.get_formsets(request, obj):
+                formset = FormSet(instance=obj)
+                try:
+                    attname = FormSet.fk.attname
+                except AttributeError:
+                    # This is a GenericInlineFormset, or similar.
+                    attname = FormSet.ct_fk_field_name
+                pk_name = FormSet.model._meta.pk.name
+                initial_overrides = dict([(getattr(version.object, pk_name), version) for version in revision if version.object.__class__ == FormSet.model and unicode(getattr(version.object, attname)) == unicode(object_id)])
+                initial = formset.initial
+                for initial_row in initial:
+                    pk = initial_row[pk_name]
+                    if pk in initial_overrides:
+                         initial_row.update(deserialized_model_to_dict(initial_overrides[pk], revision))
+                         del initial_overrides[pk]
+                    else:
+                       initial_row["DELETE"] = True
+                initial.extend([deserialized_model_to_dict(override, revision) for override in initial_overrides.values()])
+                # HACK: no way to specify initial values.
+                formset._total_form_count = len(initial)
+                formset.initial = initial
+                formset._construct_forms()
+                formsets.append(formset)
+        # Generate the context.
+        adminForm = admin.helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
+        media = self.media + adminForm.media
+        inline_admin_formsets = []
+        for inline, formset in zip(self.inline_instances, formsets):
+            fieldsets = list(inline.get_fieldsets(request, obj))
+            inline_admin_formset = admin.helpers.InlineAdminFormSet(inline, formset, fieldsets)
+            inline_admin_formsets.append(inline_admin_formset)
+            media = media + inline_admin_formset.media
+        context.update({"adminform": adminForm,
+                        "object_id": obj.pk,
+                        "original": obj,
+                        "is_popup": False,
+                        "media": mark_safe(media),
+                        "inline_admin_formsets": inline_admin_formsets,
+                        "errors": admin.helpers.AdminErrorList(form, formsets),
+                        "root_path": self.admin_site.root_path,
+                        "app_label": app_label,
+                        "add": False,
+                        "change": True,
+                        "has_add_permission": self.has_add_permission(request),
+                        "has_change_permission": self.has_change_permission(request, obj),
+                        "has_delete_permission": self.has_delete_permission(request, obj),
+                        "has_file_field": True, # FIXME - this should check if form or formsets have a FileField,
+                        "has_absolute_url": hasattr(self.model, "get_absolute_url"),
+                        "ordered_objects": ordered_objects,
+                        "form_url": mark_safe(request.path),
+                        "opts": opts,
+                        "content_type_id": ContentType.objects.get_for_model(self.model).id,
+                        "save_as": self.save_as,
+                        "save_on_top": self.save_on_top,
+                        "root_path": self.admin_site.root_path,})
+        return render_to_response(template, context, RequestContext(request))
+        
+    def recover_view(self, request, version_id, extra_context=None):
+        """Displays a form that can recover a deleted model."""
+        model = self.model
+        opts = model._meta
+        app_label = opts.app_label
+        version = get_object_or_404(Version, pk=version_id)
+        object_id = version.object_id
+        content_type = ContentType.objects.get_for_model(self.model)
+        obj = version.object_version.object
+        revision = [related_version.object_version for related_version in version.revision.version_set.all()]
+        context = {"title": _("Recover %s") % force_unicode(version.object_repr),}
+        extra_context = extra_context or {}
+        context.update(extra_context)
+        return self.render_revision_form(request, obj, version, revision, context, self.recover_form_template, "../../%s/" % object_id)
+    recover_view = transaction.commit_on_success(revision.create_on_success(recover_view))
+        
+    def revision_view(self, request, object_id, version_id, extra_context=None):
+        """Displays the contents of the given revision."""
+        model = self.model
+        content_type = ContentType.objects.get_for_model(model)
+        opts = model._meta
+        app_label = opts.app_label
+        obj = get_object_or_404(self.model, pk=object_id)
+        version = get_object_or_404(Version, pk=version_id)
+        # Generate the form.
+        revision = [related_version.object_version for related_version in version.revision.version_set.all()]
+        context = {"title": _("Revert %(name)s") % {"name": opts.verbose_name},}
+        extra_context = extra_context or {}
+        context.update(extra_context)
+        return self.render_revision_form(request, obj, version, revision, context, self.revision_form_template, "../../")
+    revision_view = transaction.commit_on_success(revision.create_on_success(revision_view))
+    
+    # Wrap the data-modifying views in revisions.
+    add_view = transaction.commit_on_success(revision.create_on_success(admin.ModelAdmin.add_view))
+    change_view = transaction.commit_on_success(revision.create_on_success(admin.ModelAdmin.change_view))
+    delete_view = transaction.commit_on_success(revision.create_on_success(admin.ModelAdmin.delete_view))
+    
+    def changelist_view(self, request, extra_context=None):
+        """Renders the modified change list."""
+        extra_context = extra_context or {}
+        extra_context.update({"has_change_permission": self.has_change_permission(request)})
+        return super(VersionAdmin, self).changelist_view(request, extra_context)
+    
+    def history_view(self, request, object_id, extra_context=None):
+        """Renders the history view."""
+        extra_context = extra_context or {}
+        content_type = ContentType.objects.get_for_model(self.model)
+        obj = content_type.get_object_for_this_type(pk=object_id) 
+        action_list = Version.objects.get_for_object(obj)
+        extra_context.update({"action_list": action_list})
+        return super(VersionAdmin, self).history_view(request, object_id, extra_context)
\ No newline at end of file
diff --git a/lib/reversion/helpers.py b/lib/reversion/helpers.py
new file mode 100644
index 0000000..6dcb5dc
--- /dev/null
+++ b/lib/reversion/helpers.py
@@ -0,0 +1,141 @@
+"""A number of useful helper functions to automate common tasks."""
+
+
+from django.contrib import admin
+from django.contrib.admin.sites import NotRegistered
+from django.db import models
+from django.db.models.query import QuerySet  
+from django.forms.models import model_to_dict
+
+from reversion.registration import get_registration_info
+
+
+def add_to_revision(instance, revision_set):
+    """
+    Calculates the objects that should be included in the same revision as the
+    given instance.
+    
+    All calculated objects are added to the `revision_set`.
+    """
+    # Prevent recursion.
+    if instance in revision_set:
+        return
+    revision_set.add(instance)
+    # Follow relations.
+    fields, follow, format = get_registration_info(instance.__class__)
+    if follow:
+        for relationship in follow:
+            try:
+                # Clear foreign key cache.
+                related_field = instance._meta.get_field(relationship)
+                if isinstance(related_field, models.ForeignKey):
+                    if hasattr(instance, related_field.get_cache_name()):
+                        delattr(instance, related_field.get_cache_name())
+            except models.FieldDoesNotExist:
+                pass
+            related = getattr(instance, relationship, None)
+            if isinstance(related, models.Model):
+                add_to_revision(related, revision_set) 
+            elif isinstance(related, (models.Manager, QuerySet)):
+                for related_obj in related.all():
+                    add_to_revision(related_obj, revision_set)
+            elif related is not None:
+                raise TypeError, "Cannot follow the relationship '%s', unexpected type %s" % (relationship, type(related).__name__)
+    
+
+def deserialized_model_to_dict(deserialized_model, revision_data):
+    """
+    Converts a deserialized model to a dictionary.
+    
+    In order to properly follow any parent links, this method requires that the
+    full revision data is also specified.
+    """
+    model = deserialized_model.object
+    result = model_to_dict(model)
+    result.update(deserialized_model.m2m_data)
+    # Add parent data.
+    for parent_class, field in model._meta.parents.items():
+        attname = field.attname
+        attvalue = getattr(model, attname)
+        pk_name = parent_class._meta.pk.attname
+        for deserialized_model in revision_data:
+            parent = deserialized_model.object
+            if parent_class == parent.__class__ and unicode(getattr(parent, pk_name)) == unicode(getattr(model, attname)):
+                result.update(deserialized_model_to_dict(deserialized_model, revision_data))
+    return result
+
+
+def version_to_dict(version):
+    """
+    Returns the serialiazed model contained in the version as dictionary of
+    property names and values.
+    """
+    revision = version.revision
+    object_version = version.object_version
+    return deserialized_model_to_dict(object_version, revision)
+
+
+def patch_admin(model, admin_site=None):
+    """
+    Enables version control with full admin integration for a model that has
+    already been registered with the django admin site.
+    
+    This is excellent for adding version control to existing Django contrib
+    applications. 
+    """
+    from reversion.admin import VersionAdmin
+    admin_site = admin_site or admin.site
+    try:
+        ModelAdmin = admin_site._registry[model].__class__
+    except KeyError:
+        raise NotRegistered, "The model %s has not been registered with the admin site." % model
+    # Unregister existing admin class.
+    admin_site.unregister(model)
+    # Register patched admin class.
+    class PatchedModelAdmin(VersionAdmin, ModelAdmin):
+        pass
+    admin_site.register(model, PatchedModelAdmin)
+
+
+# Patch generation methods, only available if the google-diff-match-patch
+# library is installed.
+#
+# http://code.google.com/p/google-diff-match-patch/
+
+try:
+    from diff_match_patch import diff_match_patch
+except ImportError:
+    pass 
+else:
+    dmp = diff_match_patch()
+    
+    def generate_diffs(old_version, new_version, field_name):
+        """
+        Generates a diff array of the named field between the two versions.
+        """
+        # Extract the text from the versions.
+        old_dict = version_to_dict(old_version)
+        new_dict = version_to_dict(new_version)
+        old_text = old_dict[field_name]
+        new_text = new_dict[field_name]
+        # Generate the patch.
+        diffs = dmp.diff_main(old_text, new_text)
+        return diffs
+    
+    def generate_patch(old_version, new_version, field_name):
+        """
+        Generates a text patch of the named field between the two versions.
+        """
+        diffs = generate_diffs(old_version, new_version, field_name)
+        patch = dmp.patch_make(diffs)
+        return dmp.patch_toText(patch)
+        
+    def generate_patch_html(old_version, new_version, field_name):
+        """
+        Generates a pretty html version of the differences between the named 
+        field in two versions.
+        """
+        diffs = generate_diffs(old_version, new_version, field_name)
+        return dmp.diff_prettyHtml(diffs)
+    
+    
\ No newline at end of file
diff --git a/lib/reversion/managers.py b/lib/reversion/managers.py
new file mode 100644
index 0000000..bede23a
--- /dev/null
+++ b/lib/reversion/managers.py
@@ -0,0 +1,60 @@
+"""Model managers for Reversion."""
+
+try:
+    set
+except NameError:
+    from sets import Set as set  # Python 2.3 fallback.
+
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+
+
+class VersionManager(models.Manager):
+    
+    """Manager for Version models."""
+    
+    def get_for_object(self, object):
+        """Returns all the versions of the given Revision, ordered by date created."""
+        content_type = ContentType.objects.get_for_model(object)
+        return self.filter(content_type=content_type, object_id=unicode(object.pk)).order_by("pk").select_related().order_by("pk")
+    
+    def get_unique_for_object(self,obj):
+        """Returns unique versions associated with the object."""
+        versions = self.get_for_object(obj)
+        changed_versions = []
+        known_serialized_data = set()
+        for version in versions:
+            serialized_data = version.serialized_data
+            if serialized_data in known_serialized_data:
+                continue
+            known_serialized_data.add(serialized_data)
+            changed_versions.append(version)
+        return changed_versions
+    
+    def get_for_date(self, object, date):
+        """Returns the latest version of an object for the given date."""
+        try:
+            return self.get_for_object(object).filter(revision__date_created__lte=date).order_by("-pk")[0]
+        except IndexError:
+            raise self.model.DoesNotExist
+    
+    def get_deleted(self, model_class):
+        """Returns all the deleted versions for the given model class."""
+        live_ids = [unicode(row[0]) for row in model_class._default_manager.all().values_list("pk")]
+        content_type = ContentType.objects.get_for_model(model_class)
+        deleted_ids = self.filter(content_type=content_type).exclude(object_id__in=live_ids).order_by().values_list("object_id").distinct()
+        deleted = []
+        for object_id, in deleted_ids:
+            deleted.append(self.get_deleted_object(model_class, object_id))
+        return deleted
+    
+    def get_deleted_object(self, model_class, object_id):
+        """
+        Returns the version corresponding to the deletion of the object with
+        the given id.
+        """
+        try:
+            content_type = ContentType.objects.get_for_model(model_class)
+            return self.filter(content_type=content_type, object_id=unicode(object_id)).order_by("-pk").select_related()[0]
+        except IndexError:
+            raise self.model.DoesNotExist
\ No newline at end of file
diff --git a/lib/reversion/middleware.py b/lib/reversion/middleware.py
new file mode 100644
index 0000000..7d7a9a9
--- /dev/null
+++ b/lib/reversion/middleware.py
@@ -0,0 +1,27 @@
+"""Middleware used by Reversion."""
+
+
+import sys
+
+from reversion.revisions import revision
+
+
+class RevisionMiddleware(object):
+    
+    """Wraps the entire request in a Revision."""
+    
+    def process_request(self, request):
+        """Starts a new revision."""
+        revision.start()
+        if request.user.is_authenticated():
+            revision.user = request.user
+        
+    def process_response(self, request, response):
+        """Closes the revision."""
+        if revision.is_active():
+            revision.end()
+        return response
+        
+    def process_exception(self, request, exception):
+        """Closes the revision."""
+        revision.invalidate()
\ No newline at end of file
diff --git a/lib/reversion/models.py b/lib/reversion/models.py
new file mode 100644
index 0000000..1584976
--- /dev/null
+++ b/lib/reversion/models.py
@@ -0,0 +1,111 @@
+"""Database models used by Reversion."""
+
+
+try:
+    set
+except NameError:
+    from sets import Set as set  # Python 2.3 fallback.
+
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.core import serializers
+from django.db import models
+
+from reversion.helpers import add_to_revision
+from reversion.managers import VersionManager
+from reversion.registration import get_registration_info
+
+
+class Revision(models.Model):
+    
+    """A group of related object versions."""
+    
+    date_created = models.DateTimeField(auto_now_add=True,
+                                        help_text="The date and time this revision was created.")
+
+    user = models.ForeignKey(User,
+                             blank=True,
+                             null=True,
+                             help_text="The user who created this revision.")
+    
+    comment = models.TextField(blank=True,
+                               null=True,
+                               help_text="A text comment on this revision.")
+    
+    def revert(self, delete=False):
+        """Reverts all objects in this revision."""
+        versions = self.version_set.all()
+        for version in versions:
+            version.revert()
+        if delete:
+            # Get a set of all objects in this revision.
+            old_revision_set = set([version.latest_object_version for version in versions])
+            # Calculate the set of all objects that would be in the revision now.
+            current_revision_set = set()
+            for latest_object_version in old_revision_set:
+                add_to_revision(latest_object_version, current_revision_set)
+            for current_object in current_revision_set:
+                if not current_object in old_revision_set:
+                    current_object.delete()
+            
+    def __unicode__(self):
+        """Returns a unicode representation."""
+        return u", ".join([unicode(version)
+                           for version in self.version_set.all()])
+            
+
+class Version(models.Model):
+    
+    """A saved version of a database model."""
+    
+    objects = VersionManager()
+    
+    revision = models.ForeignKey(Revision,
+                                 help_text="The revision that contains this version.")
+    
+    object_id = models.TextField(help_text="Primary key of the model under version control.")
+    
+    content_type = models.ForeignKey(ContentType,
+                                     help_text="Content type of the model under version control.")
+    
+    format = models.CharField(max_length=255,
+                              help_text="The serialization format used by this model.")
+    
+    serialized_data = models.TextField(help_text="The serialized form of this version of the model.")
+    
+    object_repr = models.TextField(help_text="A string representation of the object.")
+    
+    def get_object_version(self):
+        """Returns the stored version of the model."""
+        data = self.serialized_data
+        if isinstance(data, unicode):
+            data = data.encode("utf8")
+        return list(serializers.deserialize(self.format, data))[0]
+    
+    object_version = property(get_object_version,
+                              doc="The stored version of the model.")
+       
+    def get_latest_object_version(self):
+        """
+        Returns the latest version of the stored object.
+        
+        If the object no longer exists, returns None.
+        """
+        model_class = self.content_type.model_class()
+        try:
+            return model_class._default_manager.get(pk=self.object_id)
+        except model_class.DoesNotExist:
+            return None
+        
+    latest_object_version = property(get_latest_object_version,
+                              doc="The latest version of the model.")
+       
+    def revert(self):
+        """Recovers the model in this version."""
+        self.object_version.save()
+        
+    def __unicode__(self):
+        """Returns a unicode representation."""
+        return self.object_repr
+    
+    
\ No newline at end of file
diff --git a/lib/reversion/registration.py b/lib/reversion/registration.py
new file mode 100644
index 0000000..ed0fc30
--- /dev/null
+++ b/lib/reversion/registration.py
@@ -0,0 +1,59 @@
+"""Functions for registering and unregistering models with Reversion."""
+
+
+from django.db import models
+from django.db.models.signals import post_save
+
+from reversion.storage import VersionFileStorageWrapper
+
+
+registered_models = {}
+
+
+class RegistrationError(Exception):
+    
+    """Exception thrown when registration with Reversion goes wrong."""
+
+    pass
+
+
+def register(model_class, fields=None, follow=None, format="xml"):
+    """Registers a model for version control."""
+    from reversion.revisions import revision
+    if is_registered(model_class):
+        raise RegistrationError, "%s has already been registered with Reversion." % model_class.__name__
+    registered_models[model_class] = (fields, follow, format)
+    for field in model_class._meta.fields:
+        if (fields is None or field.name in fields) and isinstance(field, models.FileField):
+            field.storage = VersionFileStorageWrapper(field.storage)
+    post_save.connect(revision.post_save_receiver, model_class)
+
+
+def is_registered(model_class):
+    """Checks whether the given model has been registered."""
+    return model_class in registered_models
+        
+        
+def get_registration_info(model_class):
+    """Returns the registration information for the given model class."""
+    try:
+        return registered_models[model_class]
+    except KeyError:
+        raise RegistrationError, "%s has not been registered with Reversion." % model_class.__name__
+        
+    
+def unregister(model_class):
+    """Removes a model from version control."""
+    from reversion.revisions import revision
+    try:
+        fields, follow, format = registered_models.pop(model_class)
+    except KeyError:
+        raise RegistrationError, "%s has not been registered with Reversion." % model_class.__name__
+    else:
+        for field in model_class._meta.fields:
+            if (fields is None or field in fields) and isinstance(field, models.FileField):
+                field.storage = VersionFileStorageWrapper(field.storage)
+        post_save.disconnect(revision.post_save_receiver, model_class)
+        
+        
+        
\ No newline at end of file
diff --git a/lib/reversion/revisions.py b/lib/reversion/revisions.py
new file mode 100644
index 0000000..3a1abf6
--- /dev/null
+++ b/lib/reversion/revisions.py
@@ -0,0 +1,188 @@
+"""Revision management for Reversion."""
+
+
+try:
+    set
+except NameError:
+    from sets import Set as set  # Python 2.3 fallback.
+
+import sys
+
+try:
+    from threading import local
+except ImportError:
+    from django.utils._threading_local import local  # Python 2.3 fallback.
+
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+
+from django.contrib.contenttypes.models import ContentType
+from django.core import serializers
+from django.db import models
+from django.db.models.query import QuerySet
+
+from reversion.helpers import add_to_revision
+from reversion.models import Revision, Version
+from reversion.registration import get_registration_info
+
+
+class RevisionManagementError(Exception):
+    
+    """
+    Exception that is thrown when something goes wrong with revision managment.
+    """
+    
+    pass
+
+
+class RevisionManager(local):
+    
+    """Manages the state of the current revision."""
+    
+    def __init__(self):
+        """Initializes the RevisionManager."""
+        self._clear()
+    
+    def _clear(self):
+        """Puts the revision manager back into its default state."""
+        self._versions = set()
+        self._user = None
+        self._comment = None
+        self._depth = 0
+        self._is_invalid = False
+        self._meta = []
+        
+    def start(self):
+        """Begins a revision."""
+        self._depth += 1
+        
+    def is_active(self):
+        """Returns whether there is an active revision for this thread."""
+        return self._depth > 0
+    
+    def _assert_active(self):
+        """Checks for an active revision, throwning an exception if none."""
+        if not self.is_active():
+            raise RevisionManagementError, "There is no active revision for this thread."
+        
+    def _add(self, obj):
+        """
+        Adds an object to the current revision.
+        
+        If `fields` is specified, then only the named fields will be serialized.
+        
+        If `follow` is specified, then the named foreign relationships will also
+        be included in the revision.  `follow` can be specified as a list of
+        relationship names, or as a dictionary mapping relationship names to
+        a list of fields to be serialized.
+        """
+        self._assert_active()
+        self._versions.add(obj)
+        
+    def set_user(self, user):
+        """Sets the user for the current revision"""
+        self._assert_active()
+        self._user = user
+        
+    def get_user(self):
+        """Gets the user for the current revision."""
+        self._assert_active()
+        return self._user
+    
+    user = property(get_user,
+                    set_user,
+                    doc="The user for the current revision.")
+        
+    def set_comment(self, comment):
+        """Sets the comment for the current revision"""
+        self._assert_active()
+        self._comment = comment
+        
+    def get_comment(self):
+        """Gets the comment for the current revision."""
+        self._assert_active()
+        return self._comment
+    
+    comment = property(get_comment,
+                       set_comment,
+                       doc="The comment for the current revision.")
+        
+    def add_meta(self, cls, **kwargs):
+        """Adds a class of mete information to the current revision."""
+        self._assert_active()
+        self._meta.append((cls, kwargs))
+        
+    def invalidate(self):
+        """Marks this revision as broken, so should not be commited."""
+        self._assert_active()
+        self._is_invalid = True
+        
+    def end(self):
+        """Ends a revision."""
+        self._assert_active()
+        self._depth -= 1
+        # Handle end of revision conditions here.
+        if self._depth == 0:
+            try:
+                if self._versions and not self._is_invalid:
+                    # Save a new revision.
+                    revision = Revision.objects.create(user=self._user,
+                                                       comment=self._comment)
+                    revision_set = set()
+                    # Follow relationships.
+                    for version in self._versions:
+                        add_to_revision(version, revision_set)
+                    # Save version models.
+                    for obj in revision_set:
+                        fields, follow, format = get_registration_info(obj.__class__)
+                        object_id = unicode(obj.pk)
+                        content_type = ContentType.objects.get_for_model(obj)
+                        serialized_data = serializers.serialize(format, [obj], fields=fields)
+                        Version.objects.create(revision=revision,
+                                               object_id=object_id,
+                                               content_type=content_type,
+                                               format=format,
+                                               serialized_data=serialized_data,
+                                               object_repr=unicode(obj))
+                    for meta_cls, meta_kwargs in self._meta:
+                        meta_cls._default_manager.create(revision=revision, **meta_kwargs)
+            finally:
+                self._clear()
+        return False
+        
+    def post_save_receiver(self, instance, sender, **kwargs):
+        """Saves a new version of registered models."""
+        if self.is_active():
+            self._add(instance)
+        
+    def __enter__(self):
+        """Enters a block of revision management."""
+        self.start()
+        
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Leaves a block of revision management."""
+        if exc_type is not None:
+            self.invalidate()
+        self.end()
+        return False
+        
+    def create_on_success(self, func):
+        """Creates a revision when the given function exist successfully."""
+        def _create_on_success(*args, **kwargs):
+            self.start()
+            try:
+                try:
+                    result = func(*args, **kwargs)
+                except:
+                    self.invalidate()
+                    raise
+            finally:
+                self.end()
+            return result
+        return wraps(func)(_create_on_success)
+
+        
+# A thread-safe shared revision manager.
+revision = RevisionManager()
\ No newline at end of file
diff --git a/lib/reversion/storage.py b/lib/reversion/storage.py
new file mode 100644
index 0000000..b44473d
--- /dev/null
+++ b/lib/reversion/storage.py
@@ -0,0 +1,18 @@
+"""File storage wrapper for version controlled file fields."""
+
+
+class VersionFileStorageWrapper(object):
+    
+    """Wrapper for file storage implementations that blocks file deletions."""
+    
+    def __init__(self, storage):
+        """Initializes the VersionFileStorageWrapper."""
+        self._storage = storage
+        
+    def __getattr__(self, name):
+        """Proxies storage mechanism to the wrapped implementation."""
+        return getattr(self._storage, name)
+    
+    def delete(self, name):
+        """File deletions are blocked for this storage class."""
+        pass
diff --git a/lib/reversion/templates/reversion/change_list.html b/lib/reversion/templates/reversion/change_list.html
new file mode 100644
index 0000000..b5c708b
--- /dev/null
+++ b/lib/reversion/templates/reversion/change_list.html
@@ -0,0 +1,14 @@
+{% extends "admin/change_list.html" %}
+{% load i18n %}
+
+
+{% block object-tools %}
+	<ul class="object-tools">
+		{% if has_change_permission %}
+			<li><a href="recover/" class="recoverlink">{% blocktrans with cl.opts.verbose_name_plural|escape as name %}Recover deleted {{name}}{% endblocktrans %}</a></li>
+		{% endif %}
+		{% if has_add_permission %}
+			<li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name|escape as name %}Add {{name}}{% endblocktrans %}</a></li>
+		{% endif %}
+	</ul>
+{% endblock %}
\ No newline at end of file
diff --git a/lib/reversion/templates/reversion/object_history.html b/lib/reversion/templates/reversion/object_history.html
new file mode 100644
index 0000000..2550012
--- /dev/null
+++ b/lib/reversion/templates/reversion/object_history.html
@@ -0,0 +1,35 @@
+{% extends "admin/object_history.html" %}
+{% load i18n %}
+
+
+{% block content %}
+	<div id="content-main">
+	
+		<p>{% blocktrans %}Choose a date from the list below to revert to a previous version of this object.{% endblocktrans %}</p>
+	
+		<div class="module">
+			{% if action_list %}
+			    <table id="change-history">
+			        <thead>
+				        <tr>
+				            <th scope="col">{% trans 'Date/time' %}</th>
+				            <th scope="col">{% trans 'User' %}</th>
+				            <th scope="col">{% trans 'Comment' %}</th>
+				        </tr>
+			        </thead>
+			        <tbody>
+				        {% for action in action_list %}
+					        <tr>
+					            <th scope="row"><a href="./{{action.pk}}/">{{action.revision.date_created|date:_("DATETIME_FORMAT")}}</a></th>
+					            <td>{{action.revision.user.username}}{% if action.revision.user.first_name %} ({{action.revision.user.first_name}} {{action.revision.user.last_name}}){% endif %}</td>
+					            <td>{{action.revision.comment|default:""}}</td>
+					        </tr>
+				        {% endfor %}
+			        </tbody>
+			    </table>
+			{% else %}
+			    <p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p>
+			{% endif %}
+		</div>
+	</div>
+{% endblock %}
\ No newline at end of file
diff --git a/lib/reversion/templates/reversion/recover_form.html b/lib/reversion/templates/reversion/recover_form.html
new file mode 100644
index 0000000..548ea30
--- /dev/null
+++ b/lib/reversion/templates/reversion/recover_form.html
@@ -0,0 +1,25 @@
+{% extends "reversion/revision_form.html" %}
+{% load i18n %}
+
+
+{% block extrahead %}
+    {{block.super.super.super}}
+    <script type="text/javascript" src="../../../../jsi18n/"></script>
+    {{media}}
+{% endblock %}
+
+
+{% block breadcrumbs %}
+	<div class="breadcrumbs">
+		<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
+		<a href="../../../">{{app_label|capfirst|escape}}</a> &rsaquo; 
+		<a href="../../">{{opts.verbose_name_plural|capfirst}}</a> &rsaquo;
+		<a href="../">{% blocktrans with opts.verbose_name as verbose_name %}Recover deleted {{verbose_name}}{% endblocktrans %}</a> &rsaquo;
+		{{title}}
+	</div>
+{% endblock %}
+
+
+{% block form_top %}
+	<p>{% blocktrans %}Press the save button below to recover this version of the object.{% endblocktrans %}</p>
+{% endblock %}
\ No newline at end of file
diff --git a/lib/reversion/templates/reversion/recover_list.html b/lib/reversion/templates/reversion/recover_list.html
new file mode 100644
index 0000000..640f620
--- /dev/null
+++ b/lib/reversion/templates/reversion/recover_list.html
@@ -0,0 +1,39 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+    <a href="../../../">{% trans 'Home' %}</a> &rsaquo; 
+    <a href="../../">{{app_label|capfirst|escape}}</a> &rsaquo; 
+    <a href="../">{{module_name}}</a> &rsaquo; 
+    {% blocktrans with opts.verbose_name_plural|escape as name %}Recover deleted {{name}}{% endblocktrans %}
+</div>
+{% endblock %}
+
+{% block content %}
+	<div id="content-main">
+		<p>{% blocktrans %}Choose a date from the list below to recover a deleted version of an object.{% endblocktrans %}</p>
+		<div class="module">
+			{% if deleted %}
+			    <table id="change-history">
+			        <thead>
+			        <tr>
+			            <th scope="col">{% trans 'Date/time' %}</th>
+			            <th scope="col">{{opts.verbose_name|capfirst}}</th>
+			        </tr>
+			        </thead>
+			        <tbody>
+			        {% for deletion in deleted %}
+			        <tr>
+			            <th scope="row"><a href="{{deletion.pk}}/">{{deletion.revision.date_created|date:_("DATETIME_FORMAT")}}</a></th>
+			            <td>{{deletion.object_repr}}</td>
+			        </tr>
+			        {% endfor %}
+			        </tbody>
+			    </table>
+			{% else %}
+			    <p>{% trans "There are no deleted objects to recover." %}</p>
+			{% endif %}
+		</div>
+	</div>
+{% endblock %}
\ No newline at end of file
diff --git a/lib/reversion/templates/reversion/revision_form.html b/lib/reversion/templates/reversion/revision_form.html
new file mode 100644
index 0000000..f6afca7
--- /dev/null
+++ b/lib/reversion/templates/reversion/revision_form.html
@@ -0,0 +1,33 @@
+{% extends "admin/change_form.html" %}
+{% load i18n %}
+
+
+{% block extrahead %}
+    {{block.super.super}}
+    <script type="text/javascript" src="../../../../../jsi18n/"></script>
+    {{media}}
+{% endblock %}
+
+
+{% block breadcrumbs %}
+	<div class="breadcrumbs">
+		<a href="../../../../../">{% trans "Home" %}</a> &rsaquo;
+		<a href="../../../../">{{app_label|capfirst|escape}}</a> &rsaquo; 
+		<a href="../../../">{{opts.verbose_name_plural|capfirst}}</a> &rsaquo;
+		<a href="../../">{{original|truncatewords:"18"}}</a> &rsaquo;
+		<a href="../">{% trans "History" %}</a> &rsaquo;
+		{% blocktrans with opts.verbose_name as verbose_name %}Revert {{verbose_name}}{% endblocktrans %}
+	</div>
+{% endblock %}
+
+
+{% block content %}
+	{% with 1 as is_popup %}
+		{{block.super}}
+	{% endwith %}
+{% endblock %}
+
+
+{% block form_top %}
+	<p>{% blocktrans %}Press the save button below to revert to this version of the object.{% endblocktrans %}</p>
+{% endblock %}
\ No newline at end of file
diff --git a/settings.py b/settings.py
index becbaea..4ab9d97 100644
--- a/settings.py
+++ b/settings.py
@@ -77,14 +77,27 @@ TEMPLATE_DIRS = (
     os.path.join(PROJECT_ROOT, "templates"),
 )
 
+# Add the lib/ directory to the path for external apps
+EXTERNAL_APPS_PATH = os.path.join(PROJECT_ROOT, "lib")
+
+import sys
+sys.path.append(EXTERNAL_APPS_PATH)
+
 INSTALLED_APPS = (
-    'notes',
-    'registration',
+    # System apps
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.sites',
+
+    # External apps
+    'registration',
+    'django_evolution',
+    'reversion',
+
+    # Local apps
+    'notes',
 )
 
 ACCOUNT_ACTIVATION_DAYS = 30



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]