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
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(".")
])
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():
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):
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)
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:
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:"
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
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."