6 from django.conf import settings
7 from django.db import models
8 from django.core.exceptions import ImproperlyConfigured
9 from django.core.management import call_command
10 from models import MigrationHistory
11 from south.db import db
16 Returns the migrations module for the given app model name/module, or None
17 if it does not use migrations.
19 if isinstance(app, (str, unicode)):
20 # If it's a string, use the models module
21 app = models.get_app(app)
22 mod = __import__(app.__name__[:-7], {}, {}, ['migrations'])
23 if hasattr(mod, 'migrations'):
24 return getattr(mod, 'migrations')
27 def get_migrated_apps():
29 Returns all apps with migrations.
31 for mapp in models.get_apps():
37 def get_app_name(app):
39 Returns the _internal_ app name for the given app module.
40 i.e. for <module django.contrib.auth.models> will return 'auth'
42 return app.__name__.split('.')[-2]
45 def get_app_fullname(app):
47 Returns the full python name of an app - e.g. django.contrib.auth
49 return app.__name__[:-11]
52 def short_from_long(app_name):
53 return app_name.split(".")[-1]
56 def get_migration_names(app):
58 Returns a list of migration file names for the given app.
62 for filename in os.listdir(os.path.dirname(app.__file__))
63 if filename.endswith(".py") and filename != "__init__.py" and not filename.startswith(".")
67 def get_migration_classes(app):
69 Returns a list of migration classes (one for each migration) for the app.
71 for name in get_migration_names(app):
72 yield get_migration(app, name)
75 def get_migration(app, name):
77 Returns the migration class implied by 'name'.
80 module = __import__(app.__name__ + "." + name, '', '', ['Migration'])
81 return module.Migration
83 print " ! Migration %s:%s probably doesn't exist." % (get_app_name(app), name)
90 (app, dict([(name, get_migration(app, name)) for name in get_migration_names(app)]))
91 for app in get_migrated_apps()
95 def dependency_tree():
96 tree = all_migrations()
98 # Annotate tree with 'backwards edges'
99 for app, classes in tree.items():
100 for name, cls in classes.items():
102 if not hasattr(cls, "needed_by"):
104 if hasattr(cls, "depends_on"):
105 for dapp, dname in cls.depends_on:
108 print "Migration %s in app %s depends on unmigrated app %s." % (
114 if dname not in tree[dapp]:
115 print "Migration %s in app %s depends on nonexistent migration %s in app %s." % (
122 cls.needs.append((dapp, dname))
123 if not hasattr(tree[dapp][dname], "needed_by"):
124 tree[dapp][dname].needed_by = []
125 tree[dapp][dname].needed_by.append((app, name))
127 # Sanity check whole tree
128 for app, classes in tree.items():
129 for name, cls in classes.items():
130 cls.dependencies = dependencies(tree, app, name)
135 def nice_trace(trace):
136 return " -> ".join([str((get_app_name(a), n)) for a, n in trace])
139 def dependencies(tree, app, name, trace=[]):
140 # Copy trace to stop pass-by-ref problems
143 for papp, pname in trace:
146 print "Found circular dependency: %s" % nice_trace(trace + [(app,name)])
149 # See if they depend in the same app the wrong way
150 migrations = get_migration_names(app)
151 if migrations.index(name) > migrations.index(pname):
152 print "Found a lower migration (%s) depending on a higher migration (%s) in the same app (%s)." % (pname, name, get_app_name(app))
153 print "Path: %s" % nice_trace(trace + [(app,name)])
155 # Get the dependencies of a migration
157 migration = tree[app][name]
158 for dapp, dname in migration.needs:
160 dependencies(tree, dapp, dname, trace+[(app,name)])
165 def remove_duplicates(l):
173 def needed_before_forwards(tree, app, name, sameapp=True):
175 Returns a list of migrations that must be applied before (app, name),
176 in the order they should be applied.
177 Used to make sure a migration can be applied (and to help apply up to it).
179 app_migrations = get_migration_names(app)
182 for aname in app_migrations[:app_migrations.index(name)]:
183 needed += needed_before_forwards(tree, app, aname, False)
184 needed += [(app, aname)]
185 for dapp, dname in tree[app][name].needs:
186 needed += needed_before_forwards(tree, dapp, dname)
187 needed += [(dapp, dname)]
188 return remove_duplicates(needed)
191 def needed_before_backwards(tree, app, name, sameapp=True):
193 Returns a list of migrations that must be unapplied before (app, name) is,
194 in the order they should be unapplied.
195 Used to make sure a migration can be unapplied (and to help unapply up to it).
197 app_migrations = get_migration_names(app)
200 for aname in reversed(app_migrations[app_migrations.index(name)+1:]):
201 needed += needed_before_backwards(tree, app, aname, False)
202 needed += [(app, aname)]
203 for dapp, dname in tree[app][name].needed_by:
204 needed += needed_before_backwards(tree, dapp, dname)
205 needed += [(dapp, dname)]
206 return remove_duplicates(needed)
209 def run_migrations(toprint, torun, recorder, app, migrations, fake=False, db_dry_run=False, silent=False):
211 Runs the specified migrations forwards, in order.
213 for migration in migrations:
214 app_name = get_app_name(app)
216 print toprint % (app_name, migration)
217 klass = get_migration(app, migration)
224 # If the database doesn't support running DDL inside a transaction
225 # *cough*MySQL*cough* then do a dry run first.
226 if not db.has_ddl_transactions:
228 db.debug, old_debug = False, db.debug
230 getattr(klass(), torun)()
232 traceback.print_exc()
233 print " ! Error found during dry run of migration! Aborting."
238 db.dry_run = bool(db_dry_run)
240 if db.has_ddl_transactions:
241 db.start_transaction()
243 getattr(klass(), torun)()
244 db.execute_deferred_sql()
246 if db.has_ddl_transactions:
247 db.rollback_transaction()
250 traceback.print_exc()
251 print " ! Error found during real run of migration! Aborting."
253 print " ! Since you have a database that does not support running"
254 print " ! schema-altering statements in transactions, we have had to"
255 print " ! leave it in an interim state between migrations."
256 if torun == "forwards":
258 print " ! You *might* be able to recover with:"
259 db.debug = db.dry_run = True
262 print " ! The South developers regret this has happened, and would"
263 print " ! like to gently persuade you to consider a slightly"
264 print " ! easier-to-deal-with DBMS."
267 if db.has_ddl_transactions:
268 db.commit_transaction()
271 # Record us as having done this
272 recorder(app_name, migration)
275 def run_forwards(app, migrations, fake=False, db_dry_run=False, silent=False):
277 Runs the specified migrations forwards, in order.
280 def record(app_name, migration):
281 # Record us as having done this
282 record = MigrationHistory.for_migration(app_name, migration)
283 record.applied = datetime.datetime.utcnow()
286 return run_migrations(
287 toprint = " > %s: %s",
291 migrations = migrations,
293 db_dry_run = db_dry_run,
298 def run_backwards(app, migrations, ignore=[], fake=False, db_dry_run=False, silent=False):
300 Runs the specified migrations backwards, in order, skipping those
301 migrations in 'ignore'.
304 def record(app_name, migration):
305 # Record us as having not done this
306 record = MigrationHistory.for_migration(app_name, migration)
309 return run_migrations(
310 toprint = " < %s: %s",
314 migrations = [x for x in migrations if x not in ignore],
316 db_dry_run = db_dry_run,
321 def right_side_of(x, y):
322 return left_side_of(reversed(x), reversed(y))
325 def left_side_of(x, y):
326 return list(y)[:len(x)] == list(x)
329 def forwards_problems(tree, forwards, done, silent=False):
331 for app, name in forwards:
332 if (app, name) not in done:
333 for dapp, dname in needed_before_backwards(tree, app, name):
334 if (dapp, dname) in done:
336 print " ! Migration (%s, %s) should not have been applied before (%s, %s) but was." % (get_app_name(dapp), dname, get_app_name(app), name)
337 problems.append(((app, name), (dapp, dname)))
342 def backwards_problems(tree, backwards, done, silent=False):
344 for app, name in backwards:
345 if (app, name) in done:
346 for dapp, dname in needed_before_forwards(tree, app, name):
347 if (dapp, dname) not in done:
349 print " ! Migration (%s, %s) should have been applied before (%s, %s) but wasn't." % (get_app_name(dapp), dname, get_app_name(app), name)
350 problems.append(((app, name), (dapp, dname)))
354 def migrate_app(app, target_name=None, resolve_mode=None, fake=False, db_dry_run=False, yes=False, silent=False, load_inital_data=False):
356 app_name = get_app_name(app)
358 db.debug = not silent
360 # If any of their app names in the DB contain a ., they're 0.2 or below, so migrate em
361 longuns = MigrationHistory.objects.filter(app_name__contains=".")
364 mh.app_name = short_from_long(mh.app_name)
367 print "- Updated your South 0.2 database."
369 # Find out what delightful migrations we have
370 tree = dependency_tree()
371 migrations = get_migration_names(app)
373 # If there aren't any, quit quizically
376 print "? You have no migrations for the '%s' app. You might want some." % app_name
379 if target_name not in migrations and target_name not in ["zero", None]:
380 matches = [x for x in migrations if x.startswith(target_name)]
381 if len(matches) == 1:
382 target = migrations.index(matches[0]) + 1
384 print " - Soft matched migration %s to %s." % (
388 target_name = matches[0]
389 elif len(matches) > 1:
391 print " - Prefix %s matches more than one migration:" % target_name
392 print " " + "\n ".join(matches)
396 print " ! '%s' is not a migration." % target_name
399 # Check there's no strange ones in the database
400 ghost_migrations = []
401 for m in MigrationHistory.objects.filter(applied__isnull = False):
403 if get_app(m.app_name) not in tree or m.migration not in tree[get_app(m.app_name)]:
404 ghost_migrations.append(m)
405 except ImproperlyConfigured:
411 print " ! These migrations are in the database but not on disk:"
412 print " - " + "\n - ".join(["%s: %s" % (x.app_name, x.migration) for x in ghost_migrations])
413 print " ! I'm not trusting myself; fix this yourself by fiddling"
414 print " ! with the south_migrationhistory table."
417 # Say what we're doing
419 print "Running migrations for %s:" % app_name
421 # Get the forwards and reverse dependencies for this target
422 if target_name == None:
423 target_name = migrations[-1]
424 if target_name == "zero":
426 backwards = needed_before_backwards(tree, app, migrations[0]) + [(app, migrations[0])]
428 forwards = needed_before_forwards(tree, app, target_name) + [(app, target_name)]
429 # When migrating backwards we want to remove up to and including
430 # the next migration up in this app (not the next one, that includes other apps)
432 migration_before_here = migrations[migrations.index(target_name)+1]
433 backwards = needed_before_backwards(tree, app, migration_before_here) + [(app, migration_before_here)]
437 # Get the list of currently applied migrations from the db
438 current_migrations = []
439 for m in MigrationHistory.objects.filter(applied__isnull = False):
441 current_migrations.append((get_app(m.app_name), m.migration))
442 except ImproperlyConfigured:
448 # Work out the direction
449 applied_for_this_app = list(MigrationHistory.objects.filter(app_name=app_name, applied__isnull=False).order_by("migration"))
450 if target_name == "zero":
452 elif not applied_for_this_app:
454 elif migrations.index(target_name) > migrations.index(applied_for_this_app[-1].migration):
456 elif migrations.index(target_name) < migrations.index(applied_for_this_app[-1].migration):
461 # Is the whole forward branch applied?
462 missing = [step for step in forwards if step not in current_migrations]
463 # If they're all applied, we only know it's not backwards
466 # If the remaining migrations are strictly a right segment of the forwards
467 # trace, we just need to go forwards to our target (and check for badness)
469 problems = forwards_problems(tree, forwards, current_migrations, silent=silent)
474 # What about the whole backward trace then?
476 missing = [step for step in backwards if step not in current_migrations]
477 # If they're all missing, stick with the forwards decision
478 if missing == backwards:
480 # If what's missing is a strict left segment of backwards (i.e.
481 # all the higher migrations) then we need to go backwards
483 problems = backwards_problems(tree, backwards, current_migrations, silent=silent)
488 if bad and resolve_mode not in ['merge']:
490 print " ! Inconsistent migration history"
491 print " ! The following options are available:"
492 print " --merge: will just attempt the migration ignoring any potential dependency conflicts."
497 print " - Migrating forwards to %s." % target_name
499 for mapp, mname in forwards:
500 if (mapp, mname) not in current_migrations:
501 result = run_forwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent)
502 if result is False: # The migrations errored, but nicely.
505 # Call any pending post_syncdb signals
506 db.send_pending_create_signals()
507 # Now load initial data, only if we're really doing things and ended up at current
508 if not fake and not db_dry_run and load_inital_data and target_name == migrations[-1]:
509 print " - Loading initial data for %s." % app_name
510 # Override Django's get_apps call temporarily to only load from the
512 old_get_apps, models.get_apps = (
514 lambda: [models.get_app(get_app_name(app))],
516 # Load the initial fixture
517 call_command('loaddata', 'initial_data', verbosity=1)
519 models.get_apps = old_get_apps
520 elif direction == -1:
522 print " - Migrating backwards to just after %s." % target_name
523 for mapp, mname in backwards:
524 if (mapp, mname) in current_migrations:
525 run_backwards(mapp, [mname], fake=fake, db_dry_run=db_dry_run, silent=silent)
528 print "- Nothing to migrate."