importdatetimeimportosimportsysimporttracebackimportinspectfromdjango.confimportsettingsfromdjango.dbimportmodelsfromdjango.core.exceptionsimportImproperlyConfiguredfromdjango.core.managementimportcall_commandfrommodelsimportMigrationHistoryfromsouth.dbimportdbfromsouth.ormimportFakeORMdefget_app(app):""" Returns the migrations module for the given app model name/module, or None if it does not use migrations. """ifisinstance(app,(str,unicode)):# If it's a string, use the models moduleapp=models.get_app(app)mod=__import__(app.__name__[:-7],{},{},['migrations'])ifhasattr(mod,'migrations'):returngetattr(mod,'migrations')defget_migrated_apps():""" Returns all apps with migrations. """formappinmodels.get_apps():app=get_app(mapp)ifapp:yieldappdefget_app_name(app):""" Returns the _internal_ app name for the given app module. i.e. for <module django.contrib.auth.models> will return 'auth' """returnapp.__name__.split('.')[-2]defget_app_fullname(app):""" Returns the full python name of an app - e.g. django.contrib.auth """returnapp.__name__[:-11]defshort_from_long(app_name):returnapp_name.split(".")[-1]defget_migration_names(app):""" Returns a list of migration file names for the given app. """returnsorted([filename[:-3]forfilenameinos.listdir(os.path.dirname(app.__file__))iffilename.endswith(".py")andfilename!="__init__.py"andnotfilename.startswith(".")])defget_migration_classes(app):""" Returns a list of migration classes (one for each migration) for the app. """fornameinget_migration_names(app):yieldget_migration(app,name)defget_migration(app,name):""" Returns the migration class implied by 'name'. """try:module=__import__(app.__name__+"."+name,'','',['Migration'])migclass=module.Migrationmigclass.orm=FakeORM(migclass,get_app_name(app))module._=lambdax:x# Fake i18nreturnmigclassexceptImportError:print" ! Migration %s:%s probably doesn't exist."%(get_app_name(app),name)print" - Traceback:"raiseexceptException,e:print"While loading migration '%s.%s':"%(get_app_name(app),name)raisedefall_migrations():returndict([(app,dict([(name,get_migration(app,name))fornameinget_migration_names(app)]))forappinget_migrated_apps()])defdependency_tree():tree=all_migrations()# Annotate tree with 'backwards edges'forapp,classesintree.items():forname,clsinclasses.items():cls.needs=[]ifnothasattr(cls,"needed_by"):cls.needed_by=[]ifhasattr(cls,"depends_on"):fordapp,dnameincls.depends_on:dapp=get_app(dapp)ifdappnotintree:print"Migration %s in app %s depends on unmigrated app %s."%(name,get_app_name(app),dapp,)sys.exit(1)ifdnamenotintree[dapp]:print"Migration %s in app %s depends on nonexistent migration %s in app %s."%(name,get_app_name(app),dname,get_app_name(dapp),)sys.exit(1)cls.needs.append((dapp,dname))ifnothasattr(tree[dapp][dname],"needed_by"):tree[dapp][dname].needed_by=[]tree[dapp][dname].needed_by.append((app,name))# Sanity check whole treeforapp,classesintree.items():forname,clsinclasses.items():cls.dependencies=dependencies(tree,app,name)returntreedefnice_trace(trace):return" -> ".join([str((get_app_name(a),n))fora,nintrace])defdependencies(tree,app,name,trace=[]):# Copy trace to stop pass-by-ref problemstrace=trace[:]# Sanity checkforpapp,pnameintrace:ifapp==papp:ifpname==name:print"Found circular dependency: %s"%nice_trace(trace+[(app,name)])sys.exit(1)else:# See if they depend in the same app the wrong waymigrations=get_migration_names(app)ifmigrations.index(name)>migrations.index(pname):print"Found a lower migration (%s) depending on a higher migration (%s) in the same app (%s)."%(pname,name,get_app_name(app))print"Path: %s"%nice_trace(trace+[(app,name)])sys.exit(1)# Get the dependencies of a migrationdeps=[]migration=tree[app][name]fordapp,dnameinmigration.needs:deps.extend(dependencies(tree,dapp,dname,trace+[(app,name)]))returndepsdefremove_duplicates(l):m=[]forxinl:ifxnotinm:m.append(x)returnmdefneeded_before_forwards(tree,app,name,sameapp=True):""" Returns a list of migrations that must be applied before (app, name), in the order they should be applied. Used to make sure a migration can be applied (and to help apply up to it). """app_migrations=get_migration_names(app)needed=[]ifsameapp:foranameinapp_migrations[:app_migrations.index(name)]:needed+=needed_before_forwards(tree,app,aname,False)needed+=[(app,aname)]fordapp,dnameintree[app][name].needs:needed+=needed_before_forwards(tree,dapp,dname)needed+=[(dapp,dname)]returnremove_duplicates(needed)defneeded_before_backwards(tree,app,name,sameapp=True):""" Returns a list of migrations that must be unapplied before (app, name) is, in the order they should be unapplied. Used to make sure a migration can be unapplied (and to help unapply up to it). """app_migrations=get_migration_names(app)needed=[]ifsameapp:foranameinreversed(app_migrations[app_migrations.index(name)+1:]):needed+=needed_before_backwards(tree,app,aname,False)needed+=[(app,aname)]fordapp,dnameintree[app][name].needed_by:needed+=needed_before_backwards(tree,dapp,dname)needed+=[(dapp,dname)]returnremove_duplicates(needed)defrun_migrations(toprint,torun,recorder,app,migrations,fake=False,db_dry_run=False,silent=False):""" Runs the specified migrations forwards/backwards, in order. """formigrationinmigrations:app_name=get_app_name(app)ifnotsilent:printtoprint%(app_name,migration)klass=get_migration(app,migration)iffake:ifnotsilent:print" (faked)"else:runfunc=getattr(klass(),torun)args=inspect.getargspec(runfunc)# If the database doesn't support running DDL inside a transaction# *cough*MySQL*cough* then do a dry run first.ifnotdb.has_ddl_transactionsordb_dry_run:ifnot(hasattr(klass,"no_dry_run")andklass.no_dry_run):db.dry_run=Truedb.debug,old_debug=False,db.debugpending_creates=db.get_pending_creates()try:iflen(args[0])==1:# They don't want an ORM paramrunfunc()else:runfunc(klass.orm)except:traceback.print_exc()print" ! Error found during dry run of migration! Aborting."returnFalsedb.debug=old_debugdb.clear_run_data(pending_creates)db.dry_run=Falseelifdb_dry_run:print" - Migration '%s' is marked for no-dry-run."# If they really wanted to dry-run, then quit!ifdb_dry_run:returnifdb.has_ddl_transactions:db.start_transaction()try:iflen(args[0])==1:# They don't want an ORM paramrunfunc()else:runfunc(klass.orm)db.execute_deferred_sql()except:ifdb.has_ddl_transactions:db.rollback_transaction()raiseelse:traceback.print_exc()print" ! Error found during real run of migration! Aborting."printprint" ! 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."iftorun=="forwards":printprint" ! You *might* be able to recover with:"db.debug=db.dry_run=Trueiflen(args[0])==1:klass().backwards()else:klass().backwards(klass.orm)printprint" ! 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."returnFalseelse:ifdb.has_ddl_transactions:db.commit_transaction()ifnotdb_dry_run:# Record us as having done thisrecorder(app_name,migration)defrun_forwards(app,migrations,fake=False,db_dry_run=False,silent=False):""" Runs the specified migrations forwards, in order. """defrecord(app_name,migration):# Record us as having done thisrecord=MigrationHistory.for_migration(app_name,migration)record.applied=datetime.datetime.utcnow()record.save()returnrun_migrations(toprint=" > %s: %s",torun="forwards",recorder=record,app=app,migrations=migrations,fake=fake,db_dry_run=db_dry_run,silent=silent,)defrun_backwards(app,migrations,ignore=[],fake=False,db_dry_run=False,silent=False):""" Runs the specified migrations backwards, in order, skipping those migrations in 'ignore'. """defrecord(app_name,migration):# Record us as having not done thisrecord=MigrationHistory.for_migration(app_name,migration)record.delete()returnrun_migrations(toprint=" < %s: %s",torun="backwards",recorder=record,app=app,migrations=[xforxinmigrationsifxnotinignore],fake=fake,db_dry_run=db_dry_run,silent=silent,)defright_side_of(x,y):returnleft_side_of(reversed(x),reversed(y))defleft_side_of(x,y):returnlist(y)[:len(x)]==list(x)defforwards_problems(tree,forwards,done,silent=False):problems=[]forapp,nameinforwards:if(app,name)notindone:fordapp,dnameinneeded_before_backwards(tree,app,name):if(dapp,dname)indone:ifnotsilent:print" ! Migration (%s, %s) should not have been applied before (%s, %s) but was."%(get_app_name(dapp),dname,get_app_name(app),name)problems.append(((app,name),(dapp,dname)))returnproblemsdefbackwards_problems(tree,backwards,done,silent=False):problems=[]forapp,nameinbackwards:if(app,name)indone:fordapp,dnameinneeded_before_forwards(tree,app,name):if(dapp,dname)notindone:ifnotsilent:print" ! Migration (%s, %s) should have been applied before (%s, %s) but wasn't."%(get_app_name(dapp),dname,get_app_name(app),name)problems.append(((app,name),(dapp,dname)))returnproblemsdefmigrate_app(app,target_name=None,resolve_mode=None,fake=False,db_dry_run=False,yes=False,silent=False,load_inital_data=False,skip=False):app_name=get_app_name(app)db.debug=notsilent# If any of their app names in the DB contain a ., they're 0.2 or below, so migrate emlonguns=MigrationHistory.objects.filter(app_name__contains=".")iflonguns:formhinlonguns:mh.app_name=short_from_long(mh.app_name)mh.save()ifnotsilent:print"- Updated your South 0.2 database."# Find out what delightful migrations we havetree=dependency_tree()migrations=get_migration_names(app)# If there aren't any, quit quizicallyifnotmigrations:ifnotsilent:print"? You have no migrations for the '%s' app. You might want some."%app_namereturniftarget_namenotinmigrationsandtarget_namenotin["zero",None]:matches=[xforxinmigrationsifx.startswith(target_name)]iflen(matches)==1:target=migrations.index(matches[0])+1ifnotsilent:print" - Soft matched migration %s to %s."%(target_name,matches[0])target_name=matches[0]eliflen(matches)>1:ifnotsilent:print" - Prefix %s matches more than one migration:"%target_nameprint" "+"\n ".join(matches)returnelse:ifnotsilent:print" ! '%s' is not a migration."%target_namereturn# Check there's no strange ones in the databaseghost_migrations=[]forminMigrationHistory.objects.filter(applied__isnull=False):try:ifget_app(m.app_name)notintreeorm.migrationnotintree[get_app(m.app_name)]:ghost_migrations.append(m)exceptImproperlyConfigured:passifghost_migrations:ifnotsilent:print" ! These migrations are in the database but not on disk:"print" - "+"\n - ".join(["%s: %s"%(x.app_name,x.migration)forxinghost_migrations])print" ! I'm not trusting myself; fix this yourself by fiddling"print" ! with the south_migrationhistory table."return# Say what we're doingifnotsilent:print"Running migrations for %s:"%app_name# Get the forwards and reverse dependencies for this targetiftarget_name==None:target_name=migrations[-1]iftarget_name=="zero":forwards=[]backwards=needed_before_backwards(tree,app,migrations[0])+[(app,migrations[0])]else:forwards=needed_before_forwards(tree,app,target_name)+[(app,target_name)]# When migrating backwards we want to remove up to and including# the next migration up in this app (not the next one, that includes other apps)try:migration_before_here=migrations[migrations.index(target_name)+1]backwards=needed_before_backwards(tree,app,migration_before_here)+[(app,migration_before_here)]exceptIndexError:backwards=[]# Get the list of currently applied migrations from the dbcurrent_migrations=[]forminMigrationHistory.objects.filter(applied__isnull=False):try:current_migrations.append((get_app(m.app_name),m.migration))exceptImproperlyConfigured:passdirection=Nonebad=False# Work out the directionapplied_for_this_app=list(MigrationHistory.objects.filter(app_name=app_name,applied__isnull=False).order_by("migration"))iftarget_name=="zero":direction=-1elifnotapplied_for_this_app:direction=1elifmigrations.index(target_name)>migrations.index(applied_for_this_app[-1].migration):direction=1elifmigrations.index(target_name)<migrations.index(applied_for_this_app[-1].migration):direction=-1else:direction=None# Is the whole forward branch applied?missing=[stepforstepinforwardsifstepnotincurrent_migrations]# If they're all applied, we only know it's not backwardsifnotmissing:direction=None# If the remaining migrations are strictly a right segment of the forwards# trace, we just need to go forwards to our target (and check for badness)else:problems=forwards_problems(tree,forwards,current_migrations,silent=silent)ifproblems:bad=Truedirection=1# What about the whole backward trace then?ifnotbad:missing=[stepforstepinbackwardsifstepnotincurrent_migrations]# If they're all missing, stick with the forwards decisionifmissing==backwards:pass# If what's missing is a strict left segment of backwards (i.e.# all the higher migrations) then we need to go backwardselse:problems=backwards_problems(tree,backwards,current_migrations,silent=silent)ifproblems:bad=Truedirection=-1ifbadandresolve_modenotin['merge']andnotskip:ifnotsilent:print" ! Inconsistent migration history"print" ! The following options are available:"print" --merge: will just attempt the migration ignoring any potential dependency conflicts."sys.exit(1)ifdirection==1:ifnotsilent:print" - Migrating forwards to %s."%target_nametry:formapp,mnameinforwards:if(mapp,mname)notincurrent_migrations:result=run_forwards(mapp,[mname],fake=fake,db_dry_run=db_dry_run,silent=silent)ifresultisFalse:# The migrations errored, but nicely.returnFalsefinally:# Call any pending post_syncdb signalsdb.send_pending_create_signals()# Now load initial data, only if we're really doing things and ended up at currentifnotfakeandnotdb_dry_runandload_inital_dataandtarget_name==migrations[-1]:print" - Loading initial data for %s."%app_name# Override Django's get_apps call temporarily to only load from the# current appold_get_apps,models.get_apps=(models.get_apps,lambda:[models.get_app(get_app_name(app))],)# Load the initial fixturecall_command('loaddata','initial_data',verbosity=1)# Un-overridemodels.get_apps=old_get_appselifdirection==-1:ifnotsilent:print" - Migrating backwards to just after %s."%target_nameformapp,mnameinbackwards:if(mapp,mname)incurrent_migrations:run_backwards(mapp,[mname],fake=fake,db_dry_run=db_dry_run,silent=silent)else:ifnotsilent:print"- Nothing to migrate."