Commits

Alex Rattray  committed 9e87095

added south

  • Participants
  • Parent commits d846c39

Comments (0)

Files changed (210)

File mysite/south/__init__.py

+"""
+South - Useable migrations for Django apps
+"""
+
+__version__ = "0.7.3"
+__authors__ = [
+    "Andrew Godwin <andrew@aeracode.org>",
+    "Andy McCurdy <andy@andymccurdy.com>"
+]

File mysite/south/__init__.pyc

Binary file added.

File mysite/south/creator/__init__.py

+"""
+The creator module is responsible for making new migration files, either
+as blank templates or autodetecting changes. It contains code that used to
+all be in startmigration.py.
+"""

File mysite/south/creator/__init__.pyc

Binary file added.

File mysite/south/creator/actions.py

+"""
+Actions - things like 'a model was removed' or 'a field was changed'.
+Each one has a class, which can take the action description and insert code
+blocks into the forwards() and backwards() methods, in the right place.
+"""
+
+import sys
+import datetime
+
+from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
+from django.db.models.fields import FieldDoesNotExist, NOT_PROVIDED, CharField, TextField
+
+from south import modelsinspector
+from south.creator.freezer import remove_useless_attributes, model_key
+
+class Action(object):
+    """
+    Generic base Action class. Contains utility methods for inserting into
+    the forwards() and backwards() method lists.
+    """
+    
+    prepend_forwards = False
+    prepend_backwards = False
+    
+    def forwards_code(self):
+        raise NotImplementedError
+    
+    def backwards_code(self):
+        raise NotImplementedError
+    
+    def add_forwards(self, forwards):
+        if self.prepend_forwards:
+            forwards.insert(0, self.forwards_code())
+        else:
+            forwards.append(self.forwards_code())
+    
+    def add_backwards(self, backwards):
+        if self.prepend_backwards:
+            backwards.insert(0, self.backwards_code())
+        else:
+            backwards.append(self.backwards_code())
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        raise NotImplementedError
+    
+    @classmethod
+    def triples_to_defs(cls, fields):
+        # Turn the (class, args, kwargs) format into a string
+        for field, triple in fields.items():
+            fields[field] = cls.triple_to_def(triple)
+        return fields
+    
+    @classmethod
+    def triple_to_def(cls, triple):
+        "Turns a single triple into a definition."
+        return "self.gf(%r)(%s)" % (
+            triple[0], # Field full path
+            ", ".join(triple[1] + ["%s=%s" % (kwd, val) for kwd, val in triple[2].items()]), # args and kwds
+        )
+    
+    
+class AddModel(Action):
+    """
+    Addition of a model. Takes the Model subclass that is being created.
+    """
+    
+    FORWARDS_TEMPLATE = '''
+        # Adding model '%(model_name)s'
+        db.create_table(%(table_name)r, (
+            %(field_defs)s
+        ))
+        db.send_create_signal(%(app_label)r, [%(model_name)r])'''
+    
+    BACKWARDS_TEMPLATE = '''
+        # Deleting model '%(model_name)s'
+        db.delete_table(%(table_name)r)'''
+
+    def __init__(self, model, model_def):
+        self.model = model
+        self.model_def = model_def
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Added model %s.%s" % (
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+
+    def forwards_code(self):
+        "Produces the code snippet that gets put into forwards()"
+        field_defs = ",\n            ".join([
+            "(%r, %s)" % (name, defn) for name, defn
+            in self.triples_to_defs(self.model_def).items()
+        ]) + ","
+        
+        return self.FORWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "app_label": self.model._meta.app_label,
+            "field_defs": field_defs,
+        }
+
+    def backwards_code(self):
+        "Produces the code snippet that gets put into backwards()"
+        return self.BACKWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+        }
+    
+    
+class DeleteModel(AddModel):
+    """
+    Deletion of a model. Takes the Model subclass that is being created.
+    """
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " - Deleted model %s.%s" % (
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+
+    def forwards_code(self):
+        return AddModel.backwards_code(self)
+
+    def backwards_code(self):
+        return AddModel.forwards_code(self)
+
+
+class _NullIssuesField(object):
+    """
+    A field that might need to ask a question about rogue NULL values.
+    """
+
+    allow_third_null_option = False
+    irreversible = False
+
+    IRREVERSIBLE_TEMPLATE = '''
+        # User chose to not deal with backwards NULL issues for '%(model_name)s.%(field_name)s'
+        raise RuntimeError("Cannot reverse this migration. '%(model_name)s.%(field_name)s' and its values cannot be restored.")'''
+
+    def deal_with_not_null_no_default(self, field, field_def):
+        # If it's a CharField or TextField that's blank, skip this step.
+        if isinstance(field, (CharField, TextField)) and field.blank:
+            field_def[2]['default'] = repr("")
+            return
+        # Oh dear. Ask them what to do.
+        print " ? The field '%s.%s' does not have a default specified, yet is NOT NULL." % (
+            self.model._meta.object_name,
+            field.name,
+        )
+        print " ? Since you are %s, you MUST specify a default" % self.null_reason
+        print " ? value to use for existing rows. Would you like to:"
+        print " ?  1. Quit now, and add a default to the field in models.py"
+        print " ?  2. Specify a one-off value to use for existing columns now"
+        if self.allow_third_null_option:
+            print " ?  3. Disable the backwards migration by raising an exception."
+        while True:
+            choice = raw_input(" ? Please select a choice: ")
+            if choice == "1":
+                sys.exit(1)
+            elif choice == "2":
+                break
+            elif choice == "3" and self.allow_third_null_option:
+                break
+            else:
+                print " ! Invalid choice."
+        if choice == "2":
+            self.add_one_time_default(field, field_def)
+        elif choice == "3":
+            self.irreversible = True
+
+    def add_one_time_default(self, field, field_def):
+        # OK, they want to pick their own one-time default. Who are we to refuse?
+        print " ? Please enter Python code for your one-off default value."
+        print " ? The datetime module is available, so you can do e.g. datetime.date.today()"
+        while True:
+            code = raw_input(" >>> ")
+            if not code:
+                print " ! Please enter some code, or 'exit' (with no quotes) to exit."
+            elif code == "exit":
+                sys.exit(1)
+            else:
+                try:
+                    result = eval(code, {}, {"datetime": datetime})
+                except (SyntaxError, NameError), e:
+                    print " ! Invalid input: %s" % e
+                else:
+                    break
+        # Right, add the default in.
+        field_def[2]['default'] = repr(result)
+
+    def irreversable_code(self, field):
+        return self.IRREVERSIBLE_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "field_name": field.name,
+            "field_column": field.column,
+        }
+    
+    
+class AddField(Action, _NullIssuesField):
+    """
+    Adds a field to a model. Takes a Model class and the field name.
+    """
+
+    null_reason = "adding this field"
+    
+    FORWARDS_TEMPLATE = '''
+        # Adding field '%(model_name)s.%(field_name)s'
+        db.add_column(%(table_name)r, %(field_name)r, %(field_def)s, keep_default=False)'''
+    
+    BACKWARDS_TEMPLATE = '''
+        # Deleting field '%(model_name)s.%(field_name)s'
+        db.delete_column(%(table_name)r, %(field_column)r)'''
+    
+    def __init__(self, model, field, field_def):
+        self.model = model
+        self.field = field
+        self.field_def = field_def
+        
+        # See if they've made a NOT NULL column but also have no default (far too common)
+        is_null = self.field.null
+        default = (self.field.default is not None) and (self.field.default is not NOT_PROVIDED)
+        
+        if not is_null and not default:
+            self.deal_with_not_null_no_default(self.field, self.field_def)
+
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Added field %s on %s.%s" % (
+            self.field.name,
+            self.model._meta.app_label,
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        
+        return self.FORWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "field_name": self.field.name,
+            "field_column": self.field.column,
+            "field_def": self.triple_to_def(self.field_def),
+        }
+
+    def backwards_code(self):
+        return self.BACKWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "field_name": self.field.name,
+            "field_column": self.field.column,
+        }
+    
+    
+class DeleteField(AddField):
+    """
+    Removes a field from a model. Takes a Model class and the field name.
+    """
+
+    null_reason = "removing this field"
+    allow_third_null_option = True
+
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " - Deleted field %s on %s.%s" % (
+            self.field.name,
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        return AddField.backwards_code(self)
+
+    def backwards_code(self):
+        if not self.irreversible:
+            return AddField.forwards_code(self)
+        else:
+            return self.irreversable_code(self.field)
+
+
+class ChangeField(Action, _NullIssuesField):
+    """
+    Changes a field's type/options on a model.
+    """
+
+    null_reason = "making this field non-nullable"
+    
+    FORWARDS_TEMPLATE = BACKWARDS_TEMPLATE = '''
+        # Changing field '%(model_name)s.%(field_name)s'
+        db.alter_column(%(table_name)r, %(field_column)r, %(field_def)s)'''
+    
+    RENAME_TEMPLATE = '''
+        # Renaming column for '%(model_name)s.%(field_name)s' to match new field type.
+        db.rename_column(%(table_name)r, %(old_column)r, %(new_column)r)'''
+    
+    def __init__(self, model, old_field, new_field, old_def, new_def):
+        self.model = model
+        self.old_field = old_field
+        self.new_field = new_field
+        self.old_def = old_def
+        self.new_def = new_def
+
+        # See if they've changed a not-null field to be null
+        new_default = (self.new_field.default is not None) and (self.new_field.default is not NOT_PROVIDED)
+        old_default = (self.old_field.default is not None) and (self.old_field.default is not NOT_PROVIDED)
+        if self.old_field.null and not self.new_field.null and not new_default:
+            self.deal_with_not_null_no_default(self.new_field, self.new_def)
+        if not self.old_field.null and self.new_field.null and not old_default:
+            self.null_reason = "making this field nullable"
+            self.allow_third_null_option = True
+            self.deal_with_not_null_no_default(self.old_field, self.old_def)
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " ~ Changed field %s on %s.%s" % (
+            self.new_field.name,
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def _code(self, old_field, new_field, new_def):
+        
+        output = ""
+        
+        if self.old_field.column != self.new_field.column:
+            output += self.RENAME_TEMPLATE % {
+                "model_name": self.model._meta.object_name,
+                "table_name": self.model._meta.db_table,
+                "field_name": new_field.name,
+                "old_column": old_field.column,
+                "new_column": new_field.column,
+            }
+        
+        output += self.FORWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "field_name": new_field.name,
+            "field_column": new_field.column,
+            "field_def": self.triple_to_def(new_def),
+        }
+        
+        return output
+
+    def forwards_code(self):
+        return self._code(self.old_field, self.new_field, self.new_def)
+
+    def backwards_code(self):
+        if not self.irreversible:
+            return self._code(self.new_field, self.old_field, self.old_def)
+        else:
+            return self.irreversable_code(self.old_field)
+
+
+class AddUnique(Action):
+    """
+    Adds a unique constraint to a model. Takes a Model class and the field names.
+    """
+    
+    FORWARDS_TEMPLATE = '''
+        # Adding unique constraint on '%(model_name)s', fields %(field_names)s
+        db.create_unique(%(table_name)r, %(fields)r)'''
+    
+    BACKWARDS_TEMPLATE = '''
+        # Removing unique constraint on '%(model_name)s', fields %(field_names)s
+        db.delete_unique(%(table_name)r, %(fields)r)'''
+    
+    prepend_backwards = True
+    
+    def __init__(self, model, fields):
+        self.model = model
+        self.fields = fields
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Added unique constraint for %s on %s.%s" % (
+            [x.name for x in self.fields],
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        
+        return self.FORWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "fields":  [field.column for field in self.fields],
+            "field_names":  [field.name for field in self.fields],
+        }
+
+    def backwards_code(self):
+        return self.BACKWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "table_name": self.model._meta.db_table,
+            "fields": [field.column for field in self.fields],
+            "field_names":  [field.name for field in self.fields],
+        }
+
+
+class DeleteUnique(AddUnique):
+    """
+    Removes a unique constraint from a model. Takes a Model class and the field names.
+    """
+    
+    prepend_forwards = True
+    prepend_backwards = False
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " - Deleted unique constraint for %s on %s.%s" % (
+            [x.name for x in self.fields],
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        return AddUnique.backwards_code(self)
+
+    def backwards_code(self):
+        return AddUnique.forwards_code(self)
+
+
+class AddIndex(AddUnique):
+    """
+    Adds an index to a model field[s]. Takes a Model class and the field names.
+    """
+    
+    FORWARDS_TEMPLATE = '''
+        # Adding index on '%(model_name)s', fields %(field_names)s
+        db.create_index(%(table_name)r, %(fields)r)'''
+    
+    BACKWARDS_TEMPLATE = '''
+        # Removing index on '%(model_name)s', fields %(field_names)s
+        db.delete_index(%(table_name)r, %(fields)r)'''
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Added index for %s on %s.%s" % (
+            [x.name for x in self.fields],
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+
+
+class DeleteIndex(AddIndex):
+    """
+    Deletes an index off a model field[s]. Takes a Model class and the field names.
+    """
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Deleted index for %s on %s.%s" % (
+            [x.name for x in self.fields],
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        return AddIndex.backwards_code(self)
+
+    def backwards_code(self):
+        return AddIndex.forwards_code(self)
+
+
+class AddM2M(Action):
+    """
+    Adds a unique constraint to a model. Takes a Model class and the field names.
+    """
+    
+    FORWARDS_TEMPLATE = '''
+        # Adding M2M table for field %(field_name)s on '%(model_name)s'
+        db.create_table(%(table_name)r, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            (%(left_field)r, models.ForeignKey(orm[%(left_model_key)r], null=False)),
+            (%(right_field)r, models.ForeignKey(orm[%(right_model_key)r], null=False))
+        ))
+        db.create_unique(%(table_name)r, [%(left_column)r, %(right_column)r])'''
+    
+    BACKWARDS_TEMPLATE = '''
+        # Removing M2M table for field %(field_name)s on '%(model_name)s'
+        db.delete_table('%(table_name)s')'''
+    
+    def __init__(self, model, field):
+        self.model = model
+        self.field = field
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " + Added M2M table for %s on %s.%s" % (
+            self.field.name,
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        
+        return self.FORWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "field_name": self.field.name,
+            "table_name": self.field.m2m_db_table(),
+            "left_field": self.field.m2m_column_name()[:-3], # Remove the _id part
+            "left_column": self.field.m2m_column_name(),
+            "left_model_key": model_key(self.model),
+            "right_field": self.field.m2m_reverse_name()[:-3], # Remove the _id part
+            "right_column": self.field.m2m_reverse_name(),
+            "right_model_key": model_key(self.field.rel.to),
+        }
+
+    def backwards_code(self):
+        
+        return self.BACKWARDS_TEMPLATE % {
+            "model_name": self.model._meta.object_name,
+            "field_name": self.field.name,
+            "table_name": self.field.m2m_db_table(),
+        }
+
+
+class DeleteM2M(AddM2M):
+    """
+    Adds a unique constraint to a model. Takes a Model class and the field names.
+    """
+    
+    def console_line(self):
+        "Returns the string to print on the console, e.g. ' + Added field foo'"
+        return " - Deleted M2M table for %s on %s.%s" % (
+            self.field.name,
+            self.model._meta.app_label, 
+            self.model._meta.object_name,
+        )
+    
+    def forwards_code(self):
+        return AddM2M.backwards_code(self)
+
+    def backwards_code(self):
+        return AddM2M.forwards_code(self)
+    

File mysite/south/creator/actions.pyc

Binary file added.

File mysite/south/creator/changes.py

+"""
+Contains things to detect changes - either using options passed in on the
+commandline, or by using autodetection, etc.
+"""
+
+from django.db import models
+from django.contrib.contenttypes.generic import GenericRelation
+from django.utils.datastructures import SortedDict
+
+from south.creator.freezer import remove_useless_attributes, freeze_apps, model_key
+from south.utils import auto_through
+
+class BaseChanges(object):
+    """
+    Base changes class.
+    """
+    def suggest_name(self):
+        return ''
+    
+    def split_model_def(self, model, model_def):
+        """
+        Given a model and its model def (a dict of field: triple), returns three
+        items: the real fields dict, the Meta dict, and the M2M fields dict.
+        """
+        real_fields = SortedDict()
+        meta = SortedDict()
+        m2m_fields = SortedDict()
+        for name, triple in model_def.items():
+            if name == "Meta":
+                meta = triple
+            elif isinstance(model._meta.get_field_by_name(name)[0], models.ManyToManyField):
+                m2m_fields[name] = triple
+            else:
+                real_fields[name] = triple
+        return real_fields, meta, m2m_fields
+    
+    def current_model_from_key(self, key):
+        app_label, model_name = key.split(".")
+        return models.get_model(app_label, model_name)
+    
+    def current_field_from_key(self, key, fieldname):
+        app_label, model_name = key.split(".")
+        # Special, for the magical field from order_with_respect_to
+        if fieldname == "_order":
+            field = models.IntegerField()
+            field.name = "_order"
+            field.attname = "_order"
+            field.column = "_order"
+            field.default = 0
+            return field
+        # Otherwise, normal.
+        return models.get_model(app_label, model_name)._meta.get_field_by_name(fieldname)[0]
+
+
+class AutoChanges(BaseChanges):
+    """
+    Detects changes by 'diffing' two sets of frozen model definitions.
+    """
+    
+    # Field types we don't generate add/remove field changes for.
+    IGNORED_FIELD_TYPES = [
+        GenericRelation,
+    ]
+    
+    def __init__(self, migrations, old_defs, old_orm, new_defs):
+        self.migrations = migrations
+        self.old_defs = old_defs
+        self.old_orm = old_orm
+        self.new_defs = new_defs
+    
+    def suggest_name(self):
+        parts = ["auto"]
+        for change_name, params in self.get_changes():
+            if change_name == "AddModel":
+                parts.append("add_%s" % params['model']._meta.object_name.lower())
+            elif change_name == "DeleteModel":
+                parts.append("del_%s" % params['model']._meta.object_name.lower())
+            elif change_name == "AddField":
+                parts.append("add_field_%s_%s" % (
+                    params['model']._meta.object_name.lower(),
+                    params['field'].name,
+                ))
+            elif change_name == "DeleteField":
+                parts.append("del_field_%s_%s" % (
+                    params['model']._meta.object_name.lower(),
+                    params['field'].name,
+                ))
+            elif change_name == "ChangeField":
+                parts.append("chg_field_%s_%s" % (
+                    params['model']._meta.object_name.lower(),
+                    params['new_field'].name,
+                ))
+            elif change_name == "AddUnique":
+                parts.append("add_unique_%s_%s" % (
+                    params['model']._meta.object_name.lower(),
+                    "_".join([x.name for x in params['fields']]),
+                ))
+            elif change_name == "DeleteUnique":
+                parts.append("del_unique_%s_%s" % (
+                    params['model']._meta.object_name.lower(),
+                    "_".join([x.name for x in params['fields']]),
+                ))
+        return ("__".join(parts))[:70]
+    
+    def get_changes(self):
+        """
+        Returns the difference between the old and new sets of models as a 5-tuple:
+        added_models, deleted_models, added_fields, deleted_fields, changed_fields
+        """
+        
+        deleted_models = set()
+        
+        # See if anything's vanished
+        for key in self.old_defs:
+            if key not in self.new_defs:
+                # We shouldn't delete it if it was managed=False
+                old_fields, old_meta, old_m2ms = self.split_model_def(self.old_orm[key], self.old_defs[key])
+                if old_meta.get("managed", "True") != "False":
+                    # Alright, delete it.
+                    yield ("DeleteModel", {
+                        "model": self.old_orm[key], 
+                        "model_def": old_fields,
+                    })
+                    # Also make sure we delete any M2Ms it had.
+                    for fieldname in old_m2ms:
+                        # Only delete its stuff if it wasn't a through=.
+                        field = self.old_orm[key + ":" + fieldname]
+                        if auto_through(field):
+                            yield ("DeleteM2M", {"model": self.old_orm[key], "field": field})
+                    # And any unique constraints it had 
+                    unique_together = eval(old_meta.get("unique_together", "[]"))
+                    if unique_together:
+                        # If it's only a single tuple, make it into the longer one
+                        if isinstance(unique_together[0], basestring):
+                            unique_together = [unique_together]
+                        # For each combination, make an action for it
+                        for fields in unique_together:
+                            yield ("DeleteUnique", {
+                                "model": self.old_orm[key],
+                                "fields": [self.old_orm[key]._meta.get_field_by_name(x)[0] for x in fields],
+                            })
+                # We always add it in here so we ignore it later
+                deleted_models.add(key)
+        
+        # Or appeared
+        for key in self.new_defs:
+            if key not in self.old_defs:
+                # We shouldn't add it if it's managed=False
+                new_fields, new_meta, new_m2ms = self.split_model_def(self.current_model_from_key(key), self.new_defs[key])
+                if new_meta.get("managed", "True") != "False":
+                    yield ("AddModel", {
+                        "model": self.current_model_from_key(key), 
+                        "model_def": new_fields,
+                    })
+                    # Also make sure we add any M2Ms it has.
+                    for fieldname in new_m2ms:
+                        # Only create its stuff if it wasn't a through=.
+                        field = self.current_field_from_key(key, fieldname)
+                        if auto_through(field):
+                            yield ("AddM2M", {"model": self.current_model_from_key(key), "field": field})
+                    # And any unique constraints it has 
+                    unique_together = eval(new_meta.get("unique_together", "[]"))
+                    if unique_together:
+                        # If it's only a single tuple, make it into the longer one
+                        if isinstance(unique_together[0], basestring):
+                            unique_together = [unique_together]
+                        # For each combination, make an action for it
+                        for fields in unique_together:
+                            yield ("AddUnique", {
+                                "model": self.current_model_from_key(key),
+                                "fields": [self.current_model_from_key(key)._meta.get_field_by_name(x)[0] for x in fields],
+                            })
+        
+        # Now, for every model that's stayed the same, check its fields.
+        for key in self.old_defs:
+            if key not in deleted_models:
+                
+                old_fields, old_meta, old_m2ms = self.split_model_def(self.old_orm[key], self.old_defs[key])
+                new_fields, new_meta, new_m2ms = self.split_model_def(self.current_model_from_key(key), self.new_defs[key])
+                
+                # Find fields that have vanished.
+                for fieldname in old_fields:
+                    if fieldname not in new_fields:
+                        # Don't do it for any fields we're ignoring
+                        field = self.old_orm[key + ":" + fieldname]
+                        field_allowed = True
+                        for field_type in self.IGNORED_FIELD_TYPES:
+                            if isinstance(field, field_type):
+                                field_allowed = False
+                        if field_allowed:
+                            # Looks alright.
+                            yield ("DeleteField", {
+                                "model": self.old_orm[key],
+                                "field": field,
+                                "field_def": old_fields[fieldname],
+                            })
+                
+                # And ones that have appeared
+                for fieldname in new_fields:
+                    if fieldname not in old_fields:
+                        # Don't do it for any fields we're ignoring
+                        field = self.current_field_from_key(key, fieldname)
+                        field_allowed = True
+                        for field_type in self.IGNORED_FIELD_TYPES:
+                            if isinstance(field, field_type):
+                                field_allowed = False
+                        if field_allowed:
+                            # Looks alright.
+                            yield ("AddField", {
+                                "model": self.current_model_from_key(key),
+                                "field": field,
+                                "field_def": new_fields[fieldname],
+                            })
+                
+                # Find M2Ms that have vanished
+                for fieldname in old_m2ms:
+                    if fieldname not in new_m2ms:
+                        # Only delete its stuff if it wasn't a through=.
+                        field = self.old_orm[key + ":" + fieldname]
+                        if auto_through(field):
+                            yield ("DeleteM2M", {"model": self.old_orm[key], "field": field})
+                
+                # Find M2Ms that have appeared
+                for fieldname in new_m2ms:
+                    if fieldname not in old_m2ms:
+                        # Only create its stuff if it wasn't a through=.
+                        field = self.current_field_from_key(key, fieldname)
+                        if auto_through(field):
+                            yield ("AddM2M", {"model": self.current_model_from_key(key), "field": field})
+                
+                # For the ones that exist in both models, see if they were changed
+                for fieldname in set(old_fields).intersection(set(new_fields)):
+                    # Non-index changes
+                    if self.different_attributes(
+                     remove_useless_attributes(old_fields[fieldname], True, True),
+                     remove_useless_attributes(new_fields[fieldname], True, True)):
+                        yield ("ChangeField", {
+                            "model": self.current_model_from_key(key),
+                            "old_field": self.old_orm[key + ":" + fieldname],
+                            "new_field": self.current_field_from_key(key, fieldname),
+                            "old_def": old_fields[fieldname],
+                            "new_def": new_fields[fieldname],
+                        })
+                    # Index changes
+                    old_field = self.old_orm[key + ":" + fieldname]
+                    new_field = self.current_field_from_key(key, fieldname)
+                    if not old_field.db_index and new_field.db_index:
+                        # They've added an index.
+                        yield ("AddIndex", {
+                            "model": self.current_model_from_key(key),
+                            "fields": [new_field],
+                        })
+                    if old_field.db_index and not new_field.db_index:
+                        # They've removed an index.
+                        yield ("DeleteIndex", {
+                            "model": self.old_orm[key],
+                            "fields": [old_field],
+                        })
+                    # See if their uniques have changed
+                    if old_field.unique != new_field.unique:
+                        # Make sure we look at the one explicitly given to see what happened
+                        if new_field.unique:
+                            yield ("AddUnique", {
+                                "model": self.current_model_from_key(key),
+                                "fields": [new_field],
+                            })
+                        else:
+                            yield ("DeleteUnique", {
+                                "model": self.old_orm[key],
+                                "fields": [old_field],
+                            })
+                
+                # See if there's any M2Ms that have changed.
+                for fieldname in set(old_m2ms).intersection(set(new_m2ms)):
+                    old_field = self.old_orm[key + ":" + fieldname]
+                    new_field = self.current_field_from_key(key, fieldname)
+                    # Have they _added_ a through= ?
+                    if auto_through(old_field) and not auto_through(new_field):
+                        yield ("DeleteM2M", {"model": self.old_orm[key], "field": old_field})
+                    # Have they _removed_ a through= ?
+                    if not auto_through(old_field) and auto_through(new_field):
+                        yield ("AddM2M", {"model": self.current_model_from_key(key), "field": new_field})
+                
+                ## See if the unique_togethers have changed
+                # First, normalise them into lists of sets.
+                old_unique_together = eval(old_meta.get("unique_together", "[]"))
+                new_unique_together = eval(new_meta.get("unique_together", "[]"))
+                if old_unique_together and isinstance(old_unique_together[0], basestring):
+                    old_unique_together = [old_unique_together]
+                if new_unique_together and isinstance(new_unique_together[0], basestring):
+                    new_unique_together = [new_unique_together]
+                old_unique_together = map(set, old_unique_together)
+                new_unique_together = map(set, new_unique_together)
+                # See if any appeared or disappeared
+                for item in old_unique_together:
+                    if item not in new_unique_together:
+                        yield ("DeleteUnique", {
+                            "model": self.old_orm[key],
+                            "fields": [self.old_orm[key + ":" + x] for x in item],
+                        })
+                for item in new_unique_together:
+                    if item not in old_unique_together:
+                        yield ("AddUnique", {
+                            "model": self.current_model_from_key(key),
+                            "fields": [self.current_field_from_key(key, x) for x in item],
+                        })
+
+    @classmethod
+    def is_triple(cls, triple):
+        "Returns whether the argument is a triple."
+        return isinstance(triple, (list, tuple)) and len(triple) == 3 and \
+            isinstance(triple[0], (str, unicode)) and \
+            isinstance(triple[1], (list, tuple)) and \
+            isinstance(triple[2], dict)
+
+    @classmethod
+    def different_attributes(cls, old, new):
+        """
+        Backwards-compat comparison that ignores orm. on the RHS and not the left
+        and which knows django.db.models.fields.CharField = models.CharField.
+        Has a whole load of tests in tests/autodetection.py.
+        """
+        
+        # If they're not triples, just do normal comparison
+        if not cls.is_triple(old) or not cls.is_triple(new):
+            return old != new
+        
+        # Expand them out into parts
+        old_field, old_pos, old_kwd = old
+        new_field, new_pos, new_kwd = new
+        
+        # Copy the positional and keyword arguments so we can compare them and pop off things
+        old_pos, new_pos = old_pos[:], new_pos[:]
+        old_kwd = dict(old_kwd.items())
+        new_kwd = dict(new_kwd.items())
+        
+        # Remove comparison of the existence of 'unique', that's done elsewhere.
+        # TODO: Make this work for custom fields where unique= means something else?
+        if "unique" in old_kwd:
+            del old_kwd['unique']
+        if "unique" in new_kwd:
+            del new_kwd['unique']
+        
+        # If the first bit is different, check it's not by dj.db.models...
+        if old_field != new_field:
+            if old_field.startswith("models.") and (new_field.startswith("django.db.models") \
+             or new_field.startswith("django.contrib.gis")):
+                if old_field.split(".")[-1] != new_field.split(".")[-1]:
+                    return True
+                else:
+                    # Remove those fields from the final comparison
+                    old_field = new_field = ""
+        
+        # If there's a positional argument in the first, and a 'to' in the second,
+        # see if they're actually comparable.
+        if (old_pos and "to" in new_kwd) and ("orm" in new_kwd['to'] and "orm" not in old_pos[0]):
+            # Do special comparison to fix #153
+            try:
+                if old_pos[0] != new_kwd['to'].split("'")[1].split(".")[1]:
+                    return True
+            except IndexError:
+                pass # Fall back to next comparison
+            # Remove those attrs from the final comparison
+            old_pos = old_pos[1:]
+            del new_kwd['to']
+        
+        return old_field != new_field or old_pos != new_pos or old_kwd != new_kwd
+
+
+class ManualChanges(BaseChanges):
+    """
+    Detects changes by reading the command line.
+    """
+    
+    def __init__(self, migrations, added_models, added_fields, added_indexes):
+        self.migrations = migrations
+        self.added_models = added_models
+        self.added_fields = added_fields
+        self.added_indexes = added_indexes
+    
+    def suggest_name(self):
+        bits = []
+        for model_name in self.added_models:
+            bits.append('add_model_%s' % model_name)
+        for field_name in self.added_fields:
+            bits.append('add_field_%s' % field_name)
+        for index_name in self.added_indexes:
+            bits.append('add_index_%s' % index_name)
+        return '_'.join(bits).replace('.', '_')
+    
+    def get_changes(self):
+        # Get the model defs so we can use them for the yield later
+        model_defs = freeze_apps([self.migrations.app_label()])
+        # Make the model changes
+        for model_name in self.added_models:
+            model = models.get_model(self.migrations.app_label(), model_name)
+            real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)])
+            yield ("AddModel", {
+                "model": model,
+                "model_def": real_fields,
+            })
+        # And the field changes
+        for field_desc in self.added_fields:
+            try:
+                model_name, field_name = field_desc.split(".")
+            except (TypeError, ValueError):
+                print "%r is not a valid field description." % field_desc
+            model = models.get_model(self.migrations.app_label(), model_name)
+            real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)])
+            yield ("AddField", {
+                "model": model,
+                "field": model._meta.get_field_by_name(field_name)[0],
+                "field_def": real_fields[field_name],
+            })
+        # And the indexes
+        for field_desc in self.added_indexes:
+            try:
+                model_name, field_name = field_desc.split(".")
+            except (TypeError, ValueError):
+                print "%r is not a valid field description." % field_desc
+            model = models.get_model(self.migrations.app_label(), model_name)
+            yield ("AddIndex", {
+                "model": model,
+                "fields": [model._meta.get_field_by_name(field_name)[0]],
+            })
+    
+    
+class InitialChanges(BaseChanges):
+    """
+    Creates all models; handles --initial.
+    """
+    def suggest_name(self):
+        return 'initial'
+    
+    def __init__(self, migrations):
+        self.migrations = migrations
+    
+    def get_changes(self):
+        # Get the frozen models for this app
+        model_defs = freeze_apps([self.migrations.app_label()])
+        
+        for model in models.get_models(models.get_app(self.migrations.app_label())):
+            
+            # Don't do anything for unmanaged, abstract or proxy models
+            if model._meta.abstract or getattr(model._meta, "proxy", False) or not getattr(model._meta, "managed", True):
+                continue
+            
+            real_fields, meta, m2m_fields = self.split_model_def(model, model_defs[model_key(model)])
+            
+            # Firstly, add the main table and fields
+            yield ("AddModel", {
+                "model": model,
+                "model_def": real_fields,
+            })
+            
+            # Then, add any uniqueness that's around
+            if meta:
+                unique_together = eval(meta.get("unique_together", "[]"))
+                if unique_together:
+                    # If it's only a single tuple, make it into the longer one
+                    if isinstance(unique_together[0], basestring):
+                        unique_together = [unique_together]
+                    # For each combination, make an action for it
+                    for fields in unique_together:
+                        yield ("AddUnique", {
+                            "model": model,
+                            "fields": [model._meta.get_field_by_name(x)[0] for x in fields],
+                        })
+            
+            # Finally, see if there's some M2M action
+            for name, triple in m2m_fields.items():
+                field = model._meta.get_field_by_name(name)[0]
+                # But only if it's not through=foo (#120)
+                if field.rel.through:
+                    try:
+                        # Django 1.1 and below
+                        through_model = field.rel.through_model
+                    except AttributeError:
+                        # Django 1.2
+                        through_model = field.rel.through
+                if (not field.rel.through) or getattr(through_model._meta, "auto_created", False):
+                    yield ("AddM2M", {
+                        "model": model,
+                        "field": field,
+                    })

File mysite/south/creator/changes.pyc

Binary file added.

File mysite/south/creator/freezer.py

+"""
+Handles freezing of models into FakeORMs.
+"""
+
+import sys
+
+from django.db import models
+from django.contrib.contenttypes.generic import GenericRelation
+
+from south.orm import FakeORM
+from south.utils import auto_model
+from south import modelsinspector
+
+def freeze_apps(apps):
+    """
+    Takes a list of app labels, and returns a string of their frozen form.
+    """
+    if isinstance(apps, basestring):
+        apps = [apps]
+    frozen_models = set()
+    # For each app, add in all its models
+    for app in apps:
+        for model in models.get_models(models.get_app(app)):
+            # Only add if it's not abstract or proxy
+            if not model._meta.abstract and not getattr(model._meta, "proxy", False):
+                frozen_models.add(model)
+    # Now, add all the dependencies
+    for model in list(frozen_models):
+        frozen_models.update(model_dependencies(model))
+    # Serialise!
+    model_defs = {}
+    model_classes = {}
+    for model in frozen_models:
+        model_defs[model_key(model)] = prep_for_freeze(model)
+        model_classes[model_key(model)] = model
+    # Check for any custom fields that failed to freeze.
+    missing_fields = False
+    for key, fields in model_defs.items():
+        for field_name, value in fields.items():
+            if value is None:
+                missing_fields = True
+                model_class = model_classes[key]
+                field_class = model_class._meta.get_field_by_name(field_name)[0]
+                print " ! Cannot freeze field '%s.%s'" % (key, field_name)
+                print " ! (this field has class %s.%s)" % (field_class.__class__.__module__, field_class.__class__.__name__)
+    if missing_fields:
+        print ""
+        print " ! South cannot introspect some fields; this is probably because they are custom"
+        print " ! fields. If they worked in 0.6 or below, this is because we have removed the"
+        print " ! models parser (it often broke things)."
+        print " ! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork"
+        sys.exit(1)
+    
+    return model_defs
+    
+def freeze_apps_to_string(apps):
+    return pprint_frozen_models(freeze_apps(apps))
+    
+### 
+
+def model_key(model):
+    "For a given model, return 'appname.modelname'."
+    return "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
+
+def prep_for_freeze(model):
+    """
+    Takes a model and returns the ready-to-serialise dict (all you need
+    to do is just pretty-print it).
+    """
+    fields = modelsinspector.get_model_fields(model, m2m=True)
+    # Remove useless attributes (like 'choices')
+    for name, field in fields.items():
+        fields[name] = remove_useless_attributes(field)
+    # See if there's a Meta
+    fields['Meta'] = remove_useless_meta(modelsinspector.get_model_meta(model))
+    # Add in our own special items to track the object name and managed
+    fields['Meta']['object_name'] = model._meta.object_name # Special: not eval'able.
+    if not getattr(model._meta, "managed", True):
+        fields['Meta']['managed'] = repr(model._meta.managed)
+    return fields
+
+### Dependency resolvers
+
+def model_dependencies(model, checked_models=None):
+    """
+    Returns a set of models this one depends on to be defined; things like
+    OneToOneFields as ID, ForeignKeys everywhere, etc.
+    """
+    depends = set()
+    checked_models = checked_models or set()
+    # Get deps for each field
+    for field in model._meta.fields + model._meta.many_to_many:
+        depends.update(field_dependencies(field))
+    # Add in any non-abstract bases
+    for base in model.__bases__:
+        if issubclass(base, models.Model) and (base is not models.Model) and not base._meta.abstract:
+            depends.add(base)
+    # Now recurse
+    new_to_check = depends - checked_models
+    while new_to_check:
+        checked_model = new_to_check.pop()
+        if checked_model == model or checked_model in checked_models:
+            continue
+        checked_models.add(checked_model)
+        deps = model_dependencies(checked_model, checked_models)
+        # Loop through dependencies...
+        for dep in deps:
+            # If the new dep is not already checked, add to the queue
+            if (dep not in depends) and (dep not in new_to_check) and (dep not in checked_models):
+                new_to_check.add(dep)
+            depends.add(dep)
+    return depends
+
+def field_dependencies(field, checked_models=None):
+    checked_models = checked_models or set()
+    depends = set()
+    if isinstance(field, (models.OneToOneField, models.ForeignKey, models.ManyToManyField, GenericRelation)):
+        if field.rel.to in checked_models:
+            return depends
+        checked_models.add(field.rel.to)
+        depends.add(field.rel.to)
+        depends.update(field_dependencies(field.rel.to._meta.pk, checked_models))
+        # Also include M2M throughs
+        if isinstance(field, models.ManyToManyField):
+            if field.rel.through:
+                if hasattr(field.rel, "through_model"): # 1.1 and below
+                    depends.add(field.rel.through_model)
+                else:
+                    # Make sure it's not an automatic one
+                    if not auto_model(field.rel.through):
+                        depends.add(field.rel.through) # 1.2 and up
+    return depends
+
+### Prettyprinters
+
+def pprint_frozen_models(models):
+    return "{\n        %s\n    }" % ",\n        ".join([
+        "%r: %s" % (name, pprint_fields(fields))
+        for name, fields in sorted(models.items())
+    ])
+
+def pprint_fields(fields):
+    return "{\n            %s\n        }" % ",\n            ".join([
+        "%r: %r" % (name, defn)
+        for name, defn in sorted(fields.items())
+    ])
+
+### Output sanitisers
+
+USELESS_KEYWORDS = ["choices", "help_text", "verbose_name"]
+USELESS_DB_KEYWORDS = ["related_name", "default", "blank"] # Important for ORM, not for DB.
+INDEX_KEYWORDS = ["db_index"]
+
+def remove_useless_attributes(field, db=False, indexes=False):
+    "Removes useless (for database) attributes from the field's defn."
+    # Work out what to remove, and remove it.
+    keywords = USELESS_KEYWORDS[:]
+    if db:
+        keywords += USELESS_DB_KEYWORDS[:]
+    if indexes:
+        keywords += INDEX_KEYWORDS[:]
+    if field:
+        for name in keywords:
+            if name in field[2]:
+                del field[2][name]
+    return field
+
+USELESS_META = ["verbose_name", "verbose_name_plural"]
+def remove_useless_meta(meta):
+    "Removes useless (for database) attributes from the table's meta."
+    if meta:
+        for name in USELESS_META:
+            if name in meta:
+                del meta[name]
+    return meta

File mysite/south/creator/freezer.pyc

Binary file added.

File mysite/south/db/__init__.py

+
+# Establish the common DatabaseOperations instance, which we call 'db'.
+# Much thanks to cmkmrr for a lot of the code base here
+
+from django.conf import settings
+import sys
+
+# A few aliases, because there's FQMNs now
+engine_modules = {
+    'django.db.backends.postgresql_psycopg2': 'postgresql_psycopg2',
+    'django.db.backends.sqlite3': 'sqlite3',
+    'django.db.backends.mysql': 'mysql',
+    'django.db.backends.oracle': 'oracle',
+    'sql_server.pyodbc': 'sql_server.pyodbc', #django-pyodbc
+    'sqlserver_ado': 'sql_server.pyodbc', #django-mssql
+    'django.contrib.gis.db.backends.postgis': 'postgresql_psycopg2',
+    'django.contrib.gis.db.backends.spatialite': 'sqlite3',
+    'django.contrib.gis.db.backends.mysql': 'mysql',
+    'django.contrib.gis.db.backends.oracle': 'oracle',
+}
+
+# First, work out if we're multi-db or not, and which databases we have
+try: 
+    from django.db import DEFAULT_DB_ALIAS 
+except ImportError:
+    #### 1.1 or below ####
+    # We'll 'fake' multi-db; set the default alias
+    DEFAULT_DB_ALIAS = 'default'
+    # SOUTH_DATABASE_ADAPTER is an optional override if you have a different module
+    engine = getattr(settings, "SOUTH_DATABASE_ADAPTER", "south.db.%s" % settings.DATABASE_ENGINE)
+    # And then, we have one database with one engine
+    db_engines = {DEFAULT_DB_ALIAS: engine}
+else:
+    #### 1.2 or above ####
+    # Loop over the defined databases, gathering up their engines
+    db_engines = dict([
+        # Note we check to see if contrib.gis has overridden us.
+        (alias, "south.db.%s" % engine_modules.get(db_settings['ENGINE'], None))
+        for alias, db_settings in settings.DATABASES.items()
+    ])
+    # Update with any overrides
+    db_engines.update(getattr(settings, "SOUTH_DATABASE_ADAPTERS", {}))
+    # Check there's no None engines, or...
+    for alias, engine in db_engines.items():
+        if engine is None:
+            # They've used a backend we don't support
+            sys.stderr.write(
+                (
+                    "There is no South database module for your database backend '%s'. " + \
+                    "Please either choose a supported database, check for " + \
+                    "SOUTH_DATABASE_ADAPTER[S] settings, " + \
+                    "or remove South from INSTALLED_APPS.\n"
+                ) % (settings.DATABASES[alias]['ENGINE'],)
+            )
+            sys.exit(1)
+
+# Now, turn that into a dict of <alias: south db module>
+dbs = {}
+try:
+    for alias, module_name in db_engines.items():
+        module = __import__(module_name, {}, {}, [''])
+        dbs[alias] = module.DatabaseOperations(alias)
+except ImportError:
+    # This error should only be triggered on 1.1 and below.
+    sys.stderr.write(
+        (
+            "There is no South database module '%s' for your database. " + \
+            "Please either choose a supported database, check for " + \
+            "SOUTH_DATABASE_ADAPTER[S] settings, " + \
+            "or remove South from INSTALLED_APPS.\n"
+        ) % (module_name,)
+    )
+    sys.exit(1)
+    
+# Finally, to make old migrations work, keep 'db' around as the default database
+db = dbs[DEFAULT_DB_ALIAS]

File mysite/south/db/__init__.pyc

Binary file added.

File mysite/south/db/generic.py

+
+import datetime
+import string
+import random
+import re
+import sys
+
+from django.core.management.color import no_style
+from django.db import transaction, models
+from django.db.backends.util import truncate_name
+from django.db.models.fields import NOT_PROVIDED
+from django.dispatch import dispatcher
+from django.conf import settings
+from django.utils.datastructures import SortedDict
+
+from south.logger import get_logger
+
+def alias(attrname):
+    """
+    Returns a function which calls 'attrname' - for function aliasing.
+    We can't just use foo = bar, as this breaks subclassing.
+    """
+    def func(self, *args, **kwds):
+        return getattr(self, attrname)(*args, **kwds)
+    return func
+
+
+class DatabaseOperations(object):
+
+    """
+    Generic SQL implementation of the DatabaseOperations.
+    Some of this code comes from Django Evolution.
+    """
+
+    # We assume the generic DB can handle DDL transactions. MySQL wil change this.
+    has_ddl_transactions = True
+
+    alter_string_set_type = 'ALTER COLUMN %(column)s TYPE %(type)s'
+    alter_string_set_null = 'ALTER COLUMN %(column)s DROP NOT NULL'
+    alter_string_drop_null = 'ALTER COLUMN %(column)s SET NOT NULL'
+    has_check_constraints = True
+    delete_check_sql = 'ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s'
+    allows_combined_alters = True
+    add_column_string = 'ALTER TABLE %s ADD COLUMN %s;'
+    delete_unique_sql = "ALTER TABLE %s DROP CONSTRAINT %s"
+    delete_foreign_key_sql = 'ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s'
+    supports_foreign_keys = True
+    max_index_name_length = 63
+    drop_index_string = 'DROP INDEX %(index_name)s'
+    delete_column_string = 'ALTER TABLE %s DROP COLUMN %s CASCADE;'
+    create_primary_key_string = "ALTER TABLE %(table)s ADD CONSTRAINT %(constraint)s PRIMARY KEY (%(columns)s)"
+    delete_primary_key_sql = "ALTER TABLE %(table)s DROP CONSTRAINT %(constraint)s"
+    backend_name = None
+    default_schema_name = "public"
+
+    def __init__(self, db_alias):
+        self.debug = False
+        self.deferred_sql = []
+        self.dry_run = False
+        self.pending_transactions = 0
+        self.pending_create_signals = []
+        self.db_alias = db_alias
+        self._initialised = False
+    
+    def _is_multidb(self):
+        try: 
+            from django.db import connections
+        except ImportError:
+            return False
+        else:
+            return True
+
+    def _get_connection(self): 
+        """ 
+        Returns a django connection for a given DB Alias 
+        """
+        if self._is_multidb():
+            from django.db import connections 
+            return connections[self.db_alias] 
+        else:
+            from django.db import connection 
+            return connection 
+
+    def _get_setting(self, setting_name):
+        """
+        Allows code to get a setting (like, for example, STORAGE_ENGINE)
+        """
+        setting_name = setting_name.upper()
+        connection = self._get_connection() 
+        if self._is_multidb():
+            # Django 1.2 and above
+            return connection.settings_dict[setting_name] 
+        else:
+            # Django 1.1 and below
+            return getattr(settings, "DATABASE_%s" % setting_name)
+
+    def _has_setting(self, setting_name):
+        """
+        Existence-checking version of _get_setting.
+        """
+        try:
+            self._get_setting(setting_name)
+        except (KeyError, AttributeError):
+            return False
+        else:
+            return True
+
+    def _get_schema_name(self):
+        try:
+            return self._get_setting('schema')
+        except (KeyError, AttributeError):
+            return self.default_schema_name
+
+    
+    def _possibly_initialise(self):
+        if not self._initialised:
+            self.connection_init()
+            self._initialised = True
+
+    def connection_init(self):
+        """
+        Run before any SQL to let database-specific config be sent as a command,
+        e.g. which storage engine (MySQL) or transaction serialisability level.
+        """
+        pass
+    
+    def quote_name(self, name):
+        """
+        Uses the database backend to quote the given table/column name.
+        """
+        return self._get_connection().ops.quote_name(name)
+
+    def execute(self, sql, params=[]):
+        """
+        Executes the given SQL statement, with optional parameters.
+        If the instance's debug attribute is True, prints out what it executes.
+        """
+        
+        self._possibly_initialise()
+        
+        cursor = self._get_connection().cursor()
+        if self.debug:
+            print "   = %s" % sql, params
+
+        get_logger().debug('south execute "%s" with params "%s"' % (sql, params))
+
+        if self.dry_run:
+            return []
+
+        cursor.execute(sql, params)
+        try:
+            return cursor.fetchall()
+        except:
+            return []
+
+
+    def execute_many(self, sql, regex=r"(?mx) ([^';]* (?:'[^']*'[^';]*)*)", comment_regex=r"(?mx) (?:^\s*$)|(?:--.*$)"):
+        """
+        Takes a SQL file and executes it as many separate statements.
+        (Some backends, such as Postgres, don't work otherwise.)
+        """
+        # Be warned: This function is full of dark magic. Make sure you really
+        # know regexes before trying to edit it.
+        # First, strip comments
+        sql = "\n".join([x.strip().replace("%", "%%") for x in re.split(comment_regex, sql) if x.strip()])
+        # Now execute each statement
+        for st in re.split(regex, sql)[1:][::2]:
+            self.execute(st)
+
+
+    def add_deferred_sql(self, sql):
+        """
+        Add a SQL statement to the deferred list, that won't be executed until
+        this instance's execute_deferred_sql method is run.
+        """
+        self.deferred_sql.append(sql)
+
+
+    def execute_deferred_sql(self):
+        """
+        Executes all deferred SQL, resetting the deferred_sql list
+        """
+        for sql in self.deferred_sql:
+            self.execute(sql)
+
+        self.deferred_sql = []
+
+
+    def clear_deferred_sql(self):
+        """
+        Resets the deferred_sql list to empty.
+        """
+        self.deferred_sql = []
+
+
+    def clear_run_data(self, pending_creates = None):
+        """
+        Resets variables to how they should be before a run. Used for dry runs.
+        If you want, pass in an old panding_creates to reset to.
+        """
+        self.clear_deferred_sql()
+        self.pending_create_signals = pending_creates or []
+
+
+    def get_pending_creates(self):
+        return self.pending_create_signals
+
+
+    def create_table(self, table_name, fields):
+        """
+        Creates the table 'table_name'. 'fields' is a tuple of fields,
+        each repsented by a 2-part tuple of field name and a
+        django.db.models.fields.Field object
+        """
+
+        if len(table_name) > 63:
+            print "   ! WARNING: You have a table name longer than 63 characters; this will not fully work on PostgreSQL or MySQL."
+
+        columns = [
+            self.column_sql(table_name, field_name, field)
+            for field_name, field in fields
+        ]
+
+        self.execute('CREATE TABLE %s (%s);' % (
+            self.quote_name(table_name),
+            ', '.join([col for col in columns if col]),
+        ))
+
+    add_table = alias('create_table') # Alias for consistency's sake
+
+
+    def rename_table(self, old_table_name, table_name):
+        """
+        Renames the table 'old_table_name' to 'table_name'.
+        """
+        if old_table_name == table_name:
+            # Short-circuit out.
+            return
+        params = (self.quote_name(old_table_name), self.quote_name(table_name))
+        self.execute('ALTER TABLE %s RENAME TO %s;' % params)
+
+
+    def delete_table(self, table_name, cascade=True):
+        """
+        Deletes the table 'table_name'.
+        """
+        params = (self.quote_name(table_name), )
+        if cascade:
+            self.execute('DROP TABLE %s CASCADE;' % params)
+        else:
+            self.execute('DROP TABLE %s;' % params)
+
+    drop_table = alias('delete_table')
+
+
+    def clear_table(self, table_name):
+        """
+        Deletes all rows from 'table_name'.
+        """
+        params = (self.quote_name(table_name), )
+        self.execute('DELETE FROM %s;' % params)
+
+
+
+    def add_column(self, table_name, name, field, keep_default=True):
+        """
+        Adds the column 'name' to the table 'table_name'.
+        Uses the 'field' paramater, a django.db.models.fields.Field instance,
+        to generate the necessary sql
+
+        @param table_name: The name of the table to add the column to
+        @param name: The name of the column to add
+        @param field: The field to use
+        """
+        sql = self.column_sql(table_name, name, field)
+        if sql:
+            params = (
+                self.quote_name(table_name),
+                sql,
+            )
+            sql = self.add_column_string % params
+            self.execute(sql)
+
+            # Now, drop the default if we need to
+            if not keep_default and field.default is not None:
+                field.default = NOT_PROVIDED
+                self.alter_column(table_name, name, field, explicit_name=False, ignore_constraints=True)
+
+
+    def _db_type_for_alter_column(self, field):
+        """
+        Returns a field's type suitable for ALTER COLUMN.
+        By default it just returns field.db_type().
+        To be overriden by backend specific subclasses
+        @param field: The field to generate type for
+        """
+        try:
+            return field.db_type(connection=self._get_connection())
+        except TypeError:
+            return field.db_type()
+        
+    def _alter_set_defaults(self, field, name, params, sqls): 
+        "Subcommand of alter_column that sets default values (overrideable)"
+        # Next, set any default
+        if not field.null and field.has_default():
+            default = field.get_default()
+            sqls.append(('ALTER COLUMN %s SET DEFAULT %%s ' % (self.quote_name(name),), [default]))
+        else:
+            sqls.append(('ALTER COLUMN %s DROP DEFAULT' % (self.quote_name(name),), []))
+
+    def alter_column(self, table_name, name, field, explicit_name=True, ignore_constraints=False):
+        """
+        Alters the given column name so it will match the given field.
+        Note that conversion between the two by the database must be possible.
+        Will not automatically add _id by default; to have this behavour, pass
+        explicit_name=False.
+
+        @param table_name: The name of the table to add the column to
+        @param name: The name of the column to alter
+        @param field: The new field definition to use
+        """
+        
+        if self.dry_run:
+            return
+
+        # hook for the field to do any resolution prior to it's attributes being queried
+        if hasattr(field, 'south_init'):
+            field.south_init()
+
+        # Add _id or whatever if we need to
+        field.set_attributes_from_name(name)
+        if not explicit_name:
+            name = field.column
+        else:
+            field.column = name
+
+        if not ignore_constraints:
+            # Drop all check constraints. TODO: Add the right ones back.
+            if self.has_check_constraints:
+                check_constraints = self._constraints_affecting_columns(table_name, [name], "CHECK")
+                for constraint in check_constraints:
+                    self.execute(self.delete_check_sql % {
+                        'table': self.quote_name(table_name),
+                        'constraint': self.quote_name(constraint),
+                    })
+        
+            # Drop all foreign key constraints
+            try:
+                self.delete_foreign_key(table_name, name)
+            except ValueError:
+                # There weren't any
+                pass
+
+        # First, change the type
+        params = {
+            "column": self.quote_name(name),
+            "type": self._db_type_for_alter_column(field),            
+            "table_name": table_name
+        }
+
+        # SQLs is a list of (SQL, values) pairs.
+        sqls = []
+        
+        # Only alter the column if it has a type (Geometry ones sometimes don't)
+        if params["type"] is not None:
+            sqls.append((self.alter_string_set_type % params, []))
+        
+        # Next, nullity
+        if field.null:
+            sqls.append((self.alter_string_set_null % params, []))
+        else:
+            sqls.append((self.alter_string_drop_null % params, []))
+
+        # Next, set any default
+        self._alter_set_defaults(field, name, params, sqls)
+
+        # Finally, actually change the column
+        if self.allows_combined_alters:
+            sqls, values = zip(*sqls)
+            self.execute(
+                "ALTER TABLE %s %s;" % (self.quote_name(table_name), ", ".join(sqls)),
+                flatten(values),
+            )
+        else:
+            # Databases like e.g. MySQL don't like more than one alter at once.
+            for sql, values in sqls:
+                self.execute("ALTER TABLE %s %s;" % (self.quote_name(table_name), sql), values)
+        
+        if not ignore_constraints:
+            # Add back FK constraints if needed
+            if field.rel and self.supports_foreign_keys:
+                self.execute(
+                    self.foreign_key_sql(
+                        table_name,
+                        field.column,
+                        field.rel.to._meta.db_table,
+                        field.rel.to._meta.get_field(field.rel.field_name).column
+                    )
+                )
+
+
+    def _constraints_affecting_columns(self, table_name, columns, type="UNIQUE"):
+        """
+        Gets the names of the constraints affecting the given columns.
+        If columns is None, returns all constraints of the type on the table.
+        """
+
+        if self.dry_run:
+            raise ValueError("Cannot get constraints for columns during a dry run.")
+
+        if columns is not None:
+            columns = set(columns)
+
+        if type == "CHECK":
+            ifsc_table = "constraint_column_usage"
+        else:
+            ifsc_table = "key_column_usage"
+
+        schema = self._get_schema_name()            
+
+        # First, load all constraint->col mappings for this table.
+        rows = self.execute("""
+            SELECT kc.constraint_name, kc.column_name
+            FROM information_schema.%s AS kc
+            JOIN information_schema.table_constraints AS c ON
+                kc.table_schema = c.table_schema AND
+                kc.table_name = c.table_name AND
+                kc.constraint_name = c.constraint_name
+            WHERE
+                kc.table_schema = %%s AND
+                kc.table_name = %%s AND
+                c.constraint_type = %%s
+        """ % ifsc_table, [schema, table_name, type])
+        
+        # Load into a dict
+        mapping = {}
+        for constraint, column in rows:
+            mapping.setdefault(constraint, set())
+            mapping[constraint].add(column)
+        
+        # Find ones affe