def test_apply_migrations(self): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Apply them normally migrate_app(migrations, target_name=None, fake=False, load_initial_data=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"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="zero", fake=False) # Finish with none self.assertEqual(list(MigrationHistory.objects.all()), [])
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): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Insert one in the wrong order MigrationHistory.objects.create(app_name="fakeapp", migration="0002_eggs", applied=datetime.datetime.now()) # Did it go in? self.assertListEqual( ((u"fakeapp", u"0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name=None, fake=False) self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name='zero', fake=False) try: migrate_app(migrations, target_name=None, fake=False) except exceptions.InconsistentMigrationHistory, e: self.assertEqual( [(migrations['0002_eggs'], [migrations['0001_spam']])], e.problems)
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_migration_merge_forwards(self): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Insert one in the wrong order MigrationHistory.objects.create(app_name="fakeapp", migration="0002_eggs", applied=datetime.datetime.now()) # Did it go in? self.assertListEqual( ((u"fakeapp", u"0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration") ) # Apply them normally self.assertRaises( exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name=None, fake=False ) self.assertRaises( exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name="zero", fake=False ) try: migrate_app(migrations, target_name=None, fake=False) except exceptions.InconsistentMigrationHistory, e: self.assertEqual([(migrations["0002_eggs"], [migrations["0001_spam"]])], e.problems)
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_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(self, *args, **options): verbosity = int(options.get('verbosity', '1')) assert django.VERSION < (1, 7) from south import migration from south.models import MigrationHistory try: MigrationHistory.objects.count() except OperationalError: return apps = list(migration.all_migrations()) applied_migrations = MigrationHistory.objects.filter(app_name__in=[app.app_label() for app in apps]) applied_migrations = ['%s.%s' % (mi.app_name, mi.migration) for mi in applied_migrations] for app in apps: for app_migration in app: migration_name = '%s.%s' % (app_migration.app_label(), app_migration.name()) print migration_name, bool(migration_name in applied_migrations) if migration_name not in applied_migrations: result = migration.migrate_app( app, app_migration.name(), verbosity = verbosity, db_dry_run = True, ) if result is False: sys.exit('Migration %s failed.' % migration_name)
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() # SQLite has weird now() if db.backend_name == "sqlite3": now_func = "DATETIME('NOW')" else: now_func = "NOW()" try: cursor.execute("INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, 10.1, %s, NULL);" % now_func) except: transaction.rollback() return False else: cursor.execute("DELETE FROM southtest_spam") transaction.commit() return True MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # by default name is NOT NULL migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok()) self.assertListEqual( ((u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # after 0003, it should be NULL migrate_app(migrations, target_name="0003", fake=False) self.assert_(null_ok()) self.assertListEqual( ((u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # make sure it is NOT NULL again migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok(), 'name not null after migration') self.assertListEqual( ((u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # finish with no migrations, otherwise other tests fail... migrate_app(migrations, target_name="zero", fake=False) self.assertEqual(list(MigrationHistory.objects.all()), [])
def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, show_list=False, show_changes=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, **options): # 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_module('.management', app_name) except ImportError as 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: try: apps = [Migrations(app)] except NoMigrations: print("The app '%s' does not appear to use migrations." % app) print("./manage.py migrate " + self.args) return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database, **options) if show_changes and apps: show_migration_changes(apps) if not (show_list or show_changes): for app in apps: result = migration.migrate_app( app, target_name=target, fake=fake, db_dry_run=db_dry_run, verbosity=int(options.get('verbosity', 0)), interactive=options.get('interactive', True), load_initial_data=not options.get('no_initial_data', False), merge=merge, skip=skip, database=database, delete_ghosts=delete_ghosts, ignore_ghosts=ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails.
def test_apply_migrations(self): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Apply them normally migrate_app(migrations, target_name=None, fake=False, load_initial_data=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")), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="zero", fake=False) # Finish with none self.assertEqual(list(MigrationHistory.objects.all()), [])
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 begin(self): """ Initialize the test environment then create the test database and switch the connection over to that database. """ import django from django.conf import settings from django.db import connection from django.core import management from django.test.utils import setup_test_environment use_south = 'south' in settings.INSTALLED_APPS if use_south: from south import migration from south.hacks import hacks try: self.original_db_name = settings.DATABASE_NAME except AttributeError: # Django > 1.2 self.original_db_name = settings.DATABASES['default']['NAME'] try: django.setup() # Django >= 1.7 except AttributeError: pass setup_test_environment() if use_south: management.get_commands() hacks.patch_flush_during_test_db_creation() connection.creation.create_test_db(self.verbosity) if use_south: for app in migration.all_migrations(): migration.migrate_app(app, verbosity=self.verbosity)
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='show_list', default=False, help='List migrations noting those that have been applied'), make_option('--changes', action='store_true', dest='show_changes', default=False, help='List changes for migrations'), 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." ), make_option( '--delete-ghost-migrations', action='store_true', dest='delete_ghosts', default=False, help= "Tells South to delete any 'ghost' migrations (ones in the database but not on disk)." ), make_option( '--ignore-ghost-migrations', action='store_true', dest='ignore_ghosts', default=False, help= "Tells South to ignore any 'ghost' migrations (ones in the database but not on disk) and continue to apply new migrations." ), make_option( '--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. ' 'Defaults to the "default" database.'), make_option( '--hack', # KA-LITE-MOD: needed for relaunch_external hack action="store_true", dest="hack", default=False, help="Flag that we're getting an external callback ", ), ) 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] [--database=dbalias]" def relaunch_external(self): """ This is a KA Lite hack. #KA-LITE-MOD In our git-based update command (until version 0.10.3), we update the code, then run run the 'migrate' command under the old code (since it's loaded into memory). This can cause conflicting / now-outdated imports. The right thing to do is to run migrate via subprocess, so that it runs all fresh, new code. Alas, we've shipped versions that don't do this already, so we can't go back and fix. The hack here is: detect when this is happening, then force migrate to re-launch itself via subprocess. Note that this only works if this file (migrate.py) hasn't already been loaded into the process. It shouldn't be... but it's worth keeping in mind! """ import sys import settings import utils.django_utils.command reload( utils.django_utils.command ) # this is necessary because the 0.10.2 version of this code has a bug! # Launch through subprocess, then print output. sys.stdout.write( "[NOTE version upgrade hack: running 'migrate' through subprocess.]\n\n" ) sys.stdout.write( "Please wait. DO NOT CANCEL WHILE MIGRATE RUNS, EVEN THOUGH YOU'RE RECEIVING NO OUTPUT!! JUST WAIT!!\n" ) sys.stdout.write( "Output will print below when migrate is complete.\n\n") (out, err, rc) = utils.django_utils.command.call_outside_command_with_output( "migrate", merge=True, hack=True, manage_py_dir=settings.PROJECT_PATH) sys.stdout.write(out) if rc: sys.stderr.write(err) sys.exit(0) # abort the rest of the migrate / update command def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, show_list=False, show_changes=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, **options): if "update" in sys.argv and "--hack" not in sys.argv: self.relaunch_external() # 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_module('.management', app_name) 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: try: apps = [Migrations(app)] except NoMigrations: print "The app '%s' does not appear to use migrations." % app print "./manage.py migrate " + self.args return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database, **options) if show_changes and apps: show_migration_changes(apps) if not (show_list or show_changes): for app in apps: result = migration.migrate_app( app, target_name=target, fake=fake, db_dry_run=db_dry_run, verbosity=int(options.get('verbosity', 0)), interactive=options.get('interactive', True), load_initial_data=not options.get('no_initial_data', False), merge=merge, skip=skip, database=database, delete_ghosts=delete_ghosts, ignore_ghosts=ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails.
def test_migration_merge_forwards(self): migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Insert one in the wrong order MigrationHistory.objects.create(app_name = "fakeapp", migration = "0002_eggs", applied = datetime.datetime.now()) # Did it go in? self.assertListEqual( (("fakeapp", "0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name=None, fake=False) self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name='zero', fake=False) try: migrate_app(migrations, target_name=None, fake=False) except exceptions.InconsistentMigrationHistory as e: self.assertEqual( [ ( migrations['0002_eggs'], migrations['0001_spam'], ) ], e.problems, ) try: migrate_app(migrations, target_name="zero", fake=False) except exceptions.InconsistentMigrationHistory as e: self.assertEqual( [ ( migrations['0002_eggs'], migrations['0001_spam'], ) ], e.problems, ) # Nothing should have changed (no merge mode!) self.assertListEqual( (("fakeapp", "0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migrate_app(migrations, target_name=None, merge=True, fake=False) # We should finish with all migrations self.assertListEqual( (("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ("fakeapp", "0003_alter_spam"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="0002", fake=False) migrate_app(migrations, target_name="0001", fake=True) migrate_app(migrations, target_name="zero", fake=False) # Finish with none self.assertEqual(list(MigrationHistory.objects.all()), [])
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 handle(self, app=None, name="", added_model_list=None, added_field_list=None, freeze_list=None, initial=False, auto=False, stdout=False, added_index_list=None, verbosity=1, empty=False, update=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 [] added_index_list = added_index_list or [] freeze_list = freeze_list or [] # --stdout means name = - if stdout: name = "-" # Only allow valid names if re.search('[^_\w]', name) and name != "-": self.error( "Migration names should contain only alphanumeric characters and underscores.") # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): self.error( "You cannot use --initial and other options together\n" + self.usage_str) if auto and (added_model_list or added_field_list or initial): self.error( "You cannot use --auto and other options together\n" + self.usage_str) if not app: self.error( "You must provide an app to create a migration for.\n" + self.usage_str) # 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 # Get the Migrations for this app (creating the migrations dir if needed) migrations = Migrations(app, force_creation=True, verbose_creation=int(verbosity) > 0) # What actions do we need to do? if auto: # Get the old migration try: last_migration = migrations[-2 if update else -1] except IndexError: self.error( "You cannot use --auto on an app with no migrations. Try --initial.") # Make sure it has stored models if migrations.app_label() not in getattr( last_migration.migration_class(), "complete_apps", []): self.error( "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." % migrations.app_label()) # Alright, construct two model dicts to run the differ on. old_defs = dict( (k, v) for k, v in last_migration.migration_class().models.items() if k.split(".")[0] == migrations.app_label() ) new_defs = dict( (k, v) for k, v in freezer.freeze_apps([migrations.app_label()]).items() if k.split(".")[0] == migrations.app_label() ) change_source = changes.AutoChanges( migrations=migrations, old_defs=old_defs, old_orm=last_migration.orm(), new_defs=new_defs, ) elif initial: # Do an initial migration change_source = changes.InitialChanges(migrations) else: # Read the commands manually off of the arguments if (added_model_list or added_field_list or added_index_list): change_source = changes.ManualChanges( migrations, added_model_list, added_field_list, added_index_list, ) elif empty: change_source = None else: print( "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index.", file=sys.stderr) sys.exit(1) # Validate this so we can access the last migration without worrying if update and not migrations: self.error("You cannot use --update on an app with no migrations.") # if not name, there's an error if not name: if change_source: name = change_source.suggest_name() if update: name = re.sub(r'^\d{4}_', '', migrations[-1].name()) if not name: self.error( "You must provide a name for this migration\n" + self.usage_str) # Get the actions, and then insert them into the actions lists forwards_actions = [] backwards_actions = [] if change_source: for action_name, params in change_source.get_changes(): # Run the correct Action class try: action_class = getattr(actions, action_name) except AttributeError: raise ValueError( "Invalid action name from source: %s" % action_name) else: action = action_class(**params) action.add_forwards(forwards_actions) action.add_backwards(backwards_actions) print(action.console_line(), file=sys.stderr) # Nowt happen? That's not good for --auto. if auto and not forwards_actions: self.error("Nothing seems to have changed.") # Work out which apps to freeze apps_to_freeze = self.calc_frozen_apps(migrations, freeze_list) # So, what's in this file, then? file_contents = MIGRATION_TEMPLATE % { "forwards": "\n".join(forwards_actions or [" pass"]), "backwards": "\n".join(backwards_actions or [" pass"]), "frozen_models": freezer.freeze_apps_to_string(apps_to_freeze), "complete_apps": apps_to_freeze and "complete_apps = [%s]" % ( ", ".join(map(repr, apps_to_freeze))) or "" } # Custom Bluebottle # We find and replace the base apps with our mapped models for model in MODEL_MAP: model_map = MODEL_MAP[model] mapping = { 'u"orm[\'{0}\']"'.format(model_map[ 'model']): '"orm[\'{0}\']".format(MODEL_MAP[\'{1}\'][\'model\'])'.format( '{0}', model), 'u\'{0}\''.format( model_map['table']): 'MODEL_MAP[\'{0}\'][\'table\']'.format( model), 'u\'{0}\''.format(model_map[ 'model_lower']): 'MODEL_MAP[\'{0}\'][\'model_lower\']'.format( model), 'u\'{0}\''.format( model_map['app']): 'MODEL_MAP[\'{0}\'][\'app\']'.format( model), '[\'{0}\']'.format( model_map['app']): '[MODEL_MAP[\'{0}\'][\'app\']]'.format( model), 'to=orm[\'{0}\']'.format(model_map[ 'model']): 'to=orm[MODEL_MAP[\'{0}\'][\'model\']]'.format( model), '\'object_name\': \'{0}\''.format(model_map[ 'class']): '\'object_name\': MODEL_MAP[\'{0}\'][\'class\']'.format( model) } file_contents = reduce(lambda x, y: x.replace(y, mapping[y]), mapping, file_contents) # End Custom Bluebottle # Deal with update mode as late as possible, avoid a rollback as long # as something else can go wrong. if update: last_migration = migrations[-1] if MigrationHistory.objects.filter(applied__isnull=False, app_name=app, migration=last_migration.name()): print( "Migration to be updated, %s, is already applied, rolling it back now..." % last_migration.name(), file=sys.stderr) migrate_app(migrations, 'current-1', verbosity=verbosity) for ext in ('py', 'pyc'): old_filename = "%s.%s" % ( os.path.join(migrations.migrations_dir(), last_migration.filename), ext) if os.path.isfile(old_filename): os.unlink(old_filename) migrations.remove(last_migration) # See what filename is next in line. We assume they use numbers. new_filename = migrations.next_filename(name) # - 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.migrations_dir(), new_filename), "w") fp.write(file_contents) fp.close() verb = 'Updated' if update else 'Created' if empty: print( "%s %s. You must now edit this migration and add the code for each direction." % ( verb, new_filename), file=sys.stderr) else: print( "%s %s. You can now apply this migration with: ./manage.py migrate %s" % ( verb, new_filename, app), file=sys.stderr)
print "./manage.py migrate " + self.args return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database, **options) if not show_list: for app in apps: result = migration.migrate_app( app, target_name = target, fake = fake, db_dry_run = db_dry_run, verbosity = int(options.get('verbosity', 0)), interactive = options.get('interactive', True), load_initial_data = not options.get('no_initial_data', False), merge = merge, skip = skip, database = database, delete_ghosts = delete_ghosts, ignore_ghosts = ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails. Command.handle = new_handle
def test_migration_merge_forwards(self): migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Insert one in the wrong order MigrationHistory.objects.create(app_name="fakeapp", migration="0002_eggs", applied=datetime.datetime.now()) # Did it go in? self.assertListEqual( (("fakeapp", "0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name=None, fake=False) self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name='zero', fake=False) try: migrate_app(migrations, target_name=None, fake=False) except exceptions.InconsistentMigrationHistory as e: self.assertEqual( [( migrations['0002_eggs'], migrations['0001_spam'], )], e.problems, ) try: migrate_app(migrations, target_name="zero", fake=False) except exceptions.InconsistentMigrationHistory as e: self.assertEqual( [( migrations['0002_eggs'], migrations['0001_spam'], )], e.problems, ) # Nothing should have changed (no merge mode!) self.assertListEqual( (("fakeapp", "0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migrate_app(migrations, target_name=None, merge=True, fake=False) # We should finish with all migrations self.assertListEqual( ( ("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ("fakeapp", "0003_alter_spam"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="0002", fake=False) migrate_app(migrations, target_name="0001", fake=True) migrate_app(migrations, target_name="zero", fake=False) # Finish with none self.assertEqual(list(MigrationHistory.objects.all()), [])
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='show_list', default=False, help='List migrations noting those that have been applied'), make_option('--changes', action='store_true', dest='show_changes', default=False, help='List changes for migrations'), 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." ), make_option( '--delete-ghost-migrations', action='store_true', dest='delete_ghosts', default=False, help= "Tells South to delete any 'ghost' migrations (ones in the database but not on disk)." ), make_option( '--ignore-ghost-migrations', action='store_true', dest='ignore_ghosts', default=False, help= "Tells South to ignore any 'ghost' migrations (ones in the database but not on disk) and continue to apply new migrations." ), make_option( '--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), make_option('--database', action='store', dest='database', default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. ' 'Defaults to the "default" database.'), ) 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] [--database=dbalias]" def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, show_list=False, show_changes=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, **options): # 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: try: apps = [Migrations(app)] except NoMigrations: print "The app '%s' does not appear to use migrations." % app print "./manage.py migrate " + self.args return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database) if show_changes and apps: show_migration_changes(apps) if not (show_list or show_changes): for app in apps: result = migration.migrate_app( app, target_name=target, fake=fake, db_dry_run=db_dry_run, verbosity=int(options.get('verbosity', 0)), interactive=options.get('interactive', True), load_initial_data=not options.get('no_initial_data', False), merge=merge, skip=skip, database=database, delete_ghosts=delete_ghosts, ignore_ghosts=ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails.
def test_alter_column_null(self): def null_ok(eat_exception=True): from django.db import connection, transaction # the DBAPI introspection module fails on postgres NULLs. cursor = connection.cursor() # SQLite has weird now() if db.backend_name == "sqlite3": now_func = "DATETIME('NOW')" # So does SQLServer... should we be using a backend attribute? elif db.backend_name == "pyodbc": now_func = "GETDATE()" elif db.backend_name == "oracle": now_func = "SYSDATE" else: now_func = "NOW()" try: if db.backend_name == "pyodbc": cursor.execute("SET IDENTITY_INSERT southtest_spam ON;") cursor.execute( "INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, NULL, %s, 'whatever');" % now_func) except: if eat_exception: transaction.rollback() return False else: raise else: cursor.execute("DELETE FROM southtest_spam") transaction.commit() return True MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # by default name is NOT NULL migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok()) self.assertListEqual( ( ("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # after 0003, it should be NULL migrate_app(migrations, target_name="0003", fake=False) self.assert_(null_ok(False)) self.assertListEqual( ( ("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ("fakeapp", "0003_alter_spam"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # make sure it is NOT NULL again migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok(), 'weight not null after migration') self.assertListEqual( ( ("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # finish with no migrations, otherwise other tests fail... migrate_app(migrations, target_name="zero", fake=False) self.assertEqual(list(MigrationHistory.objects.all()), [])
def handle(self, app=None, target=None, skip=False, merge=False, backwards=False, fake=False, db_dry_run=False, show_list=False, show_changes=False, database=DEFAULT_DB_ALIAS, delete_ghosts=False, ignore_ghosts=False, **options): # 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_module('.management', app_name) except ImportError as 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: try: apps = [Migrations(app)] except NoMigrations: print("The app '%s' does not appear to use migrations." % app) print("./manage.py migrate " + self.args) return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database, **options) if show_changes and apps: show_migration_changes(apps) if not (show_list or show_changes): for app in apps: result = migration.migrate_app( app, target_name=target, fake=fake, db_dry_run=db_dry_run, verbosity=int(options.get('verbosity', 0)), interactive=options.get('interactive', True), load_initial_data=not options.get('no_initial_data', False), merge=merge, skip=skip, database=database, delete_ghosts=delete_ghosts, ignore_ghosts=ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails.
def handle(self, *args, **options): _auto = options.get('auto', False) # Loop through all give apps # Or fail as no apps are supplied. if len(args) <= 0: self.say("No apps supplied") exit(1) for a in args: try: module, app = self.app_label_to_app_module(a) #print "Objects", module, app except ImproperlyConfigured as (e): module, app = None, None self.say("App '%s' could not be found." % a) if app: me = self.migrations_exist(module) if me: self.say("Auto migrations for %s" % a) _auto = True else: self.say("New migrations for %s" % a) _auto = False if _auto == True: _initial = False else: _initial = True from django.core import management options.update({'initial': _initial, 'auto': _auto}) p = str(a) try: management.call_command('schemamigration', p, **options) finally: from south.migration import Migration, Migrations # Migrate each app if a: try: apps = [Migrations(a)] _s = 's' if len(apps) == 1: _s = '' print "Migrating %s app%s in '%s' " % (len(apps), _s, a) for app in apps: result = migration.migrate_app( apps, target_name=None, fake=options.get('fake', False), db_dry_run=options.get( 'db_dry_run', False), verbosity=int(options.get('verbosity', 0)), interactive=options.get( 'interactive', True), load_initial_data=not options.get( 'no_initial_data', False), merge=options.get('merge', True), skip=False, database=options.get( 'database', DEFAULT_DB_ALIAS), delete_ghosts=options.get( 'delete_ghosts', False), ignore_ghosts=options.get( 'ignore_ghosts', False), ) if result is False: sys.exit( 1 ) # Migration failed, so the command fails. except NoMigrations: print "The app '%s' does not appear to use migrations." % app print "./manage.py migrate " + self.args return else: apps = list(migration.all_migrations())
return else: apps = list(migration.all_migrations()) # Do we need to show the list of migrations? if show_list and apps: list_migrations(apps, database, **options) if not show_list: for app in apps: result = migration.migrate_app( app, target_name=target, fake=fake, db_dry_run=db_dry_run, verbosity=int(options.get('verbosity', 0)), interactive=options.get('interactive', True), load_initial_data=not options.get('no_initial_data', False), merge=merge, skip=skip, database=database, delete_ghosts=delete_ghosts, ignore_ghosts=ignore_ghosts, ) if result is False: sys.exit(1) # Migration failed, so the command fails. Command.handle = new_handle
self.assertEqual( [( migrations['0002_eggs'], migrations['0001_spam'], )], e.problems, ) # Nothing should have changed (no merge mode!) self.assertListEqual( ((u"fakeapp", u"0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migrate_app(migrations, target_name=None, merge=True, fake=False) # We should finish with all migrations self.assertListEqual( ( (u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="0002", fake=False) migrate_app(migrations, target_name="0001", fake=True) migrate_app(migrations, target_name="zero", fake=False)
def handle(self, *args, **options): _auto = options.get('auto', False) # Loop through all give apps # Or fail as no apps are supplied. if len(args) <= 0: self.say("No apps supplied") exit(1) for a in args: try: module, app = self.app_label_to_app_module(a) #print "Objects", module, app except ImproperlyConfigured as (e): module, app = None, None self.say("App '%s' could not be found." % a) if app: me = self.migrations_exist(module) if me: self.say("Auto migrations for %s" % a) _auto = True else: self.say("New migrations for %s" % a) _auto = False if _auto == True: _initial = False else: _initial = True from django.core import management options.update({'initial':_initial, 'auto':_auto}) p = str(a) try: management.call_command('schemamigration', p, **options) finally: from south.migration import Migration, Migrations # Migrate each app if a: try: apps = [Migrations(a)] _s = 's' if len(apps) == 1: _s = '' print "Migrating %s app%s in '%s' " % (len(apps), _s, a) for app in apps: result = migration.migrate_app( apps, target_name = None, fake = options.get('fake', False), db_dry_run = options.get('db_dry_run', False), verbosity = int(options.get('verbosity', 0)), interactive = options.get('interactive', True), load_initial_data = not options.get('no_initial_data', False), merge = options.get('merge', True), skip = False, database = options.get('database', DEFAULT_DB_ALIAS), delete_ghosts = options.get('delete_ghosts', False), ignore_ghosts = options.get('ignore_ghosts', False), ) if result is False: sys.exit(1) # Migration failed, so the command fails. except NoMigrations: print "The app '%s' does not appear to use migrations." % app print "./manage.py migrate " + self.args return else: apps = list(migration.all_migrations())
class TestMigrationLogic(Monkeypatcher): """ Tests if the various logic functions in migration actually work. """ installed_apps = ["fakeapp", "otherfakeapp"] def assertListEqual(self, list1, list2): list1 = list(list1) list2 = list(list2) list1.sort() list2.sort() return self.assertEqual(list1, list2) def test_find_ghost_migrations(self): pass def test_apply_migrations(self): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Apply them normally migrate_app(migrations, target_name=None, fake=False, load_initial_data=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"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="zero", fake=False) # Finish with none self.assertEqual(list(MigrationHistory.objects.all()), []) def test_migration_merge_forwards(self): MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # We should start with no migrations self.assertEqual(list(MigrationHistory.objects.all()), []) # Insert one in the wrong order MigrationHistory.objects.create(app_name="fakeapp", migration="0002_eggs", applied=datetime.datetime.now()) # Did it go in? self.assertListEqual( ((u"fakeapp", u"0002_eggs"), ), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply them normally self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name=None, fake=False) self.assertRaises(exceptions.InconsistentMigrationHistory, migrate_app, migrations, target_name='zero', fake=False) try: migrate_app(migrations, target_name=None, fake=False) except exceptions.InconsistentMigrationHistory, e: self.assertEqual( [( migrations['0002_eggs'], migrations['0001_spam'], )], e.problems, ) try: migrate_app(migrations, target_name="zero", fake=False) except exceptions.InconsistentMigrationHistory, e: self.assertEqual( [( migrations['0002_eggs'], migrations['0001_spam'], )], e.problems, )
def test_alter_column_null(self): def null_ok(eat_exception=True): from django.db import connection, transaction # the DBAPI introspection module fails on postgres NULLs. cursor = connection.cursor() # SQLite has weird now() if db.backend_name == "sqlite3": now_func = "DATETIME('NOW')" # So does SQLServer... should we be using a backend attribute? elif db.backend_name == "pyodbc": now_func = "GETDATE()" elif db.backend_name == "oracle": now_func = "SYSDATE" else: now_func = "NOW()" try: if db.backend_name == "pyodbc": cursor.execute("SET IDENTITY_INSERT southtest_spam ON;") cursor.execute("INSERT INTO southtest_spam (id, weight, expires, name) VALUES (100, NULL, %s, 'whatever');" % now_func) except: if eat_exception: transaction.rollback() return False else: raise else: cursor.execute("DELETE FROM southtest_spam") transaction.commit() return True MigrationHistory.objects.all().delete() migrations = Migrations("fakeapp") # by default name is NOT NULL migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok()) self.assertListEqual( (("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # after 0003, it should be NULL migrate_app(migrations, target_name="0003", fake=False) self.assert_(null_ok(False)) self.assertListEqual( (("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"), ("fakeapp", "0003_alter_spam"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # make sure it is NOT NULL again migrate_app(migrations, target_name="0002", fake=False) self.failIf(null_ok(), 'weight not null after migration') self.assertListEqual( (("fakeapp", "0001_spam"), ("fakeapp", "0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # finish with no migrations, otherwise other tests fail... migrate_app(migrations, target_name="zero", fake=False) self.assertEqual(list(MigrationHistory.objects.all()), [])
( migrations['0002_eggs'], migrations['0001_spam'], ) ], e.problems, ) # Nothing should have changed (no merge mode!) self.assertListEqual( ((u"fakeapp", u"0002_eggs"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # Apply with merge migrate_app(migrations, target_name=None, merge=True, fake=False) # We should finish with all migrations self.assertListEqual( ((u"fakeapp", u"0001_spam"), (u"fakeapp", u"0002_eggs"), (u"fakeapp", u"0003_alter_spam"),), MigrationHistory.objects.values_list("app_name", "migration"), ) # Now roll them backwards migrate_app(migrations, target_name="0002", fake=False) migrate_app(migrations, target_name="0001", fake=True) migrate_app(migrations, target_name="zero", fake=False) # Finish with none
def handle(self, app=None, name="", added_model_list=None, added_field_list=None, freeze_list=None, initial=False, auto=False, stdout=False, added_index_list=None, verbosity=1, empty=False, update=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 [] added_index_list = added_index_list or [] freeze_list = freeze_list or [] # --stdout means name = - if stdout: name = "-" # Only allow valid names if re.search('[^_\w]', name) and name != "-": self.error( "Migration names should contain only alphanumeric characters and underscores." ) # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): self.error( "You cannot use --initial and other options together\n" + self.usage_str) if auto and (added_model_list or added_field_list or initial): self.error("You cannot use --auto and other options together\n" + self.usage_str) if not app: self.error("You must provide an app to create a migration for.\n" + self.usage_str) # 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 # Get the Migrations for this app (creating the migrations dir if needed) migrations = Migrations(app, force_creation=True, verbose_creation=int(verbosity) > 0) # What actions do we need to do? if auto: # Get the old migration try: last_migration = migrations[-2 if update else -1] except IndexError: self.error( "You cannot use --auto on an app with no migrations. Try --initial." ) # Make sure it has stored models if migrations.app_label() not in getattr( last_migration.migration_class(), "complete_apps", []): self.error( "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." % migrations.app_label()) # Alright, construct two model dicts to run the differ on. old_defs = dict( (k, v) for k, v in last_migration.migration_class().models.items() if k.split(".")[0] == migrations.app_label()) new_defs = dict((k, v) for k, v in freezer.freeze_apps( [migrations.app_label()]).items() if k.split(".")[0] == migrations.app_label()) change_source = changes.AutoChanges( migrations=migrations, old_defs=old_defs, old_orm=last_migration.orm(), new_defs=new_defs, ) elif initial: # Do an initial migration change_source = changes.InitialChanges(migrations) else: # Read the commands manually off of the arguments if (added_model_list or added_field_list or added_index_list): change_source = changes.ManualChanges( migrations, added_model_list, added_field_list, added_index_list, ) elif empty: change_source = None else: print( "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index.", file=sys.stderr) sys.exit(1) # Validate this so we can access the last migration without worrying if update and not migrations: self.error("You cannot use --update on an app with no migrations.") # if not name, there's an error if not name: if change_source: name = change_source.suggest_name() if update: name = re.sub(r'^\d{4}_', '', migrations[-1].name()) if not name: self.error("You must provide a name for this migration\n" + self.usage_str) # Get the actions, and then insert them into the actions lists forwards_actions = [] backwards_actions = [] if change_source: for action_name, params in change_source.get_changes(): # Run the correct Action class try: action_class = getattr(actions, action_name) except AttributeError: raise ValueError("Invalid action name from source: %s" % action_name) else: action = action_class(**params) action.add_forwards(forwards_actions) action.add_backwards(backwards_actions) print(action.console_line(), file=sys.stderr) # Nowt happen? That's not good for --auto. if auto and not forwards_actions: self.error("Nothing seems to have changed.") # Work out which apps to freeze apps_to_freeze = self.calc_frozen_apps(migrations, freeze_list) # So, what's in this file, then? file_contents = MIGRATION_TEMPLATE % { "forwards": "\n".join(forwards_actions or [" pass"]), "backwards": "\n".join(backwards_actions or [" pass"]), "frozen_models": freezer.freeze_apps_to_string(apps_to_freeze), "complete_apps": apps_to_freeze and "complete_apps = [%s]" % (", ".join(map(repr, apps_to_freeze))) or "" } # Deal with update mode as late as possible, avoid a rollback as long # as something else can go wrong. if update: last_migration = migrations[-1] if MigrationHistory.objects.filter( applied__isnull=False, app_name=app, migration=last_migration.name()): print( "Migration to be updated, %s, is already applied, rolling it back now..." % last_migration.name(), file=sys.stderr) migrate_app(migrations, 'current-1', verbosity=verbosity) for ext in ('py', 'pyc'): old_filename = "%s.%s" % (os.path.join( migrations.migrations_dir(), last_migration.filename), ext) if os.path.isfile(old_filename): os.unlink(old_filename) migrations.remove(last_migration) # See what filename is next in line. We assume they use numbers. new_filename = migrations.next_filename(name) # - 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.migrations_dir(), new_filename), "w") fp.write(file_contents) fp.close() verb = 'Updated' if update else 'Created' if empty: print( "%s %s. You must now edit this migration and add the code for each direction." % (verb, new_filename), file=sys.stderr) else: print( "%s %s. You can now apply this migration with: ./manage.py migrate %s" % (verb, new_filename, app), file=sys.stderr)