X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/21f878e8112cf1f9b732a6dbb77e70efa68a01aa..6dc2540f663c92368314d610d161f3044fcc845c:/apps/south/migration.py diff --git a/apps/south/migration.py b/apps/south/migration.py index c650c5475..6452442d3 100644 --- a/apps/south/migration.py +++ b/apps/south/migration.py @@ -2,8 +2,11 @@ import datetime import os import sys +import traceback from django.conf import settings from django.db import models +from django.core.exceptions import ImproperlyConfigured +from django.core.management import call_command from models import MigrationHistory from south.db import db @@ -57,7 +60,7 @@ def get_migration_names(app): return sorted([ filename[:-3] for filename in os.listdir(os.path.dirname(app.__file__)) - if filename.endswith(".py") and filename != "__init__.py" + if filename.endswith(".py") and filename != "__init__.py" and not filename.startswith(".") ]) @@ -77,7 +80,9 @@ def get_migration(app, name): module = __import__(app.__name__ + "." + name, '', '', ['Migration']) return module.Migration except ImportError: - raise ValueError("Migration %s:%s does not exist." % (get_app_name(app), name)) + print " ! Migration %s:%s probably doesn't exist." % (get_app_name(app), name) + print " - Traceback:" + raise def all_migrations(): @@ -201,61 +206,116 @@ def needed_before_backwards(tree, app, name, sameapp=True): return remove_duplicates(needed) -def run_forwards(app, migrations, fake=False, silent=False): +def run_migrations(toprint, torun, recorder, app, migrations, fake=False, db_dry_run=False, silent=False): """ Runs the specified migrations forwards, in order. """ for migration in migrations: app_name = get_app_name(app) if not silent: - print " > %s: %s" % (app_name, migration) + print toprint % (app_name, migration) klass = get_migration(app, migration) + if fake: if not silent: print " (faked)" else: - db.start_transaction() + + # If the database doesn't support running DDL inside a transaction + # *cough*MySQL*cough* then do a dry run first. + if not db.has_ddl_transactions: + db.dry_run = True + db.debug, old_debug = False, db.debug + try: + getattr(klass(), torun)() + except: + traceback.print_exc() + print " ! Error found during dry run of migration! Aborting." + return False + db.debug = old_debug + db.clear_run_data() + + db.dry_run = bool(db_dry_run) + + if db.has_ddl_transactions: + db.start_transaction() try: - klass().forwards() + getattr(klass(), torun)() db.execute_deferred_sql() except: - db.rollback_transaction() - raise + if db.has_ddl_transactions: + db.rollback_transaction() + raise + else: + traceback.print_exc() + print " ! Error found during real run of migration! Aborting." + print + print " ! Since you have a database that does not support running" + print " ! schema-altering statements in transactions, we have had to" + print " ! leave it in an interim state between migrations." + if torun == "forwards": + print + print " ! You *might* be able to recover with:" + db.debug = db.dry_run = True + klass().backwards() + print + print " ! The South developers regret this has happened, and would" + print " ! like to gently persuade you to consider a slightly" + print " ! easier-to-deal-with DBMS." + return False else: - db.commit_transaction() + if db.has_ddl_transactions: + db.commit_transaction() + + if not db_dry_run: + # Record us as having done this + recorder(app_name, migration) + + +def run_forwards(app, migrations, fake=False, db_dry_run=False, silent=False): + """ + Runs the specified migrations forwards, in order. + """ + + def record(app_name, migration): # Record us as having done this record = MigrationHistory.for_migration(app_name, migration) record.applied = datetime.datetime.utcnow() record.save() - - -def run_backwards(app, migrations, ignore=[], fake=False, silent=False): + + return run_migrations( + toprint = " > %s: %s", + torun = "forwards", + recorder = record, + app = app, + migrations = migrations, + fake = fake, + db_dry_run = db_dry_run, + silent = silent, + ) + + +def run_backwards(app, migrations, ignore=[], fake=False, db_dry_run=False, silent=False): """ Runs the specified migrations backwards, in order, skipping those migrations in 'ignore'. """ - for migration in migrations: - if migration not in ignore: - app_name = get_app_name(app) - if not silent: - print " < %s: %s" % (app_name, migration) - klass = get_migration(app, migration) - if fake: - if not silent: - print " (faked)" - else: - db.start_transaction() - try: - klass().backwards() - db.execute_deferred_sql() - except: - db.rollback_transaction() - raise - else: - db.commit_transaction() - # Record us as having not done this - record = MigrationHistory.for_migration(app_name, migration) - record.delete() + + def record(app_name, migration): + # Record us as having not done this + record = MigrationHistory.for_migration(app_name, migration) + record.delete() + + return run_migrations( + toprint = " < %s: %s", + torun = "backwards", + recorder = record, + app = app, + migrations = [x for x in migrations if x not in ignore], + fake = fake, + db_dry_run = db_dry_run, + silent = silent, + ) def right_side_of(x, y): @@ -291,7 +351,7 @@ def backwards_problems(tree, backwards, done, silent=False): return problems -def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, silent=False): +def migrate_app(app, target_name=None, resolve_mode=None, fake=False, db_dry_run=False, yes=False, silent=False, load_inital_data=False): app_name = get_app_name(app) @@ -310,6 +370,12 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, tree = dependency_tree() migrations = get_migration_names(app) + # If there aren't any, quit quizically + if not migrations: + if not silent: + print "? You have no migrations for the '%s' app. You might want some." % app_name + return + if target_name not in migrations and target_name not in ["zero", None]: matches = [x for x in migrations if x.startswith(target_name)] if len(matches) == 1: @@ -331,7 +397,15 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, return # Check there's no strange ones in the database - ghost_migrations = [m for m in MigrationHistory.objects.filter(applied__isnull = False) if get_app(m.app_name) not in tree or m.migration not in tree[get_app(m.app_name)]] + ghost_migrations = [] + for m in MigrationHistory.objects.filter(applied__isnull = False): + try: + if get_app(m.app_name) not in tree or m.migration not in tree[get_app(m.app_name)]: + ghost_migrations.append(m) + except ImproperlyConfigured: + pass + + if ghost_migrations: if not silent: print " ! These migrations are in the database but not on disk:" @@ -361,7 +435,12 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, backwards = [] # Get the list of currently applied migrations from the db - current_migrations = [(get_app(m.app_name), m.migration) for m in MigrationHistory.objects.filter(applied__isnull = False)] + current_migrations = [] + for m in MigrationHistory.objects.filter(applied__isnull = False): + try: + current_migrations.append((get_app(m.app_name), m.migration)) + except ImproperlyConfigured: + pass direction = None bad = False @@ -416,15 +495,34 @@ def migrate_app(app, target_name=None, resolve_mode=None, fake=False, yes=False, if direction == 1: if not silent: print " - Migrating forwards to %s." % target_name - for mapp, mname in forwards: - if (mapp, mname) not in current_migrations: - run_forwards(mapp, [mname], fake=fake, silent=silent) + try: + for mapp, mname in forwards: + if (mapp, mname) not in current_migrations: + result = run_forwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent) + if result is False: # The migrations errored, but nicely. + return + finally: + # Call any pending post_syncdb signals + db.send_pending_create_signals() + # Now load initial data, only if we're really doing things and ended up at current + if not fake and not db_dry_run and load_inital_data and target_name == migrations[-1]: + print " - Loading initial data for %s." % app_name + # Override Django's get_apps call temporarily to only load from the + # current app + old_get_apps, models.get_apps = ( + models.get_apps, + lambda: [models.get_app(get_app_name(app))], + ) + # Load the initial fixture + call_command('loaddata', 'initial_data', verbosity=1) + # Un-override + models.get_apps = old_get_apps elif direction == -1: if not silent: print " - Migrating backwards to just after %s." % target_name for mapp, mname in backwards: if (mapp, mname) in current_migrations: - run_backwards(mapp, [mname], fake=fake, silent=silent) + run_backwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent) else: if not silent: - print "- Nothing to migrate." \ No newline at end of file + print "- Nothing to migrate."