Andrew Godwin avatar Andrew Godwin committed 9ec86af Merge

Merged in miracle2k/south/migration-update2 (pull request #65)

Comments (0)

Files changed (1)

south/management/commands/schemamigration.py

 from django.db import models
 from django.conf import settings
 
-from south.migration import Migrations
+from south.migration import Migrations, migrate_app
+from south.models import MigrationHistory
 from south.exceptions import NoMigrations
 from south.creator import changes, actions, freezer
 from south.management.commands.datamigration import Command as DataCommand
             help='Attempt to automatically detect differences from the last migration.'),
         make_option('--empty', action='store_true', dest='empty', default=False,
             help='Make a blank migration.'),
+        make_option('--update', action='store_true', dest='update', default=False,
+                    help='Update the most recent migration instead of creating a new one. Rollback this migration if it is already applied.'),
     )
     help = "Creates a new template schema migration for the given app"
     usage_str = "Usage: ./manage.py schemamigration appname migrationname [--empty] [--initial] [--auto] [--add-model ModelName] [--add-field ModelName.field_name] [--stdout]"
     
-    def handle(self, app=None, name="", added_model_list=None, added_field_list=None, freeze_list=None, initial=False, auto=False, stdout=False, added_index_list=None, verbosity=1, empty=False, **options):
+    def handle(self, app=None, name="", added_model_list=None, added_field_list=None, freeze_list=None, initial=False, auto=False, stdout=False, added_index_list=None, verbosity=1, empty=False, update=False, **options):
         
         # Any supposed lists that are None become empty lists
         added_model_list = added_model_list or []
         if auto:
             # Get the old migration
             try:
-                last_migration = migrations[-1]
+                last_migration = migrations[-2 if update else -1]
             except IndexError:
                 self.error("You cannot use --auto on an app with no migrations. Try --initial.")
             # Make sure it has stored models
             else:
                 print >>sys.stderr, "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index."
                 sys.exit(1)
+
+        # Validate this so we can access the last migration without worrying
+        if update and not migrations:
+            self.error("You cannot use --update on an app with no migrations.")
         
         # if not name, there's an error
         if not name:
             if change_source:
                 name = change_source.suggest_name()
+            if update:
+                name = re.sub(r'^\d{4}_', '', migrations[-1].name())
             if not name:
                 self.error("You must provide a name for this migration\n" + self.usage_str)
         
-        # See what filename is next in line. We assume they use numbers.
-        new_filename = migrations.next_filename(name)
-        
         # Get the actions, and then insert them into the actions lists
         forwards_actions = []
         backwards_actions = []
             "frozen_models":  freezer.freeze_apps_to_string(apps_to_freeze),
             "complete_apps": apps_to_freeze and "complete_apps = [%s]" % (", ".join(map(repr, apps_to_freeze))) or ""
         }
-        
+
+        # Deal with update mode as late as possible, avoid a rollback as long
+        # as something else can go wrong.
+        if update:
+            last_migration = migrations[-1]
+            if MigrationHistory.objects.filter(applied__isnull=False, app_name=app, migration=last_migration.name()):
+                print >>sys.stderr, "Migration to be updated, %s, is already applied, rolling it back now..." % last_migration.name()
+                migrate_app(migrations, 'current-1', verbosity=verbosity)
+            for ext in ('py', 'pyc'):
+                old_filename = "%s.%s" % (os.path.join(migrations.migrations_dir(), last_migration.filename), ext)
+                if os.path.isfile(old_filename):
+                    os.unlink(old_filename)
+            migrations.remove(last_migration)
+
+        # See what filename is next in line. We assume they use numbers.
+        new_filename = migrations.next_filename(name)
+
         # - is a special name which means 'print to stdout'
         if name == "-":
             print file_contents
             fp = open(os.path.join(migrations.migrations_dir(), new_filename), "w")
             fp.write(file_contents)
             fp.close()
+            verb = 'Updated' if update else 'Created'
             if empty:
-                print >>sys.stderr, "Created %s. You must now edit this migration and add the code for each direction." % new_filename
+                print >>sys.stderr, "%s %s. You must now edit this migration and add the code for each direction." % (verb, new_filename)
             else:
-                print >>sys.stderr, "Created %s. You can now apply this migration with: ./manage.py migrate %s" % (new_filename, app)
+                print >>sys.stderr, "%s %s. You can now apply this migration with: ./manage.py migrate %s" % (verb, new_filename, app)
 
 
 MIGRATION_TEMPLATE = """# -*- coding: utf-8 -*-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.