def test_get_app(self): P1 = __import__("fakeapp.migrations", {}, {}, ['']) self.assertEqual(P1, migration.get_app("fakeapp")) self.assertEqual( P1, migration.get_app(self.create_fake_app("fakeapp.models")))
def test_dependencies(self): fakeapp = migration.get_app("fakeapp") otherfakeapp = migration.get_app("otherfakeapp") # Test a simple path tree = migration.dependency_tree() self.assertEqual( map( snd, migration.needed_before_forwards(tree, fakeapp, "0003_alter_spam")), ['0001_spam', '0002_eggs'], ) # And a complex one, with both back and forwards deps self.assertEqual( map( snd, migration.needed_before_forwards(tree, otherfakeapp, "0003_third")), [ '0001_spam', '0001_first', '0002_second', '0002_eggs', '0003_alter_spam' ], )
def test_all_migrations(self): app = migration.get_app("fakeapp") otherapp = migration.get_app("otherfakeapp") self.assertEqual( { app: { "0001_spam": migration.get_migration(app, "0001_spam"), "0002_eggs": migration.get_migration(app, "0002_eggs"), "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"), }, otherapp: { "0001_first": migration.get_migration( otherapp, "0001_first"), "0002_second": migration.get_migration( otherapp, "0002_second"), "0003_third": migration.get_migration( otherapp, "0003_third"), }, }, migration.all_migrations(), )
def handle(self, app=None, *args, **options): # Make sure we have an app if not app: print "Please specify an app to convert." return # See if the app exists app = app.split(".")[-1] try: app_module = models.get_app(app) except ImproperlyConfigured: print "There is no enabled application matching '%s'." % app return # Try to get its list of models model_list = models.get_models(app_module) if not model_list: print "This application has no models; this command is for applications that already have models syncdb'd." print "Make some models, and then use ./manage.py startmigration %s --initial instead." % app return # Ask South if it thinks it's already got migrations if get_app(app_module): print "This application is already managed by South." return # Finally! It seems we've got a candidate, so do the two-command trick management.call_command("startmigration", app, initial=True) management.call_command("migrate", app, "0001", fake=True) print print "App '%s' converted. Note that South assumed the application's models matched the database" % app print "(i.e. you haven't changed it since last syncdb); if you have, you should delete the %s/migrations" print "directory, revert models.py so it matches the database, and try again."
def test_apply_migrations(self): migration.MigrationHistory.objects.all().delete() app = migration.get_app("fakeapp") # We should start with no migrations self.assertEqual(list(migration.MigrationHistory.objects.all()), []) # Apply them normally tree = migration.dependency_tree() migration.migrate_app(app, tree, target_name=None, resolve_mode=None, fake=False, verbosity=0) # We should finish with all migrations self.assertListEqual( ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migration.migrate_app(app, tree, target_name="zero", resolve_mode=None, fake=False, verbosity=0) # Finish with none self.assertEqual(list(migration.MigrationHistory.objects.all()), [])
def test_migration_merge_forwards(self): migration.MigrationHistory.objects.all().delete() app = migration.get_app("fakeapp") # We should start with no migrations self.assertEqual(list(migration.MigrationHistory.objects.all()), []) # Insert one in the wrong order migration.MigrationHistory.objects.create( app_name = "fakeapp", migration = "0002_eggs", applied = datetime.datetime.now(), ) # Did it go in? self.assertListEqual( ( (u"fakeapp", u"0002_eggs"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally tree = migration.dependency_tree() try: # Redirect the error it will print to nowhere stdout, sys.stdout = sys.stdout, StringIO.StringIO() migration.migrate_app(app, tree, target_name=None, resolve_mode=None, fake=False, verbosity=0) sys.stdout = stdout except SystemExit: pass # Nothing should have changed (no merge mode!) self.assertListEqual( ( (u"fakeapp", u"0002_eggs"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migration.migrate_app(app, tree, target_name=None, resolve_mode="merge", fake=False, verbosity=0) # We should finish with all migrations self.assertListEqual( ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migration.migrate_app(app, tree, target_name="0002", resolve_mode=None, fake=False, verbosity=0) migration.migrate_app(app, tree, target_name="0001", resolve_mode=None, fake=True, verbosity=0) migration.migrate_app(app, tree, target_name="zero", resolve_mode=None, fake=False, verbosity=0) # Finish with none self.assertEqual(list(migration.MigrationHistory.objects.all()), [])
def test_alter_column_null(self): def null_ok(): from django.db import connection, transaction # the DBAPI introspection module fails on postgres NULLs. cursor = connection.cursor() try: cursor.execute("INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, 10.1, now(), NULL);") except: transaction.rollback() return False else: cursor.execute("DELETE FROM southtest_spam") transaction.commit() return True app = migration.get_app("fakeapp") tree = migration.dependency_tree() self.assertEqual(list(migration.MigrationHistory.objects.all()), []) # by default name is NOT NULL migration.migrate_app(app, tree, target_name="0002", resolve_mode=None, fake=False, verbosity=0) self.failIf(null_ok()) # after 0003, it should be NULL migration.migrate_app(app, tree, target_name="0003", resolve_mode=None, fake=False, verbosity=0) self.assert_(null_ok()) # make sure it is NOT NULL again migration.migrate_app(app, tree, target_name="0002", resolve_mode=None, fake=False, verbosity=0) self.failIf(null_ok(), 'name not null after migration') # finish with no migrations, otherwise other tests fail... migration.migrate_app(app, tree, target_name="zero", resolve_mode=None, fake=False, verbosity=0) self.assertEqual(list(migration.MigrationHistory.objects.all()), [])
def handle_noargs(self, **options): # Work out what uses migrations and so doesn't need syncing apps_needing_sync = [] apps_migrated = [] for app in models.get_apps(): app_name = get_app_name(app) migrations = migration.get_app(app) if migrations is None: apps_needing_sync.append(app_name) else: # This is a migrated app, leave it apps_migrated.append(app_name) # Run syncdb on only the ones needed print "Syncing..." old_installed, settings.INSTALLED_APPS = settings.INSTALLED_APPS, apps_needing_sync old_app_store, cache.app_store = cache.app_store, SortedDict([ (k, v) for (k, v) in cache.app_store.items() if get_app_name(k) in apps_needing_sync ]) syncdb.Command().execute(**options) settings.INSTALLED_APPS = old_installed cache.app_store = old_app_store # Migrate if needed if options.get('migrate', True): print "Migrating..." management.call_command('migrate') # Be obvious about what we did print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync) if options.get('migrate', True): print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated) else: print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated) print "(use ./manage.py migrate to migrate these)"
def test_dependencies(self): fakeapp = migration.get_app("fakeapp") otherfakeapp = migration.get_app("otherfakeapp") # Test a simple path tree = migration.dependency_tree() self.assertEqual( map(snd, migration.needed_before_forwards(tree, fakeapp, "0003_alter_spam")), ['0001_spam', '0002_eggs'], ) # And a complex one, with both back and forwards deps self.assertEqual( map(snd, migration.needed_before_forwards(tree, otherfakeapp, "0003_third")), ['0001_spam', '0001_first', '0002_second', '0002_eggs', '0003_alter_spam'], )
def test_migration_merge_forwards(self): app = migration.get_app("fakeapp") # We should start with no migrations self.assertEqual(list(migration.MigrationHistory.objects.all()), []) # Insert one in the wrong order migration.MigrationHistory.objects.create( app_name = "fakeapp", migration = "0002_eggs", applied = datetime.datetime.now(), ) # Did it go in? self.assertListEqual( ( (u"fakeapp", u"0002_eggs"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally try: migration.migrate_app(app, target_name=None, resolve_mode=None, fake=False, silent=True) except SystemExit: pass # Nothing should have changed (no merge mode!) self.assertListEqual( ( (u"fakeapp", u"0002_eggs"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migration.migrate_app(app, target_name=None, resolve_mode="merge", fake=False, silent=True) # We should finish with all migrations self.assertListEqual( ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migration.migrate_app(app, target_name="0002", resolve_mode=None, fake=False, silent=True) migration.migrate_app(app, target_name="0001", resolve_mode=None, fake=True, silent=True) migration.migrate_app(app, target_name="zero", resolve_mode=None, fake=False, silent=True) # Finish with none self.assertEqual(list(migration.MigrationHistory.objects.all()), [])
def test_all_migrations(self): app = migration.get_app("fakeapp") otherapp = migration.get_app("otherfakeapp") self.assertEqual({ app: { "0001_spam": migration.get_migration(app, "0001_spam"), "0002_eggs": migration.get_migration(app, "0002_eggs"), "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"), }, otherapp: { "0001_first": migration.get_migration(otherapp, "0001_first"), "0002_second": migration.get_migration(otherapp, "0002_second"), "0003_third": migration.get_migration(otherapp, "0003_third"), }, }, migration.all_migrations(), )
def test_all_migrations(self): app = migration.get_app("fakeapp") self.assertEqual( {app: { "0001_spam": migration.get_migration(app, "0001_spam"), "0002_eggs": migration.get_migration(app, "0002_eggs"), }}, migration.all_migrations(), )
def test_all_migrations(self): app = migration.get_app("fakeapp") self.assertEqual( {app: { "0001_spam": migration.get_migration(app, "0001_spam"), "0002_eggs": migration.get_migration(app, "0002_eggs"), "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"), }}, migration.all_migrations(), )
def handle_noargs(self, migrate_all=False, **options): # Work out what uses migrations and so doesn't need syncing apps_needing_sync = [] apps_migrated = [] for app in models.get_apps(): app_name = get_app_name(app) migrations = migration.get_app(app) if migrations is None or migrate_all: apps_needing_sync.append(app_name) else: # This is a migrated app, leave it apps_migrated.append(app_name) verbosity = int(options.get('verbosity', 0)) # Run syncdb on only the ones needed if verbosity: print "Syncing..." old_installed, settings.INSTALLED_APPS = settings.INSTALLED_APPS, apps_needing_sync old_app_store, cache.app_store = cache.app_store, SortedDict([ (k, v) for (k, v) in cache.app_store.items() if get_app_name(k) in apps_needing_sync ]) # This will allow the setting of the MySQL storage engine, for example. db.connection_init() # OK, run the actual syncdb syncdb.Command().execute(**options) settings.INSTALLED_APPS = old_installed cache.app_store = old_app_store # Migrate if needed if options.get('migrate', True): if verbosity: print "Migrating..." management.call_command('migrate', **options) # Be obvious about what we did if verbosity: print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync) if options.get('migrate', True): if verbosity: print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated) else: if verbosity: print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated) print "(use ./manage.py migrate to migrate these)"
def handle(self, app=None, *args, **options): # Make sure we have an app if not app: print "Please specify an app to convert." return # See if the app exists app = app.split(".")[-1] try: app_module = models.get_app(app) except ImproperlyConfigured: print "There is no enabled application matching '%s'." % app return # Try to get its list of models model_list = models.get_models(app_module) if not model_list: print "This application has no models; this command is for applications that already have models syncdb'd." print "Make some models, and then use ./manage.py startmigration %s --initial instead." % app return # Ask South if it thinks it's already got migrations if get_app(app_module): print "This application is already managed by South." return # Finally! It seems we've got a candidate, so do the two-command trick verbosity = int(options.get('verbosity', 0)) management.call_command("startmigration", app, initial=True, verbosity=verbosity) # Now, we need to re-clean and sanitise appcache hacks.clear_app_cache() hacks.repopulate_app_cache() # Now, migrate management.call_command("migrate", app, "0001", fake=True, verbosity=verbosity) print print "App '%s' converted. Note that South assumed the application's models matched the database" % app print "(i.e. you haven't changed it since last syncdb); if you have, you should delete the %s/migrations" print "directory, revert models.py so it matches the database, and try again."
def test_apply_migrations(self): app = migration.get_app("fakeapp") # We should start with no migrations self.assertEqual(list(migration.MigrationHistory.objects.all()), []) # Apply them normally migration.migrate_app(app, target_name=None, resolve_mode=None, fake=False, silent=True) # We should finish with all migrations self.assertListEqual( ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), ), migration.MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migration.migrate_app(app, target_name="zero", resolve_mode=None, fake=False, silent=True) # Finish with none self.assertEqual(list(migration.MigrationHistory.objects.all()), [])
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
class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--all', action='store_true', dest='all_apps', default=False, help='Run the specified migration for all apps.'), make_option('--list', action='store_true', dest='list', default=False, help='List migrations noting those that have been applied'), make_option('--skip', action='store_true', dest='skip', default=False, help='Will skip over out-of-order missing migrations'), make_option('--merge', action='store_true', dest='merge', default=False, help='Will run out-of-order missing migrations as they are - no rollbacks.'), make_option('--no-initial-data', action='store_true', dest='no_initial_data', default=False, help='Skips loading initial data if specified.'), make_option('--fake', action='store_true', dest='fake', default=False, help="Pretends to do the migrations, but doesn't actually execute them."), make_option('--db-dry-run', action='store_true', dest='db_dry_run', default=False, help="Doesn't execute the SQL generated by the db methods, and doesn't store a record that the migration(s) occurred. Useful to test migrations before applying them."), ) if '--verbosity' not in [opt.get_opt_string() for opt in BaseCommand.option_list]: option_list += ( make_option('--verbosity', action='store', dest='verbosity', default='1', type='choice', choices=['0', '1', '2'], help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), ) help = "Runs migrations for all apps." args = "[appname] [migrationname|zero] [--all] [--list] [--skip] [--merge] [--no-initial-data] [--fake] [--db-dry-run]" def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, list=False, **options): # Work out what the resolve mode is resolve_mode = merge and "merge" or (skip and "skip" or None) # NOTE: THIS IS DUPLICATED FROM django.core.management.commands.syncdb # This code imports any module named 'management' in INSTALLED_APPS. # The 'management' module is the preferred way of listening to post_syncdb # signals, and since we're sending those out with create_table migrations, # we need apps to behave correctly. for app_name in settings.INSTALLED_APPS: try: __import__(app_name + '.management', {}, {}, ['']) except ImportError, exc: msg = exc.args[0] if not msg.startswith('No module named') or 'management' not in msg: raise # END DJANGO DUPE CODE # if all_apps flag is set, shift app over to target if options.get('all_apps', False): target = app app = None # Migrate each app if app: apps = [migration.get_app(app.split(".")[-1])] if apps == [None]: print "The app '%s' does not appear to use migrations." % app print "./manage.py migrate " + self.args return else: apps = migration.get_migrated_apps() if list and apps: list_migrations(apps) if not list: tree = migration.dependency_tree() for app in apps: result = migration.migrate_app( app, tree, resolve_mode = resolve_mode, target_name = target, fake = fake, db_dry_run = db_dry_run, verbosity = int(options.get('verbosity', 0)), load_inital_data = not options.get('no_initial_data', False), skip = skip, ) if result is False: return
def test_get_app(self): P1 = __import__("fakeapp.migrations", {}, {}, ['']) self.assertEqual(P1, migration.get_app("fakeapp")) self.assertEqual(P1, migration.get_app(self.create_fake_app("fakeapp.models")))
def handle(self, app=None, name="", model_list=None, field_list=None, initial=False, **options): # If model_list is None, then it's an empty list model_list = model_list or [] # If field_list is None, then it's an empty list field_list = field_list or [] # make sure --model and --all aren't both specified if initial and (model_list or field_list): print "You cannot use --initial 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 # 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 # Determine what models should be included in this migration. models_to_migrate = [] if initial: models_to_migrate = models.get_models(app_models_module) if not models_to_migrate: print "No models found in app '%s'" % (app) return else: for model_name in model_list: model = models.get_model(app, model_name) if not model: print "Couldn't find model '%s' in app '%s'" % (model_name, app) return models_to_migrate.append(model) # See what fields need to be included fields_to_add = [] for field_spec in field_list: model_name, field_name = field_spec.split(".", 1) model = models.get_model(app, model_name) if not model: print "Couldn't find model '%s' in app '%s'" % (model_name, app) return try: field = model._meta.get_field(field_name) except FieldDoesNotExist: print "Model '%s' doesn't have a field '%s'" % (model_name, field_name) return fields_to_add.append((model, field_name, field)) # 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, ) # If there's a model, make the migration skeleton, else leave it bare forwards, backwards = '', '' if fields_to_add: # First, do the added fields for model, field_name, field in fields_to_add: field_definition = generate_field_definition(model, field) if isinstance(field, models.ManyToManyField): # Make a mock model for each side mock_model = "\n".join([ create_mock_model(model, " "), create_mock_model(field.rel.to, " ") ]) # And a field defn, that's actually a table creation forwards += ''' # Mock Model %s # Adding ManyToManyField '%s.%s' db.create_table('%s', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('%s', models.ForeignKey(%s, null=False)), ('%s', models.ForeignKey(%s, null=False)) )) ''' % ( mock_model, 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 += ''' # Dropping ManyToManyField '%s.%s' db.drop_table('%s')''' % ( model._meta.object_name, field.name, field.m2m_db_table() ) continue elif field.rel: # ForeignKey, etc. mock_model = create_mock_model(field.rel.to, " ") field_definition = related_field_definition(field, field_definition) else: mock_model = None # If we can't get it (inspect madness?) then insert placeholder if not field_definition: print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \ (model._meta.object_name, field.name) field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name) if mock_model: forwards += ''' # Mock model %s ''' % (mock_model) forwards += ''' # Adding field '%s.%s' db.add_column(%r, %r, %s) ''' % ( model._meta.object_name, field.name, model._meta.db_table, field.name, field_definition, ) backwards += ''' # Deleting field '%s.%s' db.delete_column(%r, %r) ''' % ( model._meta.object_name, field.name, model._meta.db_table, field.column, ) if models_to_migrate: # Now, do the added models for model in models_to_migrate: table_name = model._meta.db_table mock_models = [] fields = [] for f in model._meta.local_fields: # Look up the field definition to see how this was created field_definition = generate_field_definition(model, f) # If it's a OneToOneField, and ends in _ptr, just use it if isinstance(f, models.OneToOneField) and f.name.endswith("_ptr"): mock_models.append(create_mock_model(f.rel.to, " ")) field_definition = "models.OneToOneField(%s)" % f.rel.to.__name__ # It's probably normal then elif field_definition: if isinstance(f, models.ForeignKey): mock_models.append(create_mock_model(f.rel.to, " ")) field_definition = related_field_definition(f, field_definition) # Oh noes, no defn found else: print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \ (model._meta.object_name, f.name) print f, type(f) field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name) fields.append((f.name, field_definition)) if mock_models: forwards += ''' # Mock Models %s ''' % "\n".join(mock_models) forwards += ''' # Model '%s' db.create_table(%r, ( %s ))''' % ( model._meta.object_name, table_name, "\n ".join(["('%s', %s)," % (f[0], f[1]) for f in fields]), ) backwards = ('''db.delete_table('%s') ''' % table_name) + backwards # Now go through local M2Ms and add extra stuff for them for m in model._meta.local_many_to_many: # ignore generic relations if isinstance(m, GenericRelation): continue # if the 'through' option is specified, the table will # be created through the normal model creation above. if m.rel.through: continue mock_models = [create_mock_model(model, " "), create_mock_model(m.rel.to, " ")] forwards += ''' # Mock Models %s # M2M field '%s.%s' db.create_table('%s', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('%s', models.ForeignKey(%s, null=False)), ('%s', models.ForeignKey(%s, null=False)) )) ''' % ( "\n".join(mock_models), model._meta.object_name, m.name, m.m2m_db_table(), m.m2m_column_name()[:-3], # strip off the '_id' at the end model._meta.object_name, m.m2m_reverse_name()[:-3], # strip off the '_id' at the ned m.rel.to._meta.object_name ) backwards = '''db.delete_table('%s') ''' % m.m2m_db_table() + backwards if model._meta.unique_together: ut = model._meta.unique_together if not isinstance(ut[0], (list, tuple)): ut = (ut,) for unique in ut: columns = ["'%s'" % model._meta.get_field(f).column for f in unique] forwards += ''' db.create_index('%s', [%s], unique=True, db_tablespace='%s') ''' % ( table_name, ','.join(columns), model._meta.db_tablespace ) forwards += ''' db.send_create_signal('%s', ['%s'])''' % ( app, "','".join(model._meta.object_name for model in models_to_migrate) ) # Try sniffing the 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 if (not forwards) and (not backwards): forwards = '"Write your forwards migration here"' backwards = '"Write your backwards migration here"' fp = open(os.path.join(migrations_dir, new_filename), "w") fp.write("""%s from south.db import db from django.db import models from %s.models import * class Migration: def forwards(self): %s def backwards(self): %s """ % (encoding or "", '.'.join(app_module_path), forwards, backwards)) fp.close() print "Created %s." % new_filename
def handle(self, app=None, name="", model_list=None, initial=False, **options): # If model_list is None, then it's an empty list model_list = model_list or [] # make sure --model and --all aren't both specified if initial and model_list: print "You cannot use --initial 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 # 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 # Determine what models should be included in this migration. models_to_migrate = [] if initial: models_to_migrate = models.get_models(app_models_module) if not models_to_migrate: print "No models found in app '%s'" % (app) return else: for model_name in model_list: model = models.get_model(app, model_name) if not model: print "Couldn't find model '%s' in app '%s'" % (model_name, app) return models_to_migrate.append(model) # 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", ) if not os.path.isdir(migrations_dir): print "Creating migrations directory at '%s'..." % migrations_dir os.mkdir(migrations_dir) # Touch the init py file open(os.path.join(migrations_dir, "__init__.py"), "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, ) # If there's a model, make the migration skeleton, else leave it bare forwards, backwards = '', '' if models_to_migrate: for model in models_to_migrate: table_name = model._meta.db_table mock_models = [] fields = [] for f in model._meta.local_fields: # look up the field definition to see how this was created field_definition = generate_field_definition(model, f) if field_definition: if isinstance(f, models.ForeignKey): mock_models.append(create_mock_model(f.rel.to)) field_definition = related_field_definition(f, field_definition) else: print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \ (model._meta.object_name, f.name) field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name) fields.append((f.name, field_definition)) if mock_models: forwards += ''' # Mock Models %s ''' % "\n ".join(mock_models) forwards += ''' # Model '%s' db.create_table('%s', ( %s ))''' % ( model._meta.object_name, table_name, "\n ".join(["('%s', %s)," % (f[0], f[1]) for f in fields]), ) backwards = ('''db.delete_table('%s') ''' % table_name) + backwards # Now go through local M2Ms and add extra stuff for them for m in model._meta.local_many_to_many: # ignore generic relations if isinstance(m, GenericRelation): continue # if the 'through' option is specified, the table will # be created through the normal model creation above. if m.rel.through: continue mock_models = [create_mock_model(model), create_mock_model(m.rel.to)] forwards += ''' # Mock Models %s # M2M field '%s.%s' db.create_table('%s', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), ('%s', models.ForeignKey(%s, null=False)), ('%s', models.ForeignKey(%s, null=False)) )) ''' % ( "\n ".join(mock_models), model._meta.object_name, m.name, m.m2m_db_table(), m.m2m_column_name()[:-3], # strip off the '_id' at the end model._meta.object_name, m.m2m_reverse_name()[:-3], # strip off the '_id' at the ned m.rel.to._meta.object_name ) backwards = '''db.delete_table('%s') ''' % m.m2m_db_table() + backwards if model._meta.unique_together: ut = model._meta.unique_together if not isinstance(ut[0], (list, tuple)): ut = (ut,) for unique in ut: columns = ["'%s'" % model._meta.get_field(f).column for f in unique] forwards += ''' db.create_index('%s', [%s], unique=True, db_tablespace='%s') ''' % ( table_name, ','.join(columns), model._meta.db_tablespace ) forwards += ''' db.send_create_signal('%s', ['%s'])''' % ( app, "','".join(model._meta.object_name for model in models_to_migrate) ) else: forwards = '"Write your forwards migration here"' backwards = '"Write your backwards migration here"' fp = open(os.path.join(migrations_dir, new_filename), "w") fp.write(""" from south.db import db from %s.models import * class Migration: def forwards(self): %s def backwards(self): %s """ % ('.'.join(app_module_path), forwards, backwards)) 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