def test_get_migration_classes(self): app = self.create_test_app() # Can't use vanilla import, modules beginning with numbers aren't in grammar M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ["Migration"]).Migration M2 = __import__("fakeapp.migrations.0002_eggs", {}, {}, ["Migration"]).Migration M3 = __import__("fakeapp.migrations.0003_alter_spam", {}, {}, ["Migration"]).Migration self.assertEqual([M1, M2, M3], list(migration.get_migration_classes(app)))
def test_get_migration_classes(self): app = self.create_test_app() # Can't use vanilla import, modules beginning with numbers aren't in grammar M1 = __import__("fakeapp.migrations.0001_spam", {}, {}, ['Migration']).Migration M2 = __import__("fakeapp.migrations.0002_eggs", {}, {}, ['Migration']).Migration M3 = __import__("fakeapp.migrations.0003_alter_spam", {}, {}, ['Migration']).Migration self.assertEqual( [M1, M2, M3], list(migration.get_migration_classes(app)), )
def handle(self, app=None, name="", added_model_list=None, added_field_list=None, initial=False, freeze_list=None, auto=False, stdout=False, **options): # Any supposed lists that are None become empty lists added_model_list = added_model_list or [] added_field_list = added_field_list or [] # --stdout means name = - if stdout: name = "-" # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): print "You cannot use --initial and other options together" print self.usage_str return if auto and (added_model_list or added_field_list or initial): print "You cannot use --auto and other options together" print self.usage_str return # specify the default name 'initial' if a name wasn't specified and we're # doing a migration for an entire app if not name and initial: name = 'initial' # if not name, there's an error if not name: print "You must name this migration" print self.usage_str return if not app: print "Please provide an app in which to create the migration." print self.usage_str return # Make sure the app is short form app = app.split(".")[-1] # See if the app exists app_models_module = models.get_app(app) if not app_models_module: print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app print self.usage_str return # If they've set SOUTH_AUTO_FREEZE_APP = True (or not set it - defaults to True) if not hasattr(settings, 'SOUTH_AUTO_FREEZE_APP') or settings.SOUTH_AUTO_FREEZE_APP: if freeze_list and app not in freeze_list: freeze_list += [app] else: freeze_list = [app] # Make the migrations directory if it's not there app_module_path = app_models_module.__name__.split('.')[0:-1] try: app_module = __import__('.'.join(app_module_path), {}, {}, ['']) except ImportError: print "Couldn't find path to App '%s'." % app print self.usage_str return migrations_dir = os.path.join( os.path.dirname(app_module.__file__), "migrations", ) # Make sure there's a migrations directory and __init__.py if not os.path.isdir(migrations_dir): print "Creating migrations directory at '%s'..." % migrations_dir os.mkdir(migrations_dir) init_path = os.path.join(migrations_dir, "__init__.py") if not os.path.isfile(init_path): # Touch the init py file print "Creating __init__.py in '%s'..." % migrations_dir open(init_path, "w").close() # See what filename is next in line. We assume they use numbers. migrations = migration.get_migration_names(migration.get_app(app)) highest_number = 0 for migration_name in migrations: try: number = int(migration_name.split("_")[0]) highest_number = max(highest_number, number) except ValueError: pass # Make the new filename new_filename = "%04i%s_%s.py" % ( highest_number + 1, "".join([random.choice(string.letters.lower()) for i in range(0)]), # Possible random stuff insertion name, ) # Find the source file encoding, using PEP 0263's method encoding = None first_two_lines = inspect.getsourcelines(app_models_module)[0][:2] for line in first_two_lines: if re.search("coding[:=]\s*([-\w.]+)", line): encoding = line # Initialise forwards, backwards and models to blank things forwards = "" backwards = "" frozen_models = {} # Frozen models, used by the Fake ORM complete_apps = set() # Apps that are completely frozen - useable for diffing. # Sets of actions added_models = set() deleted_models = [] # Special: contains instances _not_ string keys added_fields = set() deleted_fields = [] # Similar to deleted_models changed_fields = [] # (mkey, fname, old_def, new_def) added_uniques = set() # (mkey, field_names) deleted_uniques = set() # (mkey, field_names) # --initial means 'add all models in this app'. if initial: for model in models.get_models(app_models_module): added_models.add("%s.%s" % (app, model._meta.object_name)) # Added models might be 'model' or 'app.model'. for modelname in added_model_list: if "." in modelname: added_models.add(modelname) else: added_models.add("%s.%s" % (app, modelname)) # Fields need translating from "model.field" to (app.model, field) for fielddef in added_field_list: try: modelname, fieldname = fielddef.split(".", 1) except ValueError: print "The field specification '%s' is not in modelname.fieldname format." % fielddef else: added_fields.add(("%s.%s" % (app, modelname), fieldname)) # Add anything frozen (I almost called the dict Iceland...) if freeze_list: for item in freeze_list: if "." in item: # It's a specific model app_name, model_name = item.split(".", 1) model = models.get_model(app_name, model_name) if model is None: print "Cannot find the model '%s' to freeze it." % item print self.usage_str return frozen_models[model] = None else: # Get everything in an app! frozen_models.update(dict([(x, None) for x in models.get_models(models.get_app(item))])) complete_apps.add(item.split(".")[-1]) # For every model in the freeze list, add in frozen dependencies for model in list(frozen_models): frozen_models.update(model_dependencies(model)) ### Automatic Detection ### if auto: # Get the last migration for this app last_models = None app_module = migration.get_app(app) if app_module is None: print "You cannot use automatic detection on the first migration of an app. Try --initial instead." else: migrations = list(migration.get_migration_classes(app_module)) if not migrations: print "You cannot use automatic detection on the first migration of an app. Try --initial instead." else: if hasattr(migrations[-1], "complete_apps") and \ app in migrations[-1].complete_apps: last_models = migrations[-1].models last_orm = migrations[-1].orm else: print "You cannot use automatic detection, since the previous migration does not have this whole app frozen.\nEither make migrations using '--freeze %s' or set 'SOUTH_AUTO_FREEZE_APP = True' in your settings.py." % app # Right, did we manage to get the last set of models? if last_models is None: print self.usage_str return new = dict([ (model_key(model), prep_for_freeze(model)) for model in models.get_models(app_models_module) if ( not getattr(model._meta, "proxy", False) and \ getattr(model._meta, "managed", True) and \ not getattr(model._meta, "abstract", False) ) ]) # And filter other apps out of the old old = dict([ (key, fields) for key, fields in last_models.items() if key.split(".", 1)[0] == app ]) am, dm, cm, af, df, cf, afu, dfu = models_diff(old, new) # For models that were there before and after, do a meta diff was_meta_change = False for mkey in cm: au, du = meta_diff(old[mkey].get("Meta", {}), new[mkey].get("Meta", {})) for entry in au: added_uniques.add((mkey, entry)) was_meta_change = True for entry in du: deleted_uniques.add((mkey, entry, last_orm[mkey])) was_meta_change = True if not (am or dm or af or df or cf or afu or dfu or was_meta_change): print "Nothing seems to have changed." return # Add items to the todo lists added_models.update(am) added_fields.update(af) changed_fields.extend(cf) # Deleted models are from the past, and so we use instances instead. for mkey in dm: model = last_orm[mkey] fields = last_models[mkey] if "Meta" in fields: del fields['Meta'] deleted_models.append((model, fields, last_models)) # For deleted fields, we tag the instance on the end too for mkey, fname in df: deleted_fields.append(( mkey, fname, last_orm[mkey]._meta.get_field_by_name(fname)[0], last_models[mkey][fname], last_models, )) # Uniques need merging added_uniques = added_uniques.union(afu) for mkey, entry in dfu: deleted_uniques.add((mkey, entry, last_orm[mkey])) ### Added model ### for mkey in added_models: print " + Added model '%s'" % (mkey,) model = model_unkey(mkey) # Add the model's dependencies to the frozens frozen_models.update(model_dependencies(model)) # Get the field definitions fields = modelsinspector.get_model_fields(model) # Turn the (class, args, kwargs) format into a string fields = triples_to_defs(app, model, fields) # Make the code forwards += CREATE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table, "\n ".join(["('%s', orm[%r])," % (fname, mkey + ":" + fname) for fname, fdef in fields.items()]), model._meta.app_label, model._meta.object_name, ) # And the backwards code backwards += DELETE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table ) # Now add M2M fields to be done for field in model._meta.local_many_to_many: added_fields.add((mkey, field.attname)) # And unique_togethers to be added for ut in model._meta.unique_together: added_uniques.add((mkey, tuple(ut))) ### Added fields ### for mkey, field_name in added_fields: # Get the model model = model_unkey(mkey) # Get the field try: field = model._meta.get_field(field_name) except FieldDoesNotExist: print "Model '%s' doesn't have a field '%s'" % (mkey, field_name) return # ManyToMany fields need special attention. if isinstance(field, models.ManyToManyField): if not field.rel.through: # Bug #120 # Add a frozen model for each side frozen_models[model] = None frozen_models[field.rel.to] = None # And a field defn, that's actually a table creation forwards += CREATE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table(), field.m2m_column_name()[:-3], # strip off the '_id' at the end poss_ormise(app, model, model._meta.object_name), field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned poss_ormise(app, field.rel.to, field.rel.to._meta.object_name) ) backwards += DELETE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table() ) print " + Added M2M '%s.%s'" % (mkey, field_name) continue # GenericRelations need ignoring if isinstance(field, GenericRelation): continue print " + Added field '%s.%s'" % (mkey, field_name) # Add any dependencies frozen_models.update(field_dependencies(field)) # Work out the definition triple = remove_useless_attributes( modelsinspector.get_model_fields(model)[field_name]) field_definition = make_field_constructor(app, field, triple) forwards += CREATE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.name, "orm[%r]" % (mkey + ":" + field.name), ) backwards += DELETE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.column, ) ### Deleted fields ### for mkey, field_name, field, triple, last_models in deleted_fields: print " - Deleted field '%s.%s'" % (mkey, field_name) # Get the model model = model_unkey(mkey) # ManyToMany fields need special attention. if isinstance(field, models.ManyToManyField): # And a field defn, that's actually a table deletion forwards += DELETE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table() ) backwards += CREATE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table(), field.m2m_column_name()[:-3], # strip off the '_id' at the end poss_ormise(app, model, model._meta.object_name), field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned poss_ormise(app, field.rel.to, field.rel.to._meta.object_name) ) continue # Work out the definition triple = remove_useless_attributes(triple) field_definition = make_field_constructor(app, field, triple) forwards += DELETE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.column, ) backwards += CREATE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.name, "orm[%r]" % (mkey + ":" + field.name), ) ### Deleted model ### for model, fields, last_models in deleted_models: print " - Deleted model '%s.%s'" % (model._meta.app_label,model._meta.object_name) # Turn the (class, args, kwargs) format into a string fields = triples_to_defs(app, model, fields) # Make the code forwards += DELETE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table ) # And the backwards code backwards += CREATE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table, "\n ".join(["('%s', orm[%r])," % (fname, mkey + ":" + fname) for fname, fdef in fields.items()]), model._meta.app_label, model._meta.object_name, ) ### Changed fields ### for mkey, field_name, old_triple, new_triple in changed_fields: model = model_unkey(mkey) old_def = triples_to_defs(app, model, { field_name: old_triple, })[field_name] new_def = triples_to_defs(app, model, { field_name: new_triple, })[field_name] # We need to create the field, to see if it needs _id, or if it's an M2M field = model._meta.get_field_by_name(field_name)[0] if hasattr(field, "m2m_db_table"): # See if anything has ACTUALLY changed if old_triple[1] != new_triple[1]: print " ! Detected change to the target model of M2M field '%s.%s'. South can't handle this; leaving this change out." % (mkey, field_name) continue print " ~ Changed field '%s.%s'." % (mkey, field_name) forwards += CHANGE_FIELD_SNIPPET % ( model._meta.object_name, field_name, new_def, model._meta.db_table, field.get_attname(), "orm[%r]" % (mkey + ":" + field.name), ) backwards += CHANGE_FIELD_SNIPPET % ( model._meta.object_name, field_name, old_def, model._meta.db_table, field.get_attname(), "orm[%r]" % (mkey + ":" + field.name), ) ### Added unique_togethers ### for mkey, ut in added_uniques: model = model_unkey(mkey) if len(ut) == 1: print " + Added unique for %s on %s." % (", ".join(ut), model._meta.object_name) else: print " + Added unique_together for [%s] on %s." % (", ".join(ut), model._meta.object_name) cols = [get_field_column(model, f) for f in ut] forwards += CREATE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) backwards = DELETE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) + backwards ### Deleted unique_togethers ### for mkey, ut, model in deleted_uniques: if len(ut) == 1: print " - Deleted unique for %s on %s." % (", ".join(ut), model._meta.object_name) else: print " - Deleted unique_together for [%s] on %s." % (", ".join(ut), model._meta.object_name) cols = [get_field_column(model, f) for f in ut] forwards = DELETE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) + forwards backwards += CREATE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) # Default values for forwards/backwards if (not forwards) and (not backwards): forwards = '"Write your forwards migration here"' backwards = '"Write your backwards migration here"' all_models = {} # Fill out frozen model definitions for model, last_models in frozen_models.items(): all_models[model_key(model)] = prep_for_freeze(model, last_models) # Do some model cleanup, and warnings for modelname, model in all_models.items(): for fieldname, fielddef in model.items(): # Remove empty-after-cleaning Metas. if fieldname == "Meta" and not fielddef: del model['Meta'] # Warn about undefined fields elif fielddef is None: print "WARNING: Cannot get definition for '%s' on '%s'. Please edit the migration manually to define it, or add the south_field_triple method to it." % ( fieldname, modelname, ) model[fieldname] = FIELD_NEEDS_DEF_SNIPPET # So, what's in this file, then? file_contents = MIGRATION_SNIPPET % ( encoding or "", '.'.join(app_module_path), forwards, backwards, pprint_frozen_models(all_models), complete_apps and "complete_apps = [%s]" % (", ".join(map(repr, complete_apps))) or "" ) # - is a special name which means 'print to stdout' if name == "-": print file_contents # Write the migration file if the name isn't - else: fp = open(os.path.join(migrations_dir, new_filename), "w") fp.write(file_contents) fp.close() print "Created %s." % new_filename
def handle(self, app=None, name="", added_model_list=None, added_field_list=None, initial=False, freeze_list=None, auto=False, **options): # Any supposed lists that are None become empty lists added_model_list = added_model_list or [] added_field_list = added_field_list or [] # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): print "You cannot use --initial and other options together" return if auto and (added_model_list or added_field_list or initial): print "You cannot use --auto and other options together" return # specify the default name 'initial' if a name wasn't specified and we're # doing a migration for an entire app if not name and initial: name = 'initial' # if not name, there's an error if not name: print "You must name this migration" return if not app: print "Please provide an app in which to create the migration." return # Make sure the app is short form app = app.split(".")[-1] # See if the app exists app_models_module = models.get_app(app) if not app_models_module: print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app return # If they've set SOUTH_AUTO_FREEZE_APP = True (or not set it - defaults to True) if not hasattr( settings, 'SOUTH_AUTO_FREEZE_APP') or settings.SOUTH_AUTO_FREEZE_APP: if freeze_list and app not in freeze_list: freeze_list += [app] else: freeze_list = [app] # Make the migrations directory if it's not there app_module_path = app_models_module.__name__.split('.')[0:-1] try: app_module = __import__('.'.join(app_module_path), {}, {}, ['']) except ImportError: print "Couldn't find path to App '%s'." % app return migrations_dir = os.path.join( os.path.dirname(app_module.__file__), "migrations", ) # Make sure there's a migrations directory and __init__.py if not os.path.isdir(migrations_dir): print "Creating migrations directory at '%s'..." % migrations_dir os.mkdir(migrations_dir) init_path = os.path.join(migrations_dir, "__init__.py") if not os.path.isfile(init_path): # Touch the init py file print "Creating __init__.py in '%s'..." % migrations_dir open(init_path, "w").close() # See what filename is next in line. We assume they use numbers. migrations = migration.get_migration_names(migration.get_app(app)) highest_number = 0 for migration_name in migrations: try: number = int(migration_name.split("_")[0]) highest_number = max(highest_number, number) except ValueError: pass # Make the new filename new_filename = "%04i%s_%s.py" % ( highest_number + 1, "".join([random.choice(string.letters.lower()) for i in range(0)]), # Possible random stuff insertion name, ) # Find the source file encoding, using PEP 0263's method encoding = None first_two_lines = inspect.getsourcelines(app_models_module)[0][:2] for line in first_two_lines: if re.search("coding[:=]\s*([-\w.]+)", line): encoding = line # Initialise forwards, backwards and models to blank things forwards = "" backwards = "" frozen_models = {} # Frozen models, used by the Fake ORM stub_models = { } # Frozen models, but only enough for relation ends (old mock models) complete_apps = set( ) # Apps that are completely frozen - useable for diffing. # Sets of actions added_models = set() deleted_models = [] # Special: contains instances _not_ string keys added_fields = set() deleted_fields = [] # Similar to deleted_models changed_fields = [] # (mkey, fname, old_def, new_def) added_uniques = set() # (mkey, field_names) deleted_uniques = set() # (mkey, field_names) # --initial means 'add all models in this app'. if initial: for model in models.get_models(app_models_module): added_models.add("%s.%s" % (app, model._meta.object_name)) # Added models might be 'model' or 'app.model'. for modelname in added_model_list: if "." in modelname: added_models.add(modelname) else: added_models.add("%s.%s" % (app, modelname)) # Fields need translating from "model.field" to (app.model, field) for fielddef in added_field_list: try: modelname, fieldname = fielddef.split(".", 1) except ValueError: print "The field specification '%s' is not in modelname.fieldname format." % fielddef else: added_fields.add(("%s.%s" % (app, modelname), fieldname)) # Add anything frozen (I almost called the dict Iceland...) if freeze_list: for item in freeze_list: if "." in item: # It's a specific model app_name, model_name = item.split(".", 1) model = models.get_model(app_name, model_name) if model is None: print "Cannot find the model '%s' to freeze it." % item return frozen_models[model] = None else: # Get everything in an app! frozen_models.update( dict([(x, None) for x in models.get_models(models.get_app(item)) ])) complete_apps.add(item.split(".")[-1]) # For every model in the freeze list, add in dependency stubs for model in frozen_models: stub_models.update(model_dependencies(model)) ### Automatic Detection ### if auto: # Get the last migration for this app last_models = None app_module = migration.get_app(app) if app_module is None: print "You cannot use automatic detection on the first migration of an app. Try --initial instead." else: migrations = list(migration.get_migration_classes(app_module)) if not migrations: print "You cannot use automatic detection on the first migration of an app. Try --initial instead." else: if hasattr(migrations[-1], "complete_apps") and \ app in migrations[-1].complete_apps: last_models = migrations[-1].models last_orm = migrations[-1].orm else: print "You cannot use automatic detection, since the previous migration does not have this whole app frozen.\nEither make migrations using '--freeze %s' or set 'SOUTH_AUTO_FREEZE_APP = True' in your settings.py." % app # Right, did we manage to get the last set of models? if last_models is None: return # Good! Get new things. new = dict([(model_key(model), prep_for_freeze(model)) for model in models.get_models(app_models_module)]) # And filter other apps out of the old old = dict([(key, fields) for key, fields in last_models.items() if key.split(".", 1)[0] == app]) am, dm, cm, af, df, cf = models_diff(old, new) # For models that were there before and after, do a meta diff was_meta_change = False for mkey in cm: au, du = meta_diff(old[mkey].get("Meta", {}), new[mkey].get("Meta", {})) for entry in au: added_uniques.add((mkey, entry)) was_meta_change = True for entry in du: deleted_uniques.add((mkey, entry)) was_meta_change = True if not (am or dm or af or df or cf or was_meta_change): print "Nothing seems to have changed." return # Add items to the todo lists added_models.update(am) added_fields.update(af) changed_fields.extend(cf) # Deleted models are from the past, and so we use instances instead. for mkey in dm: model = last_orm[mkey] fields = last_models[mkey] if "Meta" in fields: del fields['Meta'] deleted_models.append((model, fields, last_models)) # For deleted fields, we tag the instance on the end too for mkey, fname in df: deleted_fields.append(( mkey, fname, last_orm[mkey]._meta.get_field_by_name(fname)[0], last_models[mkey][fname], last_models, )) ### Added model ### for mkey in added_models: print " + Added model '%s'" % (mkey, ) model = model_unkey(mkey) # Add the model's dependencies to the stubs stub_models.update(model_dependencies(model)) # Get the field definitions fields = modelsparser.get_model_fields(model) # Turn the (class, args, kwargs) format into a string fields = triples_to_defs(app, model, fields) # Make the code forwards += CREATE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table, "\n ".join([ "('%s', %s)," % (fname, fdef) for fname, fdef in fields.items() ]), model._meta.app_label, model._meta.object_name, ) # And the backwards code backwards += DELETE_TABLE_SNIPPET % (model._meta.object_name, model._meta.db_table) # Now add M2M fields to be done for field in model._meta.local_many_to_many: added_fields.add((mkey, field.attname)) # And unique_togethers to be added for ut in model._meta.unique_together: added_uniques.add((mkey, tuple(ut))) ### Added fields ### for mkey, field_name in added_fields: print " + Added field '%s.%s'" % (mkey, field_name) # Get the model model = model_unkey(mkey) # Get the field try: field = model._meta.get_field(field_name) except FieldDoesNotExist: print "Model '%s' doesn't have a field '%s'" % (mkey, field_name) return # ManyToMany fields need special attention. if isinstance(field, models.ManyToManyField): if not field.rel.through: # Bug #120 # Add a stub model for each side stub_models[model] = None stub_models[field.rel.to] = None # And a field defn, that's actually a table creation forwards += CREATE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table(), field.m2m_column_name() [:-3], # strip off the '_id' at the end model._meta.object_name, field.m2m_reverse_name() [:-3], # strip off the '_id' at the ned field.rel.to._meta.object_name) backwards += DELETE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table()) continue # GenericRelations need ignoring if isinstance(field, GenericRelation): continue # Add any dependencies stub_models.update(field_dependencies(field)) # Work out the definition triple = remove_useless_attributes( modelsparser.get_model_fields(model)[field_name]) field_definition = make_field_constructor(app, field, triple) forwards += CREATE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.name, field_definition, ) backwards += DELETE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.column, ) ### Deleted fields ### for mkey, field_name, field, triple, last_models in deleted_fields: print " - Deleted field '%s.%s'" % (mkey, field_name) # Get the model model = model_unkey(mkey) # ManyToMany fields need special attention. if isinstance(field, models.ManyToManyField): # Add a stub model for each side, if they're not already there # (if we just added old versions, we might override new ones) if model not in stub_models: stub_models[model] = last_models if field.rel.to not in last_models: stub_models[field.rel.to] = last_models # And a field defn, that's actually a table deletion forwards += DELETE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table()) backwards += CREATE_M2MFIELD_SNIPPET % ( model._meta.object_name, field.name, field.m2m_db_table(), field.m2m_column_name() [:-3], # strip off the '_id' at the end model._meta.object_name, field.m2m_reverse_name() [:-3], # strip off the '_id' at the ned field.rel.to._meta.object_name) continue # Add any dependencies deps = field_dependencies(field, last_models) deps.update(stub_models) stub_models = deps # Work out the definition triple = remove_useless_attributes(triple) field_definition = make_field_constructor(app, field, triple) forwards += DELETE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.column, ) backwards += CREATE_FIELD_SNIPPET % ( model._meta.object_name, field.name, model._meta.db_table, field.name, field_definition, ) ### Deleted model ### for model, fields, last_models in deleted_models: print " - Deleted model '%s.%s'" % (model._meta.app_label, model._meta.object_name) # Add the model's dependencies to the stubs deps = model_dependencies(model, last_models) deps.update(stub_models) stub_models = deps # Turn the (class, args, kwargs) format into a string fields = triples_to_defs(app, model, fields) # Make the code forwards += DELETE_TABLE_SNIPPET % (model._meta.object_name, model._meta.db_table) # And the backwards code backwards += CREATE_TABLE_SNIPPET % ( model._meta.object_name, model._meta.db_table, "\n ".join([ "('%s', %s)," % (fname, fdef) for fname, fdef in fields.items() ]), model._meta.app_label, model._meta.object_name, ) ### Changed fields ### for mkey, field_name, old_triple, new_triple in changed_fields: model = model_unkey(mkey) old_def = triples_to_defs(app, model, { field_name: old_triple, })[field_name] new_def = triples_to_defs(app, model, { field_name: new_triple, })[field_name] # We need to create the field, to see if it needs _id, or if it's an M2M field = model._meta.get_field_by_name(field_name)[0] if hasattr(field, "m2m_db_table"): # See if anything has ACTUALLY changed if old_triple[1] != new_triple[1]: print " ! Detected change to the target model of M2M field '%s.%s'. South can't handle this; leaving this change out." % ( mkey, field_name) continue print " ~ Changed field '%s.%s'." % (mkey, field_name) forwards += CHANGE_FIELD_SNIPPET % ( model._meta.object_name, field_name, model._meta.db_table, field.get_attname(), new_def, ) backwards += CHANGE_FIELD_SNIPPET % ( model._meta.object_name, field_name, model._meta.db_table, field.get_attname(), old_def, ) ### Added unique_togethers ### for mkey, ut in added_uniques: model = model_unkey(mkey) print " + Added unique_together for [%s] on %s." % ( ", ".join(ut), model._meta.object_name) cols = [get_field_column(model, f) for f in ut] forwards += CREATE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) backwards += DELETE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, cols, ) ### Deleted unique_togethers ### for mkey, ut in deleted_uniques: model = model_unkey(mkey) print " - Deleted unique_together for [%s] on %s." % ( ", ".join(ut), model._meta.object_name) forwards += DELETE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, ut, ) backwards += CREATE_UNIQUE_SNIPPET % ( ", ".join(ut), model._meta.object_name, model._meta.db_table, ut, ) # Default values for forwards/backwards if (not forwards) and (not backwards): forwards = '"Write your forwards migration here"' backwards = '"Write your backwards migration here"' all_models = {} # Fill out frozen model definitions for model, last_models in frozen_models.items(): all_models[model_key(model)] = prep_for_freeze(model, last_models) # Fill out stub model definitions for model, last_models in stub_models.items(): key = model_key(model) if key in all_models: continue # We'd rather use full models than stubs. all_models[key] = prep_for_stub(model, last_models) # Do some model cleanup, and warnings for modelname, model in all_models.items(): for fieldname, fielddef in model.items(): # Remove empty-after-cleaning Metas. if fieldname == "Meta" and not fielddef: del model['Meta'] # Warn about undefined fields elif fielddef is None: print "WARNING: Cannot get definition for '%s' on '%s'. Please edit the migration manually." % ( fieldname, modelname, ) model[fieldname] = FIELD_NEEDS_DEF_SNIPPET # Write the migration file fp = open(os.path.join(migrations_dir, new_filename), "w") fp.write(MIGRATION_SNIPPET % (encoding or "", '.'.join(app_module_path), forwards, backwards, pprint_frozen_models(all_models), complete_apps and "complete_apps = [%s]" % (", ".join(map(repr, complete_apps))) or "")) fp.close() print "Created %s." % new_filename