Ejemplo n.º 1
0
    def test_get_app(self):

        P1 = __import__("fakeapp.migrations", {}, {}, [''])

        self.assertEqual(P1, migration.get_app("fakeapp"))
        self.assertEqual(
            P1, migration.get_app(self.create_fake_app("fakeapp.models")))
Ejemplo n.º 2
0
    def test_dependencies(self):

        fakeapp = migration.get_app("fakeapp")
        otherfakeapp = migration.get_app("otherfakeapp")

        # Test a simple path
        tree = migration.dependency_tree()
        self.assertEqual(
            map(
                snd,
                migration.needed_before_forwards(tree, fakeapp,
                                                 "0003_alter_spam")),
            ['0001_spam', '0002_eggs'],
        )

        # And a complex one, with both back and forwards deps
        self.assertEqual(
            map(
                snd,
                migration.needed_before_forwards(tree, otherfakeapp,
                                                 "0003_third")),
            [
                '0001_spam', '0001_first', '0002_second', '0002_eggs',
                '0003_alter_spam'
            ],
        )
Ejemplo n.º 3
0
    def test_all_migrations(self):

        app = migration.get_app("fakeapp")
        otherapp = migration.get_app("otherfakeapp")

        self.assertEqual(
            {
                app: {
                    "0001_spam":
                    migration.get_migration(app, "0001_spam"),
                    "0002_eggs":
                    migration.get_migration(app, "0002_eggs"),
                    "0003_alter_spam":
                    migration.get_migration(app, "0003_alter_spam"),
                },
                otherapp: {
                    "0001_first": migration.get_migration(
                        otherapp, "0001_first"),
                    "0002_second": migration.get_migration(
                        otherapp, "0002_second"),
                    "0003_third": migration.get_migration(
                        otherapp, "0003_third"),
                },
            },
            migration.all_migrations(),
        )
Ejemplo n.º 4
0
 def handle(self, app=None, *args, **options):
     
     # Make sure we have an app
     if not app:
         print "Please specify an app to convert."
         return
     
     # 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
     
     # Try to get its list of models
     model_list = models.get_models(app_module)
     if not model_list:
         print "This application has no models; this command is for applications that already have models syncdb'd."
         print "Make some models, and then use ./manage.py startmigration %s --initial instead." % app
         return
     
     # Ask South if it thinks it's already got migrations
     if get_app(app_module):
         print "This application is already managed by South."
         return
     
     # Finally! It seems we've got a candidate, so do the two-command trick
     management.call_command("startmigration", app, initial=True)
     management.call_command("migrate", app, "0001", fake=True)
     print 
     print "App '%s' converted. Note that South assumed the application's models matched the database" % app
     print "(i.e. you haven't changed it since last syncdb); if you have, you should delete the %s/migrations"
     print "directory, revert models.py so it matches the database, and try again."
Ejemplo n.º 5
0
 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()), [])
Ejemplo n.º 6
0
 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()), [])
Ejemplo n.º 7
0
    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()), [])
Ejemplo n.º 8
0
 def handle_noargs(self, **options):
     # Work out what uses migrations and so doesn't need syncing
     apps_needing_sync = []
     apps_migrated = []
     for app in models.get_apps():
         app_name = get_app_name(app)
         migrations = migration.get_app(app)
         if migrations is None:
             apps_needing_sync.append(app_name)
         else:
             # This is a migrated app, leave it
             apps_migrated.append(app_name)
     # Run syncdb on only the ones needed
     print "Syncing..."
     old_installed, settings.INSTALLED_APPS = settings.INSTALLED_APPS, apps_needing_sync
     old_app_store, cache.app_store = cache.app_store, SortedDict([
         (k, v) for (k, v) in cache.app_store.items()
         if get_app_name(k) in apps_needing_sync
     ])
     syncdb.Command().execute(**options)
     settings.INSTALLED_APPS = old_installed
     cache.app_store = old_app_store
     # Migrate if needed
     if options.get('migrate', True):
         print "Migrating..."
         management.call_command('migrate')
     # Be obvious about what we did
     print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync)
     
     if options.get('migrate', True):
         print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated)
     else:
         print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated)
         print "(use ./manage.py migrate to migrate these)"
Ejemplo n.º 9
0
 def test_dependencies(self):
     
     fakeapp = migration.get_app("fakeapp")
     otherfakeapp = migration.get_app("otherfakeapp")
     
     # Test a simple path
     tree = migration.dependency_tree()
     self.assertEqual(
         map(snd, migration.needed_before_forwards(tree, fakeapp, "0003_alter_spam")),
         ['0001_spam', '0002_eggs'],
     )
     
     # And a complex one, with both back and forwards deps
     self.assertEqual(
         map(snd, migration.needed_before_forwards(tree, otherfakeapp, "0003_third")),
         ['0001_spam', '0001_first', '0002_second', '0002_eggs', '0003_alter_spam'],
     )
Ejemplo n.º 10
0
 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()), [])
Ejemplo n.º 11
0
 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()), [])
Ejemplo n.º 12
0
 def test_all_migrations(self):
     
     app = migration.get_app("fakeapp")
     otherapp = migration.get_app("otherfakeapp")
     
     self.assertEqual({
             app: {
                 "0001_spam": migration.get_migration(app, "0001_spam"),
                 "0002_eggs": migration.get_migration(app, "0002_eggs"),
                 "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"),
             },
             otherapp: {
                 "0001_first": migration.get_migration(otherapp, "0001_first"),
                 "0002_second": migration.get_migration(otherapp, "0002_second"),
                 "0003_third": migration.get_migration(otherapp, "0003_third"),
             },
         },
         migration.all_migrations(),
     )
Ejemplo n.º 13
0
 def test_all_migrations(self):
     
     app = migration.get_app("fakeapp")
     
     self.assertEqual(
         {app: {
             "0001_spam": migration.get_migration(app, "0001_spam"),
             "0002_eggs": migration.get_migration(app, "0002_eggs"),
         }},
         migration.all_migrations(),
     )
Ejemplo n.º 14
0
 def test_all_migrations(self):
     
     app = migration.get_app("fakeapp")
     
     self.assertEqual(
         {app: {
             "0001_spam": migration.get_migration(app, "0001_spam"),
             "0002_eggs": migration.get_migration(app, "0002_eggs"),
             "0003_alter_spam": migration.get_migration(app, "0003_alter_spam"),
         }},
         migration.all_migrations(),
     )
Ejemplo n.º 15
0
 def handle_noargs(self, migrate_all=False, **options):
     # Work out what uses migrations and so doesn't need syncing
     apps_needing_sync = []
     apps_migrated = []
     for app in models.get_apps():
         app_name = get_app_name(app)
         migrations = migration.get_app(app)
         if migrations is None or migrate_all:
             apps_needing_sync.append(app_name)
         else:
             # This is a migrated app, leave it
             apps_migrated.append(app_name)
     verbosity = int(options.get('verbosity', 0))
     
     # Run syncdb on only the ones needed
     if verbosity:
         print "Syncing..."
     
     old_installed, settings.INSTALLED_APPS = settings.INSTALLED_APPS, apps_needing_sync
     old_app_store, cache.app_store = cache.app_store, SortedDict([
         (k, v) for (k, v) in cache.app_store.items()
         if get_app_name(k) in apps_needing_sync
     ])
     
     # This will allow the setting of the MySQL storage engine, for example.
     db.connection_init()
     
     # OK, run the actual syncdb
     syncdb.Command().execute(**options)
     
     settings.INSTALLED_APPS = old_installed
     cache.app_store = old_app_store
     
     # Migrate if needed
     if options.get('migrate', True):
         if verbosity:
             print "Migrating..."
         management.call_command('migrate', **options)
     
     # Be obvious about what we did
     if verbosity:
         print "\nSynced:\n > %s" % "\n > ".join(apps_needing_sync)
     
     if options.get('migrate', True):
         if verbosity:
             print "\nMigrated:\n - %s" % "\n - ".join(apps_migrated)
     else:
         if verbosity:
             print "\nNot synced (use migrations):\n - %s" % "\n - ".join(apps_migrated)
             print "(use ./manage.py migrate to migrate these)"
Ejemplo n.º 16
0
    def handle(self, app=None, *args, **options):

        # Make sure we have an app
        if not app:
            print "Please specify an app to convert."
            return

        # 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

        # Try to get its list of models
        model_list = models.get_models(app_module)
        if not model_list:
            print "This application has no models; this command is for applications that already have models syncdb'd."
            print "Make some models, and then use ./manage.py startmigration %s --initial instead." % app
            return

        # Ask South if it thinks it's already got migrations
        if get_app(app_module):
            print "This application is already managed by South."
            return

        # Finally! It seems we've got a candidate, so do the two-command trick
        verbosity = int(options.get('verbosity', 0))
        management.call_command("startmigration",
                                app,
                                initial=True,
                                verbosity=verbosity)

        # Now, we need to re-clean and sanitise appcache
        hacks.clear_app_cache()
        hacks.repopulate_app_cache()

        # Now, migrate
        management.call_command("migrate",
                                app,
                                "0001",
                                fake=True,
                                verbosity=verbosity)

        print
        print "App '%s' converted. Note that South assumed the application's models matched the database" % app
        print "(i.e. you haven't changed it since last syncdb); if you have, you should delete the %s/migrations"
        print "directory, revert models.py so it matches the database, and try again."
Ejemplo n.º 17
0
 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()), [])
Ejemplo n.º 18
0
 def handle(self, app=None, name="", added_model_list=None, added_field_list=None, initial=False, freeze_list=None, auto=False, stdout=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 []
     
     # --stdout means name = -
     if stdout:
         name = "-"
     
     # Make sure options are compatable
     if initial and (added_model_list or added_field_list or auto):
         print "You cannot use --initial and other options together"
         print self.usage_str
         return
     if auto and (added_model_list or added_field_list or initial):
         print "You cannot use --auto and other options together"
         print self.usage_str
         return
     
     # specify the default name 'initial' if a name wasn't specified and we're
     # doing a migration for an entire app
     if not name and initial:
         name = 'initial'
     
     # if not name, there's an error
     if not name:
         print "You must name this migration"
         print self.usage_str
         return
     
     if not app:
         print "Please provide an app in which to create the migration."
         print self.usage_str
         return
     
     # Make sure the app is short form
     app = app.split(".")[-1]
     
     # See if the app exists
     app_models_module = models.get_app(app)
     if not app_models_module:
         print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app
         print self.usage_str
         return
     
     # If they've set SOUTH_AUTO_FREEZE_APP = True (or not set it - defaults to True)
     if not hasattr(settings, 'SOUTH_AUTO_FREEZE_APP') or settings.SOUTH_AUTO_FREEZE_APP:
         if freeze_list and app not in freeze_list:
             freeze_list += [app]
         else:
             freeze_list = [app]
     
     # Make the migrations directory if it's not there
     app_module_path = app_models_module.__name__.split('.')[0:-1]
     try:
         app_module = __import__('.'.join(app_module_path), {}, {}, [''])
     except ImportError:
         print "Couldn't find path to App '%s'." % app
         print self.usage_str
         return
         
     migrations_dir = os.path.join(
         os.path.dirname(app_module.__file__),
         "migrations",
     )
     
     # Make sure there's a migrations directory and __init__.py
     if not os.path.isdir(migrations_dir):
         print "Creating migrations directory at '%s'..." % migrations_dir
         os.mkdir(migrations_dir)
     init_path = os.path.join(migrations_dir, "__init__.py")
     if not os.path.isfile(init_path):
         # Touch the init py file
         print "Creating __init__.py in '%s'..." % migrations_dir
         open(init_path, "w").close()
     
     # See what filename is next in line. We assume they use numbers.
     migrations = migration.get_migration_names(migration.get_app(app))
     highest_number = 0
     for migration_name in migrations:
         try:
             number = int(migration_name.split("_")[0])
             highest_number = max(highest_number, number)
         except ValueError:
             pass
     
     # Make the new filename
     new_filename = "%04i%s_%s.py" % (
         highest_number + 1,
         "".join([random.choice(string.letters.lower()) for i in range(0)]), # Possible random stuff insertion
         name,
     )
     
     # Find the source file encoding, using PEP 0263's method
     encoding = None
     first_two_lines = inspect.getsourcelines(app_models_module)[0][:2]
     for line in first_two_lines:
         if re.search("coding[:=]\s*([-\w.]+)", line):
             encoding = line
     
     # Initialise forwards, backwards and models to blank things
     forwards = ""
     backwards = ""
     frozen_models = {} # Frozen models, used by the Fake ORM
     complete_apps = set() # Apps that are completely frozen - useable for diffing.
     
     # Sets of actions
     added_models = set()
     deleted_models = [] # Special: contains instances _not_ string keys
     added_fields = set()
     deleted_fields = [] # Similar to deleted_models
     changed_fields = [] # (mkey, fname, old_def, new_def)
     added_uniques = set() # (mkey, field_names)
     deleted_uniques = set() # (mkey, field_names)
     
     # --initial means 'add all models in this app'.
     if initial:
         for model in models.get_models(app_models_module):
             added_models.add("%s.%s" % (app, model._meta.object_name))
     
     # Added models might be 'model' or 'app.model'.
     for modelname in added_model_list:
         if "." in modelname:
             added_models.add(modelname)
         else:
             added_models.add("%s.%s" % (app, modelname))
     
     # Fields need translating from "model.field" to (app.model, field)
     for fielddef in added_field_list:
         try:
             modelname, fieldname = fielddef.split(".", 1)
         except ValueError:
             print "The field specification '%s' is not in modelname.fieldname format." % fielddef
         else:
             added_fields.add(("%s.%s" % (app, modelname), fieldname))
     
     # Add anything frozen (I almost called the dict Iceland...)
     if freeze_list:
         for item in freeze_list:
             if "." in item:
                 # It's a specific model
                 app_name, model_name = item.split(".", 1)
                 model = models.get_model(app_name, model_name)
                 if model is None:
                     print "Cannot find the model '%s' to freeze it." % item
                     print self.usage_str
                     return
                 frozen_models[model] = None
             else:
                 # Get everything in an app!
                 frozen_models.update(dict([(x, None) for x in models.get_models(models.get_app(item))]))
                 complete_apps.add(item.split(".")[-1])
         # For every model in the freeze list, add in frozen dependencies
         for model in list(frozen_models):
             frozen_models.update(model_dependencies(model))
     
     
     ### Automatic Detection ###
     if auto:
         # Get the last migration for this app
         last_models = None
         app_module = migration.get_app(app)
         if app_module is None:
             print "You cannot use automatic detection on the first migration of an app. Try --initial instead."
         else:
             migrations = list(migration.get_migration_classes(app_module))
             if not migrations:
                 print "You cannot use automatic detection on the first migration of an app. Try --initial instead."
             else:
                 if hasattr(migrations[-1], "complete_apps") and \
                    app in migrations[-1].complete_apps:
                     last_models = migrations[-1].models
                     last_orm = migrations[-1].orm
                 else:
                     print "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." % app
         
         # Right, did we manage to get the last set of models?
         if last_models is None:
             print self.usage_str
             return
         
         new = dict([
             (model_key(model), prep_for_freeze(model))
             for model in models.get_models(app_models_module)
             if (
                 not getattr(model._meta, "proxy", False) and \
                 getattr(model._meta, "managed", True) and \
                 not getattr(model._meta, "abstract", False)
             )
         ])
         # And filter other apps out of the old
         old = dict([
             (key, fields)
             for key, fields in last_models.items()
             if key.split(".", 1)[0] == app
         ])
         am, dm, cm, af, df, cf, afu, dfu = models_diff(old, new)
         
         # For models that were there before and after, do a meta diff
         was_meta_change = False
         for mkey in cm:
             au, du = meta_diff(old[mkey].get("Meta", {}), new[mkey].get("Meta", {}))
             for entry in au:
                 added_uniques.add((mkey, entry))
                 was_meta_change = True
             for entry in du:
                 deleted_uniques.add((mkey, entry, last_orm[mkey]))
                 was_meta_change = True
         
         if not (am or dm or af or df or cf or afu or dfu or was_meta_change):
             print "Nothing seems to have changed."
             return
         
         # Add items to the todo lists
         added_models.update(am)
         added_fields.update(af)
         changed_fields.extend(cf)
         
         # Deleted models are from the past, and so we use instances instead.
         for mkey in dm:
             model = last_orm[mkey]
             fields = last_models[mkey]
             if "Meta" in fields:
                 del fields['Meta']
             deleted_models.append((model, fields, last_models))
         
         # For deleted fields, we tag the instance on the end too
         for mkey, fname in df:
             deleted_fields.append((
                 mkey,
                 fname,
                 last_orm[mkey]._meta.get_field_by_name(fname)[0],
                 last_models[mkey][fname],
                 last_models,
             ))
         
         # Uniques need merging
         added_uniques = added_uniques.union(afu)
         
         for mkey, entry in dfu:
             deleted_uniques.add((mkey, entry, last_orm[mkey]))
     
     
     ### Added model ###
     for mkey in added_models:
         
         print " + Added model '%s'" % (mkey,)
         
         model = model_unkey(mkey)
         
         # Add the model's dependencies to the frozens
         frozen_models.update(model_dependencies(model))
         # Get the field definitions
         fields = modelsinspector.get_model_fields(model)
         # Turn the (class, args, kwargs) format into a string
         fields = triples_to_defs(app, model, fields)
         # Make the code
         forwards += CREATE_TABLE_SNIPPET % (
             model._meta.object_name,
             model._meta.db_table,
             "\n            ".join(["('%s', orm[%r])," % (fname, mkey + ":" + fname) for fname, fdef in fields.items()]),
             model._meta.app_label,
             model._meta.object_name,
         )
         # And the backwards code
         backwards += DELETE_TABLE_SNIPPET % (
             model._meta.object_name, 
             model._meta.db_table
         )
         # Now add M2M fields to be done
         for field in model._meta.local_many_to_many:
             added_fields.add((mkey, field.attname))
         # And unique_togethers to be added
         for ut in model._meta.unique_together:
             added_uniques.add((mkey, tuple(ut)))
     
     
     ### Added fields ###
     for mkey, field_name in added_fields:
         
         # Get the model
         model = model_unkey(mkey)
         # Get the field
         try:
             field = model._meta.get_field(field_name)
         except FieldDoesNotExist:
             print "Model '%s' doesn't have a field '%s'" % (mkey, field_name)
             return
         
         # ManyToMany fields need special attention.
         if isinstance(field, models.ManyToManyField):
             if not field.rel.through: # Bug #120
                 # Add a frozen model for each side
                 frozen_models[model] = None
                 frozen_models[field.rel.to] = None
                 # And a field defn, that's actually a table creation
                 forwards += CREATE_M2MFIELD_SNIPPET % (
                     model._meta.object_name,
                     field.name,
                     field.m2m_db_table(),
                     field.m2m_column_name()[:-3], # strip off the '_id' at the end
                     poss_ormise(app, model, model._meta.object_name),
                     field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned
                     poss_ormise(app, field.rel.to, field.rel.to._meta.object_name)
                     )
                 backwards += DELETE_M2MFIELD_SNIPPET % (
                     model._meta.object_name,
                     field.name,
                     field.m2m_db_table()
                 )
                 print " + Added M2M '%s.%s'" % (mkey, field_name)
             continue
         
         # GenericRelations need ignoring
         if isinstance(field, GenericRelation):
             continue
         
         print " + Added field '%s.%s'" % (mkey, field_name)
         
         # Add any dependencies
         frozen_models.update(field_dependencies(field))
         
         # Work out the definition
         triple = remove_useless_attributes(
             modelsinspector.get_model_fields(model)[field_name])
         
         field_definition = make_field_constructor(app, field, triple)
         
         forwards += CREATE_FIELD_SNIPPET % (
             model._meta.object_name,
             field.name,
             model._meta.db_table,
             field.name,
             "orm[%r]" % (mkey + ":" + field.name),
         )
         backwards += DELETE_FIELD_SNIPPET % (
             model._meta.object_name,
             field.name,
             model._meta.db_table,
             field.column,
         )
     
     
     ### Deleted fields ###
     for mkey, field_name, field, triple, last_models in deleted_fields:
         
         print " - Deleted field '%s.%s'" % (mkey, field_name)
         
         # Get the model
         model = model_unkey(mkey)
         
         # ManyToMany fields need special attention.
         if isinstance(field, models.ManyToManyField):
             # And a field defn, that's actually a table deletion
             forwards += DELETE_M2MFIELD_SNIPPET % (
                 model._meta.object_name,
                 field.name,
                 field.m2m_db_table()
             )
             backwards += CREATE_M2MFIELD_SNIPPET % (
                 model._meta.object_name,
                 field.name,
                 field.m2m_db_table(),
                 field.m2m_column_name()[:-3], # strip off the '_id' at the end
                 poss_ormise(app, model, model._meta.object_name),
                 field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned
                 poss_ormise(app, field.rel.to, field.rel.to._meta.object_name)
                 )
             continue
         
         # Work out the definition
         triple = remove_useless_attributes(triple)
         field_definition = make_field_constructor(app, field, triple)
         
         forwards += DELETE_FIELD_SNIPPET % (
             model._meta.object_name,
             field.name,
             model._meta.db_table,
             field.column,
         )
         backwards += CREATE_FIELD_SNIPPET % (
             model._meta.object_name,
             field.name,
             model._meta.db_table,
             field.name,
             "orm[%r]" % (mkey + ":" + field.name),
         )
     
     
     ### Deleted model ###
     for model, fields, last_models in deleted_models:
         
         print " - Deleted model '%s.%s'" % (model._meta.app_label,model._meta.object_name)
         
         # Turn the (class, args, kwargs) format into a string
         fields = triples_to_defs(app, model, fields)
         
         # Make the code
         forwards += DELETE_TABLE_SNIPPET % (
             model._meta.object_name, 
             model._meta.db_table
         )
         # And the backwards code
         backwards += CREATE_TABLE_SNIPPET % (
             model._meta.object_name,
             model._meta.db_table,
             "\n            ".join(["('%s', orm[%r])," % (fname, mkey + ":" + fname) for fname, fdef in fields.items()]),
             model._meta.app_label,
             model._meta.object_name,
         )
     
     
     ### Changed fields ###
     for mkey, field_name, old_triple, new_triple in changed_fields:
         
         model = model_unkey(mkey)
         old_def = triples_to_defs(app, model, {
             field_name: old_triple,
         })[field_name]
         new_def = triples_to_defs(app, model, {
             field_name: new_triple,
         })[field_name]
         
         # We need to create the field, to see if it needs _id, or if it's an M2M
         field = model._meta.get_field_by_name(field_name)[0]
         
         if hasattr(field, "m2m_db_table"):
             # See if anything has ACTUALLY changed
             if old_triple[1] != new_triple[1]:
                 print " ! Detected change to the target model of M2M field '%s.%s'. South can't handle this; leaving this change out." % (mkey, field_name)
             continue
         
         print " ~ Changed field '%s.%s'." % (mkey, field_name)
         
         forwards += CHANGE_FIELD_SNIPPET % (
             model._meta.object_name,
             field_name,
             new_def,
             model._meta.db_table,
             field.get_attname(),
             "orm[%r]" % (mkey + ":" + field.name),
         )
         
         backwards += CHANGE_FIELD_SNIPPET % (
             model._meta.object_name,
             field_name,
             old_def,
             model._meta.db_table,
             field.get_attname(),
             "orm[%r]" % (mkey + ":" + field.name),
         )
     
     
     ### Added unique_togethers ###
     for mkey, ut in added_uniques:
         
         model = model_unkey(mkey)
         if len(ut) == 1:
             print " + Added unique for %s on %s." % (", ".join(ut), model._meta.object_name)
         else:
             print " + Added unique_together for [%s] on %s." % (", ".join(ut), model._meta.object_name)
         
         cols = [get_field_column(model, f) for f in ut]
         
         forwards += CREATE_UNIQUE_SNIPPET % (
             ", ".join(ut),
             model._meta.object_name,
             model._meta.db_table,
             cols,
         )
         
         backwards = DELETE_UNIQUE_SNIPPET % (
             ", ".join(ut),
             model._meta.object_name,
             model._meta.db_table,
             cols,
         ) + backwards
     
     
     ### Deleted unique_togethers ###
     for mkey, ut, model in deleted_uniques:
         
         if len(ut) == 1:
             print " - Deleted unique for %s on %s." % (", ".join(ut), model._meta.object_name)
         else:
             print " - Deleted unique_together for [%s] on %s." % (", ".join(ut), model._meta.object_name)
         
         cols = [get_field_column(model, f) for f in ut]
         
         forwards = DELETE_UNIQUE_SNIPPET % (
             ", ".join(ut),
             model._meta.object_name,
             model._meta.db_table,
             cols,
         ) + forwards
         
         backwards += CREATE_UNIQUE_SNIPPET % (
             ", ".join(ut),
             model._meta.object_name,
             model._meta.db_table,
             cols,
         )
     
     
     # Default values for forwards/backwards
     if (not forwards) and (not backwards):
         forwards = '"Write your forwards migration here"'
         backwards = '"Write your backwards migration here"'
     
     all_models = {}
     
     # Fill out frozen model definitions
     for model, last_models in frozen_models.items():
         all_models[model_key(model)] = prep_for_freeze(model, last_models)
     
     # Do some model cleanup, and warnings
     for modelname, model in all_models.items():
         for fieldname, fielddef in model.items():
             # Remove empty-after-cleaning Metas.
             if fieldname == "Meta" and not fielddef:
                 del model['Meta']
             # Warn about undefined fields
             elif fielddef is None:
                 print "WARNING: Cannot get definition for '%s' on '%s'. Please edit the migration manually to define it, or add the south_field_triple method to it." % (
                     fieldname,
                     modelname,
                 )
                 model[fieldname] = FIELD_NEEDS_DEF_SNIPPET
     
     # So, what's in this file, then?
     file_contents = MIGRATION_SNIPPET % (
         encoding or "", '.'.join(app_module_path), 
         forwards, 
         backwards, 
         pprint_frozen_models(all_models),
         complete_apps and "complete_apps = [%s]" % (", ".join(map(repr, complete_apps))) or ""
     )
     # - 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_dir, new_filename), "w")
         fp.write(file_contents)
         fp.close()
         print "Created %s." % new_filename
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
 def test_get_app(self):
     
     P1 = __import__("fakeapp.migrations", {}, {}, [''])
     
     self.assertEqual(P1, migration.get_app("fakeapp"))
     self.assertEqual(P1, migration.get_app(self.create_fake_app("fakeapp.models")))
Ejemplo n.º 21
0
    def handle(self, app=None, name="", model_list=None, field_list=None, initial=False, **options):
        
        # If model_list is None, then it's an empty list
        model_list = model_list or []
        
        # If field_list is None, then it's an empty list
        field_list = field_list or []
        
        # make sure --model and --all aren't both specified
        if initial and (model_list or field_list):
            print "You cannot use --initial and other options together"
            return
            
        # specify the default name 'initial' if a name wasn't specified and we're
        # doing a migration for an entire app
        if not name and initial:
            name = 'initial'
            
        # if not name, there's an error
        if not name:
            print "You must name this migration"
            return
        
        if not app:
            print "Please provide an app in which to create the migration."
            return
            
        # See if the app exists
        app_models_module = models.get_app(app)
        if not app_models_module:
            print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app
            return
            
        # Determine what models should be included in this migration.
        models_to_migrate = []
        if initial:
            models_to_migrate = models.get_models(app_models_module)
            if not models_to_migrate:
                print "No models found in app '%s'" % (app)
                return
        else:
            for model_name in model_list:
                model = models.get_model(app, model_name)
                if not model:
                    print "Couldn't find model '%s' in app '%s'" % (model_name, app)
                    return
                    
                models_to_migrate.append(model)
        
        # See what fields need to be included
        fields_to_add = []
        for field_spec in field_list:
            model_name, field_name = field_spec.split(".", 1)
            model = models.get_model(app, model_name)
            if not model:
                print "Couldn't find model '%s' in app '%s'" % (model_name, app)
                return
            try:
                field = model._meta.get_field(field_name)
            except FieldDoesNotExist:
                print "Model '%s' doesn't have a field '%s'" % (model_name, field_name)
                return
            fields_to_add.append((model, field_name, field))
        
        # Make the migrations directory if it's not there
        app_module_path = app_models_module.__name__.split('.')[0:-1]
        try:
            app_module = __import__('.'.join(app_module_path), {}, {}, [''])
        except ImportError:
            print "Couldn't find path to App '%s'." % app
            return
            
        migrations_dir = os.path.join(
            os.path.dirname(app_module.__file__),
            "migrations",
        )
        # Make sure there's a migrations directory and __init__.py
        if not os.path.isdir(migrations_dir):
            print "Creating migrations directory at '%s'..." % migrations_dir
            os.mkdir(migrations_dir)
        init_path = os.path.join(migrations_dir, "__init__.py")
        if not os.path.isfile(init_path):
            # Touch the init py file
            print "Creating __init__.py in '%s'..." % migrations_dir
            open(init_path, "w").close()
        # See what filename is next in line. We assume they use numbers.
        migrations = migration.get_migration_names(migration.get_app(app))
        highest_number = 0
        for migration_name in migrations:
            try:
                number = int(migration_name.split("_")[0])
                highest_number = max(highest_number, number)
            except ValueError:
                pass
        # Make the new filename
        new_filename = "%04i%s_%s.py" % (
            highest_number + 1,
            "".join([random.choice(string.letters.lower()) for i in range(0)]), # Possible random stuff insertion
            name,
        )
        # If there's a model, make the migration skeleton, else leave it bare
        forwards, backwards = '', ''
        if fields_to_add:
            # First, do the added fields
            for model, field_name, field in fields_to_add:
                field_definition = generate_field_definition(model, field)
                
                if isinstance(field, models.ManyToManyField):
                    # Make a mock model for each side
                    mock_model = "\n".join([
                        create_mock_model(model, "        "), 
                        create_mock_model(field.rel.to, "        ")
                    ])
                    # And a field defn, that's actually a table creation
                    forwards += '''
        # Mock Model
%s
        # Adding ManyToManyField '%s.%s'
        db.create_table('%s', (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('%s', models.ForeignKey(%s, null=False)),
            ('%s', models.ForeignKey(%s, null=False))
        )) ''' % (
                mock_model,
                model._meta.object_name,
                field.name,
                field.m2m_db_table(),
                field.m2m_column_name()[:-3], # strip off the '_id' at the end
                model._meta.object_name,
                field.m2m_reverse_name()[:-3], # strip off the '_id' at the ned
                field.rel.to._meta.object_name
                )
                    backwards += '''
        # Dropping ManyToManyField '%s.%s'
        db.drop_table('%s')''' % (
                        model._meta.object_name,
                        field.name,
                        field.m2m_db_table()
                    )
                    continue
                elif field.rel: # ForeignKey, etc.
                    mock_model = create_mock_model(field.rel.to, "        ")
                    field_definition = related_field_definition(field, field_definition)
                else:
                    mock_model = None
                
                # If we can't get it (inspect madness?) then insert placeholder
                if not field_definition:
                    print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \
                                (model._meta.object_name, field.name)
                    field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name)
                
                if mock_model:
                    forwards += '''
        # Mock model
%s
        ''' % (mock_model)
                
                forwards += '''
        # Adding field '%s.%s'
        db.add_column(%r, %r, %s)
        ''' % (
            model._meta.object_name,
            field.name,
            model._meta.db_table,
            field.name,
            field_definition,
        )
                backwards += '''
        # Deleting field '%s.%s'
        db.delete_column(%r, %r)
        ''' % (
            model._meta.object_name,
            field.name,
            model._meta.db_table,
            field.column,
        )
        
        if models_to_migrate:
            # Now, do the added models
            for model in models_to_migrate:
                table_name = model._meta.db_table
                mock_models = []
                fields = []
                for f in model._meta.local_fields:
                    
                    # Look up the field definition to see how this was created
                    field_definition = generate_field_definition(model, f)
                    
                    # If it's a OneToOneField, and ends in _ptr, just use it
                    if isinstance(f, models.OneToOneField) and f.name.endswith("_ptr"):
                        mock_models.append(create_mock_model(f.rel.to, "        "))
                        field_definition = "models.OneToOneField(%s)" % f.rel.to.__name__
                    
                    # It's probably normal then
                    elif field_definition:
                        
                        if isinstance(f, models.ForeignKey):
                            mock_models.append(create_mock_model(f.rel.to, "        "))
                            field_definition = related_field_definition(f, field_definition)
                    
                    # Oh noes, no defn found
                    else:
                        print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \
                                (model._meta.object_name, f.name)
                        print f, type(f)
                                
                        field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name)
                                                
                    fields.append((f.name, field_definition))
                    
                if mock_models:
                    forwards += '''
        
        # Mock Models
%s
        ''' % "\n".join(mock_models)
        
                forwards += '''
        # Model '%s'
        db.create_table(%r, (
            %s
        ))''' % (
                    model._meta.object_name,
                    table_name,
                    "\n            ".join(["('%s', %s)," % (f[0], f[1]) for f in fields]),
                )

                backwards = ('''db.delete_table('%s')
        ''' % table_name) + backwards
        
                # Now go through local M2Ms and add extra stuff for them
                for m in model._meta.local_many_to_many:
                    # ignore generic relations
                    if isinstance(m, GenericRelation):
                        continue

                    # if the 'through' option is specified, the table will
                    # be created through the normal model creation above.
                    if m.rel.through:
                        continue
                        
                    mock_models = [create_mock_model(model, "        "), create_mock_model(m.rel.to, "        ")]
                    
                    forwards += '''
        # Mock Models
%s
        
        # M2M field '%s.%s'
        db.create_table('%s', (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('%s', models.ForeignKey(%s, null=False)),
            ('%s', models.ForeignKey(%s, null=False))
        )) ''' % (
                        "\n".join(mock_models),
                        model._meta.object_name,
                        m.name,
                        m.m2m_db_table(),
                        m.m2m_column_name()[:-3], # strip off the '_id' at the end
                        model._meta.object_name,
                        m.m2m_reverse_name()[:-3], # strip off the '_id' at the ned
                        m.rel.to._meta.object_name
                )
                
                    backwards = '''db.delete_table('%s')
        ''' % m.m2m_db_table() + backwards
                
                if model._meta.unique_together:
                    ut = model._meta.unique_together
                    if not isinstance(ut[0], (list, tuple)):
                        ut = (ut,)
                        
                    for unique in ut:
                        columns = ["'%s'" % model._meta.get_field(f).column for f in unique]
                        
                        forwards += '''
        db.create_index('%s', [%s], unique=True, db_tablespace='%s')
        ''' %   (
                        table_name,
                        ','.join(columns),
                        model._meta.db_tablespace
                )
                
                
            forwards += '''
        
        db.send_create_signal('%s', ['%s'])''' % (
                app, 
                "','".join(model._meta.object_name for model in models_to_migrate)
                )
        
        # Try sniffing the encoding using PEP 0263's method
        encoding = None
        first_two_lines = inspect.getsourcelines(app_models_module)[0][:2]
        for line in first_two_lines:
            if re.search("coding[:=]\s*([-\w.]+)", line):
                encoding = line
        
        if (not forwards) and (not backwards):
            forwards = '"Write your forwards migration here"'
            backwards = '"Write your backwards migration here"'
        fp = open(os.path.join(migrations_dir, new_filename), "w")
        fp.write("""%s
from south.db import db
from django.db import models
from %s.models import *

class Migration:
    
    def forwards(self):
        %s
    
    def backwards(self):
        %s
""" % (encoding or "", '.'.join(app_module_path), forwards, backwards))
        fp.close()
        print "Created %s." % new_filename
Ejemplo n.º 22
0
    def handle(self, app=None, name="", model_list=None, initial=False, **options):
        
        # If model_list is None, then it's an empty list
        model_list = model_list or []
        
        # make sure --model and --all aren't both specified
        if initial and model_list:
            print "You cannot use --initial and other options together"
            return
            
        # specify the default name 'initial' if a name wasn't specified and we're
        # doing a migration for an entire app
        if not name and initial:
            name = 'initial'
            
        # if not name, there's an error
        if not name:
            print "You must name this migration"
            return
        
        if not app:
            print "Please provide an app in which to create the migration."
            return
            
        # See if the app exists
        app_models_module = models.get_app(app)
        if not app_models_module:
            print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app
            return
            
        # Determine what models should be included in this migration.
        models_to_migrate = []
        if initial:
            models_to_migrate = models.get_models(app_models_module)
            if not models_to_migrate:
                print "No models found in app '%s'" % (app)
                return
        else:
            for model_name in model_list:
                model = models.get_model(app, model_name)
                if not model:
                    print "Couldn't find model '%s' in app '%s'" % (model_name, app)
                    return
                    
                models_to_migrate.append(model)
                
        # Make the migrations directory if it's not there
        app_module_path = app_models_module.__name__.split('.')[0:-1]
        try:
            app_module = __import__('.'.join(app_module_path), {}, {}, [''])
        except ImportError:
            print "Couldn't find path to App '%s'." % app
            return
            
        migrations_dir = os.path.join(
            os.path.dirname(app_module.__file__),
            "migrations",
        )
        if not os.path.isdir(migrations_dir):
            print "Creating migrations directory at '%s'..." % migrations_dir
            os.mkdir(migrations_dir)
            # Touch the init py file
            open(os.path.join(migrations_dir, "__init__.py"), "w").close()
        # See what filename is next in line. We assume they use numbers.
        migrations = migration.get_migration_names(migration.get_app(app))
        highest_number = 0
        for migration_name in migrations:
            try:
                number = int(migration_name.split("_")[0])
                highest_number = max(highest_number, number)
            except ValueError:
                pass
        # Make the new filename
        new_filename = "%04i%s_%s.py" % (
            highest_number + 1,
            "".join([random.choice(string.letters.lower()) for i in range(0)]), # Possible random stuff insertion
            name,
        )
        # If there's a model, make the migration skeleton, else leave it bare
        forwards, backwards = '', ''
        if models_to_migrate:
            for model in models_to_migrate:
                table_name = model._meta.db_table
                mock_models = []
                fields = []
                for f in model._meta.local_fields:
                    # look up the field definition to see how this was created
                    field_definition = generate_field_definition(model, f)
                    if field_definition:
                        
                        if isinstance(f, models.ForeignKey):
                            mock_models.append(create_mock_model(f.rel.to))
                            field_definition = related_field_definition(f, field_definition)
                            
                    else:
                        print "Warning: Could not generate field definition for %s.%s, manual editing of migration required." % \
                                (model._meta.object_name, f.name)
                                
                        field_definition = '<<< REPLACE THIS WITH FIELD DEFINITION FOR %s.%s >>>' % (model._meta.object_name, f.name)
                                                
                    fields.append((f.name, field_definition))
                    
                if mock_models:
                    forwards += '''
        
        # Mock Models
        %s
        ''' % "\n        ".join(mock_models)
        
                forwards += '''
        # Model '%s'
        db.create_table('%s', (
            %s
        ))''' % (
                    model._meta.object_name,
                    table_name,
                    "\n            ".join(["('%s', %s)," % (f[0], f[1]) for f in fields]),
                )

                backwards = ('''db.delete_table('%s')
        ''' % table_name) + backwards
        
                # Now go through local M2Ms and add extra stuff for them
                for m in model._meta.local_many_to_many:
                    # ignore generic relations
                    if isinstance(m, GenericRelation):
                        continue

                    # if the 'through' option is specified, the table will
                    # be created through the normal model creation above.
                    if m.rel.through:
                        continue
                        
                    mock_models = [create_mock_model(model), create_mock_model(m.rel.to)]
                    
                    forwards += '''
        # Mock Models
        %s
        
        # M2M field '%s.%s'
        db.create_table('%s', (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('%s', models.ForeignKey(%s, null=False)),
            ('%s', models.ForeignKey(%s, null=False))
        )) ''' % (
                        "\n        ".join(mock_models),
                        model._meta.object_name,
                        m.name,
                        m.m2m_db_table(),
                        m.m2m_column_name()[:-3], # strip off the '_id' at the end
                        model._meta.object_name,
                        m.m2m_reverse_name()[:-3], # strip off the '_id' at the ned
                        m.rel.to._meta.object_name
                )
                
                    backwards = '''db.delete_table('%s')
        ''' % m.m2m_db_table() + backwards
                
                if model._meta.unique_together:
                    ut = model._meta.unique_together
                    if not isinstance(ut[0], (list, tuple)):
                        ut = (ut,)
                        
                    for unique in ut:
                        columns = ["'%s'" % model._meta.get_field(f).column for f in unique]
                        
                        forwards += '''
        db.create_index('%s', [%s], unique=True, db_tablespace='%s')
        ''' %   (
                        table_name,
                        ','.join(columns),
                        model._meta.db_tablespace
                )
                
                
            forwards += '''
        
        db.send_create_signal('%s', ['%s'])''' % (
                app, 
                "','".join(model._meta.object_name for model in models_to_migrate)
                )
        
        else:
            forwards = '"Write your forwards migration here"'
            backwards = '"Write your backwards migration here"'
        fp = open(os.path.join(migrations_dir, new_filename), "w")
        fp.write("""
from south.db import db
from %s.models import *

class Migration:
    
    def forwards(self):
        %s
    
    def backwards(self):
        %s
""" % ('.'.join(app_module_path), forwards, backwards))
        fp.close()
        print "Created %s." % new_filename
Ejemplo n.º 23
0
    def handle(self,
               app=None,
               name="",
               added_model_list=None,
               added_field_list=None,
               initial=False,
               freeze_list=None,
               auto=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 []

        # Make sure options are compatable
        if initial and (added_model_list or added_field_list or auto):
            print "You cannot use --initial and other options together"
            return
        if auto and (added_model_list or added_field_list or initial):
            print "You cannot use --auto and other options together"
            return

        # specify the default name 'initial' if a name wasn't specified and we're
        # doing a migration for an entire app
        if not name and initial:
            name = 'initial'

        # if not name, there's an error
        if not name:
            print "You must name this migration"
            return

        if not app:
            print "Please provide an app in which to create the migration."
            return

        # Make sure the app is short form
        app = app.split(".")[-1]

        # See if the app exists
        app_models_module = models.get_app(app)
        if not app_models_module:
            print "App '%s' doesn't seem to exist, isn't in INSTALLED_APPS, or has no models." % app
            return

        # If they've set SOUTH_AUTO_FREEZE_APP = True (or not set it - defaults to True)
        if not hasattr(
                settings,
                'SOUTH_AUTO_FREEZE_APP') or settings.SOUTH_AUTO_FREEZE_APP:
            if freeze_list and app not in freeze_list:
                freeze_list += [app]
            else:
                freeze_list = [app]

        # Make the migrations directory if it's not there
        app_module_path = app_models_module.__name__.split('.')[0:-1]
        try:
            app_module = __import__('.'.join(app_module_path), {}, {}, [''])
        except ImportError:
            print "Couldn't find path to App '%s'." % app
            return

        migrations_dir = os.path.join(
            os.path.dirname(app_module.__file__),
            "migrations",
        )

        # Make sure there's a migrations directory and __init__.py
        if not os.path.isdir(migrations_dir):
            print "Creating migrations directory at '%s'..." % migrations_dir
            os.mkdir(migrations_dir)
        init_path = os.path.join(migrations_dir, "__init__.py")
        if not os.path.isfile(init_path):
            # Touch the init py file
            print "Creating __init__.py in '%s'..." % migrations_dir
            open(init_path, "w").close()

        # See what filename is next in line. We assume they use numbers.
        migrations = migration.get_migration_names(migration.get_app(app))
        highest_number = 0
        for migration_name in migrations:
            try:
                number = int(migration_name.split("_")[0])
                highest_number = max(highest_number, number)
            except ValueError:
                pass

        # Make the new filename
        new_filename = "%04i%s_%s.py" % (
            highest_number + 1,
            "".join([random.choice(string.letters.lower())
                     for i in range(0)]),  # Possible random stuff insertion
            name,
        )

        # Find the source file encoding, using PEP 0263's method
        encoding = None
        first_two_lines = inspect.getsourcelines(app_models_module)[0][:2]
        for line in first_two_lines:
            if re.search("coding[:=]\s*([-\w.]+)", line):
                encoding = line

        # Initialise forwards, backwards and models to blank things
        forwards = ""
        backwards = ""
        frozen_models = {}  # Frozen models, used by the Fake ORM
        stub_models = {
        }  # Frozen models, but only enough for relation ends (old mock models)
        complete_apps = set(
        )  # Apps that are completely frozen - useable for diffing.

        # Sets of actions
        added_models = set()
        deleted_models = []  # Special: contains instances _not_ string keys
        added_fields = set()
        deleted_fields = []  # Similar to deleted_models
        changed_fields = []  # (mkey, fname, old_def, new_def)
        added_uniques = set()  # (mkey, field_names)
        deleted_uniques = set()  # (mkey, field_names)

        # --initial means 'add all models in this app'.
        if initial:
            for model in models.get_models(app_models_module):
                added_models.add("%s.%s" % (app, model._meta.object_name))

        # Added models might be 'model' or 'app.model'.
        for modelname in added_model_list:
            if "." in modelname:
                added_models.add(modelname)
            else:
                added_models.add("%s.%s" % (app, modelname))

        # Fields need translating from "model.field" to (app.model, field)
        for fielddef in added_field_list:
            try:
                modelname, fieldname = fielddef.split(".", 1)
            except ValueError:
                print "The field specification '%s' is not in modelname.fieldname format." % fielddef
            else:
                added_fields.add(("%s.%s" % (app, modelname), fieldname))

        # Add anything frozen (I almost called the dict Iceland...)
        if freeze_list:
            for item in freeze_list:
                if "." in item:
                    # It's a specific model
                    app_name, model_name = item.split(".", 1)
                    model = models.get_model(app_name, model_name)
                    if model is None:
                        print "Cannot find the model '%s' to freeze it." % item
                        return
                    frozen_models[model] = None
                else:
                    # Get everything in an app!
                    frozen_models.update(
                        dict([(x, None)
                              for x in models.get_models(models.get_app(item))
                              ]))
                    complete_apps.add(item.split(".")[-1])
            # For every model in the freeze list, add in dependency stubs
            for model in frozen_models:
                stub_models.update(model_dependencies(model))

        ### Automatic Detection ###
        if auto:
            # Get the last migration for this app
            last_models = None
            app_module = migration.get_app(app)
            if app_module is None:
                print "You cannot use automatic detection on the first migration of an app. Try --initial instead."
            else:
                migrations = list(migration.get_migration_classes(app_module))
                if not migrations:
                    print "You cannot use automatic detection on the first migration of an app. Try --initial instead."
                else:
                    if hasattr(migrations[-1], "complete_apps") and \
                       app in migrations[-1].complete_apps:
                        last_models = migrations[-1].models
                        last_orm = migrations[-1].orm
                    else:
                        print "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." % app

            # Right, did we manage to get the last set of models?
            if last_models is None:
                return

            # Good! Get new things.
            new = dict([(model_key(model), prep_for_freeze(model))
                        for model in models.get_models(app_models_module)])
            # And filter other apps out of the old
            old = dict([(key, fields) for key, fields in last_models.items()
                        if key.split(".", 1)[0] == app])
            am, dm, cm, af, df, cf = models_diff(old, new)

            # For models that were there before and after, do a meta diff
            was_meta_change = False
            for mkey in cm:
                au, du = meta_diff(old[mkey].get("Meta", {}),
                                   new[mkey].get("Meta", {}))
                for entry in au:
                    added_uniques.add((mkey, entry))
                    was_meta_change = True
                for entry in du:
                    deleted_uniques.add((mkey, entry))
                    was_meta_change = True

            if not (am or dm or af or df or cf or was_meta_change):
                print "Nothing seems to have changed."
                return

            # Add items to the todo lists
            added_models.update(am)
            added_fields.update(af)
            changed_fields.extend(cf)

            # Deleted models are from the past, and so we use instances instead.
            for mkey in dm:
                model = last_orm[mkey]
                fields = last_models[mkey]
                if "Meta" in fields:
                    del fields['Meta']
                deleted_models.append((model, fields, last_models))

            # For deleted fields, we tag the instance on the end too
            for mkey, fname in df:
                deleted_fields.append((
                    mkey,
                    fname,
                    last_orm[mkey]._meta.get_field_by_name(fname)[0],
                    last_models[mkey][fname],
                    last_models,
                ))

        ### Added model ###
        for mkey in added_models:

            print " + Added model '%s'" % (mkey, )

            model = model_unkey(mkey)

            # Add the model's dependencies to the stubs
            stub_models.update(model_dependencies(model))
            # Get the field definitions
            fields = modelsparser.get_model_fields(model)
            # Turn the (class, args, kwargs) format into a string
            fields = triples_to_defs(app, model, fields)
            # Make the code
            forwards += CREATE_TABLE_SNIPPET % (
                model._meta.object_name,
                model._meta.db_table,
                "\n            ".join([
                    "('%s', %s)," % (fname, fdef)
                    for fname, fdef in fields.items()
                ]),
                model._meta.app_label,
                model._meta.object_name,
            )
            # And the backwards code
            backwards += DELETE_TABLE_SNIPPET % (model._meta.object_name,
                                                 model._meta.db_table)
            # Now add M2M fields to be done
            for field in model._meta.local_many_to_many:
                added_fields.add((mkey, field.attname))
            # And unique_togethers to be added
            for ut in model._meta.unique_together:
                added_uniques.add((mkey, tuple(ut)))

        ### Added fields ###
        for mkey, field_name in added_fields:

            print " + Added field '%s.%s'" % (mkey, field_name)

            # Get the model
            model = model_unkey(mkey)
            # Get the field
            try:
                field = model._meta.get_field(field_name)
            except FieldDoesNotExist:
                print "Model '%s' doesn't have a field '%s'" % (mkey,
                                                                field_name)
                return

            # ManyToMany fields need special attention.
            if isinstance(field, models.ManyToManyField):
                if not field.rel.through:  # Bug #120
                    # Add a stub model for each side
                    stub_models[model] = None
                    stub_models[field.rel.to] = None
                    # And a field defn, that's actually a table creation
                    forwards += CREATE_M2MFIELD_SNIPPET % (
                        model._meta.object_name,
                        field.name,
                        field.m2m_db_table(),
                        field.m2m_column_name()
                        [:-3],  # strip off the '_id' at the end
                        model._meta.object_name,
                        field.m2m_reverse_name()
                        [:-3],  # strip off the '_id' at the ned
                        field.rel.to._meta.object_name)
                    backwards += DELETE_M2MFIELD_SNIPPET % (
                        model._meta.object_name, field.name,
                        field.m2m_db_table())
                continue

            # GenericRelations need ignoring
            if isinstance(field, GenericRelation):
                continue

            # Add any dependencies
            stub_models.update(field_dependencies(field))

            # Work out the definition
            triple = remove_useless_attributes(
                modelsparser.get_model_fields(model)[field_name])

            field_definition = make_field_constructor(app, field, triple)

            forwards += CREATE_FIELD_SNIPPET % (
                model._meta.object_name,
                field.name,
                model._meta.db_table,
                field.name,
                field_definition,
            )
            backwards += DELETE_FIELD_SNIPPET % (
                model._meta.object_name,
                field.name,
                model._meta.db_table,
                field.column,
            )

        ### Deleted fields ###
        for mkey, field_name, field, triple, last_models in deleted_fields:

            print " - Deleted field '%s.%s'" % (mkey, field_name)

            # Get the model
            model = model_unkey(mkey)

            # ManyToMany fields need special attention.
            if isinstance(field, models.ManyToManyField):
                # Add a stub model for each side, if they're not already there
                # (if we just added old versions, we might override new ones)
                if model not in stub_models:
                    stub_models[model] = last_models
                if field.rel.to not in last_models:
                    stub_models[field.rel.to] = last_models
                # And a field defn, that's actually a table deletion
                forwards += DELETE_M2MFIELD_SNIPPET % (
                    model._meta.object_name, field.name, field.m2m_db_table())
                backwards += CREATE_M2MFIELD_SNIPPET % (
                    model._meta.object_name,
                    field.name,
                    field.m2m_db_table(),
                    field.m2m_column_name()
                    [:-3],  # strip off the '_id' at the end
                    model._meta.object_name,
                    field.m2m_reverse_name()
                    [:-3],  # strip off the '_id' at the ned
                    field.rel.to._meta.object_name)
                continue

            # Add any dependencies
            deps = field_dependencies(field, last_models)
            deps.update(stub_models)
            stub_models = deps

            # Work out the definition
            triple = remove_useless_attributes(triple)
            field_definition = make_field_constructor(app, field, triple)

            forwards += DELETE_FIELD_SNIPPET % (
                model._meta.object_name,
                field.name,
                model._meta.db_table,
                field.column,
            )
            backwards += CREATE_FIELD_SNIPPET % (
                model._meta.object_name,
                field.name,
                model._meta.db_table,
                field.name,
                field_definition,
            )

        ### Deleted model ###
        for model, fields, last_models in deleted_models:

            print " - Deleted model '%s.%s'" % (model._meta.app_label,
                                                model._meta.object_name)

            # Add the model's dependencies to the stubs
            deps = model_dependencies(model, last_models)
            deps.update(stub_models)
            stub_models = deps

            # Turn the (class, args, kwargs) format into a string
            fields = triples_to_defs(app, model, fields)

            # Make the code
            forwards += DELETE_TABLE_SNIPPET % (model._meta.object_name,
                                                model._meta.db_table)
            # And the backwards code
            backwards += CREATE_TABLE_SNIPPET % (
                model._meta.object_name,
                model._meta.db_table,
                "\n            ".join([
                    "('%s', %s)," % (fname, fdef)
                    for fname, fdef in fields.items()
                ]),
                model._meta.app_label,
                model._meta.object_name,
            )

        ### Changed fields ###
        for mkey, field_name, old_triple, new_triple in changed_fields:

            model = model_unkey(mkey)
            old_def = triples_to_defs(app, model, {
                field_name: old_triple,
            })[field_name]
            new_def = triples_to_defs(app, model, {
                field_name: new_triple,
            })[field_name]

            # We need to create the field, to see if it needs _id, or if it's an M2M
            field = model._meta.get_field_by_name(field_name)[0]

            if hasattr(field, "m2m_db_table"):
                # See if anything has ACTUALLY changed
                if old_triple[1] != new_triple[1]:
                    print " ! Detected change to the target model of M2M field '%s.%s'. South can't handle this; leaving this change out." % (
                        mkey, field_name)
                continue

            print " ~ Changed field '%s.%s'." % (mkey, field_name)

            forwards += CHANGE_FIELD_SNIPPET % (
                model._meta.object_name,
                field_name,
                model._meta.db_table,
                field.get_attname(),
                new_def,
            )

            backwards += CHANGE_FIELD_SNIPPET % (
                model._meta.object_name,
                field_name,
                model._meta.db_table,
                field.get_attname(),
                old_def,
            )

        ### Added unique_togethers ###
        for mkey, ut in added_uniques:

            model = model_unkey(mkey)
            print " + Added unique_together for [%s] on %s." % (
                ", ".join(ut), model._meta.object_name)

            cols = [get_field_column(model, f) for f in ut]

            forwards += CREATE_UNIQUE_SNIPPET % (
                ", ".join(ut),
                model._meta.object_name,
                model._meta.db_table,
                cols,
            )

            backwards += DELETE_UNIQUE_SNIPPET % (
                ", ".join(ut),
                model._meta.object_name,
                model._meta.db_table,
                cols,
            )

        ### Deleted unique_togethers ###
        for mkey, ut in deleted_uniques:

            model = model_unkey(mkey)
            print " - Deleted unique_together for [%s] on %s." % (
                ", ".join(ut), model._meta.object_name)

            forwards += DELETE_UNIQUE_SNIPPET % (
                ", ".join(ut),
                model._meta.object_name,
                model._meta.db_table,
                ut,
            )

            backwards += CREATE_UNIQUE_SNIPPET % (
                ", ".join(ut),
                model._meta.object_name,
                model._meta.db_table,
                ut,
            )

        # Default values for forwards/backwards
        if (not forwards) and (not backwards):
            forwards = '"Write your forwards migration here"'
            backwards = '"Write your backwards migration here"'

        all_models = {}

        # Fill out frozen model definitions
        for model, last_models in frozen_models.items():
            all_models[model_key(model)] = prep_for_freeze(model, last_models)

        # Fill out stub model definitions
        for model, last_models in stub_models.items():
            key = model_key(model)
            if key in all_models:
                continue  # We'd rather use full models than stubs.
            all_models[key] = prep_for_stub(model, last_models)

        # Do some model cleanup, and warnings
        for modelname, model in all_models.items():
            for fieldname, fielddef in model.items():
                # Remove empty-after-cleaning Metas.
                if fieldname == "Meta" and not fielddef:
                    del model['Meta']
                # Warn about undefined fields
                elif fielddef is None:
                    print "WARNING: Cannot get definition for '%s' on '%s'. Please edit the migration manually." % (
                        fieldname,
                        modelname,
                    )
                    model[fieldname] = FIELD_NEEDS_DEF_SNIPPET

        # Write the migration file
        fp = open(os.path.join(migrations_dir, new_filename), "w")
        fp.write(MIGRATION_SNIPPET %
                 (encoding or "", '.'.join(app_module_path), forwards,
                  backwards, pprint_frozen_models(all_models),
                  complete_apps and "complete_apps = [%s]" %
                  (", ".join(map(repr, complete_apps))) or ""))
        fp.close()
        print "Created %s." % new_filename