def get_indexes(self): # TODO: не удаляются индексы у внешних ключей и добавочные # _like-индексы к ним. Например у House migration = Migrations('fias', force_creation=True) # получим текущее состояние базы new_defs = dict( (k, v) for k, v in freezer.freeze_apps([migration.app_label()]).items() if k.split(".")[0] == migration.app_label()) # скопируем и удалим все индексы old_defs = copy.deepcopy(new_defs) for table in old_defs.values(): for key, value in table.items(): # удалим 'index_together' if key == 'Meta' and 'index_together' in value: del value['index_together'] if isinstance(value, tuple): # удалим 'unique' if 'unique' in value[2]: value[2]['unique'] = False # удалим 'db_index' if 'db_index' in value[2]: value[2]['db_index'] = False class InitialMigration(SchemaMigration): def forwards(self, orm): pass def backwards(self, orm): pass models = old_defs complete_apps = ['fias'] initial_orm = FakeORM(InitialMigration, "fias") # получим все изменения, т.е. список индексов change_source = changes.AutoChanges( migrations=migration, old_defs=old_defs, old_orm=initial_orm, new_defs=new_defs, ) for action_name, params in change_source.get_changes(): try: action_class = getattr(actions, action_name) except AttributeError: raise ValueError("Invalid action name from source: %s" % action_name) else: if issubclass(action_class, AddUnique): yield action_class, params
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): # Any supposed lists that are None become empty lists added_model_list = added_model_list or [] added_field_list = added_field_list or [] added_index_list = added_index_list or [] freeze_list = freeze_list or [] # --stdout means name = - if stdout: name = "-" # Only allow valid names if re.search('[^_\w]', name) and name != "-": self.error( "Migration names should contain only alphanumeric characters and underscores." ) # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): self.error( "You cannot use --initial and other options together\n" + self.usage_str) if auto and (added_model_list or added_field_list or initial): self.error("You cannot use --auto and other options together\n" + self.usage_str) if not app: self.error("You must provide an app to create a migration for.\n" + self.usage_str) # Get the Migrations for this app (creating the migrations dir if needed) migrations = Migrations(app, force_creation=True, verbose_creation=verbosity > 0) # What actions do we need to do? if auto: # Get the old migration try: last_migration = migrations[-1] except IndexError: self.error( "You cannot use --auto on an app with no migrations. Try --initial." ) # Make sure it has stored models if migrations.app_label() not in getattr( last_migration.migration_class(), "complete_apps", []): self.error( "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." % migrations.app_label()) # Alright, construct two model dicts to run the differ on. old_defs = dict( (k, v) for k, v in last_migration.migration_class().models.items() if k.split(".")[0] == migrations.app_label()) new_defs = dict((k, v) for k, v in freezer.freeze_apps( [migrations.app_label()]).items() if k.split(".")[0] == migrations.app_label()) change_source = changes.AutoChanges( migrations=migrations, old_defs=old_defs, old_orm=last_migration.orm(), new_defs=new_defs, ) elif initial: # Do an initial migration change_source = changes.InitialChanges(migrations) else: # Read the commands manually off of the arguments if (added_model_list or added_field_list or added_index_list): change_source = changes.ManualChanges( migrations, added_model_list, added_field_list, added_index_list, ) elif empty: change_source = None else: print >> sys.stderr, "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index." sys.exit(1) # if not name, there's an error if not name: if change_source: name = change_source.suggest_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 = [] if change_source: for action_name, params in change_source.get_changes(): # Run the correct Action class try: action_class = getattr(actions, action_name) except AttributeError: raise ValueError("Invalid action name from source: %s" % action_name) else: action = action_class(**params) action.add_forwards(forwards_actions) action.add_backwards(backwards_actions) print >> sys.stderr, action.console_line() # Nowt happen? That's not good for --auto. if auto and not forwards_actions: self.error("Nothing seems to have changed.") # Work out which apps to freeze apps_to_freeze = self.calc_frozen_apps(migrations, freeze_list) # So, what's in this file, then? file_contents = MIGRATION_TEMPLATE % { "forwards": "\n".join(forwards_actions or ["pass"]), "backwards": "\n".join(backwards_actions or ["pass"]), "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 "" } # - 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.migrations_dir(), new_filename), "w") fp.write(file_contents) fp.close() if empty: print >> sys.stderr, "Created %s. You must now edit this migration and add the code for each direction." % new_filename else: print >> sys.stderr, "Created %s. You can now apply this migration with: ./manage.py migrate %s" % ( new_filename, app)
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 [] added_field_list = added_field_list or [] added_index_list = added_index_list or [] freeze_list = freeze_list or [] # --stdout means name = - if stdout: name = "-" # Only allow valid names if re.search('[^_\w]', name) and name != "-": self.error( "Migration names should contain only alphanumeric characters and underscores." ) # Make sure options are compatable if initial and (added_model_list or added_field_list or auto): self.error( "You cannot use --initial and other options together\n" + self.usage_str) if auto and (added_model_list or added_field_list or initial): self.error("You cannot use --auto and other options together\n" + self.usage_str) if not app: self.error("You must provide an app to create a migration for.\n" + self.usage_str) # 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 # Get the Migrations for this app (creating the migrations dir if needed) migrations = Migrations(app, force_creation=True, verbose_creation=int(verbosity) > 0) # What actions do we need to do? if auto: # Get the old migration try: 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 if migrations.app_label() not in getattr( last_migration.migration_class(), "complete_apps", []): self.error( "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." % migrations.app_label()) # Alright, construct two model dicts to run the differ on. old_defs = dict( (k, v) for k, v in last_migration.migration_class().models.items() if k.split(".")[0] == migrations.app_label()) new_defs = dict((k, v) for k, v in freezer.freeze_apps( [migrations.app_label()]).items() if k.split(".")[0] == migrations.app_label()) change_source = changes.AutoChanges( migrations=migrations, old_defs=old_defs, old_orm=last_migration.orm(), new_defs=new_defs, ) elif initial: # Do an initial migration change_source = changes.InitialChanges(migrations) else: # Read the commands manually off of the arguments if (added_model_list or added_field_list or added_index_list): change_source = changes.ManualChanges( migrations, added_model_list, added_field_list, added_index_list, ) elif empty: change_source = None else: print( "You have not passed any of --initial, --auto, --empty, --add-model, --add-field or --add-index.", file=sys.stderr) 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) # Get the actions, and then insert them into the actions lists forwards_actions = [] backwards_actions = [] if change_source: for action_name, params in change_source.get_changes(): # Run the correct Action class try: action_class = getattr(actions, action_name) except AttributeError: raise ValueError("Invalid action name from source: %s" % action_name) else: action = action_class(**params) action.add_forwards(forwards_actions) action.add_backwards(backwards_actions) print(action.console_line(), file=sys.stderr) # Nowt happen? That's not good for --auto. if auto and not forwards_actions: self.error("Nothing seems to have changed.") # Work out which apps to freeze apps_to_freeze = self.calc_frozen_apps(migrations, freeze_list) # So, what's in this file, then? file_contents = self.get_migration_template() % { "forwards": "\n".join(forwards_actions or [" pass"]), "backwards": "\n".join(backwards_actions or [" pass"]), "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( "Migration to be updated, %s, is already applied, rolling it back now..." % last_migration.name(), file=sys.stderr) 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) # Write the migration file if the name isn't - else: 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( "%s %s. You must now edit this migration and add the code for each direction." % (verb, new_filename), file=sys.stderr) else: print( "%s %s. You can now apply this migration with: ./manage.py migrate %s" % (verb, new_filename, app), file=sys.stderr)
def test_south_migrations(self): from django.core.exceptions import ImproperlyConfigured from django.conf import settings from django.db import models 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 apps = [ app for app in settings.INSTALLED_APPS if app.startswith('wagtail.') ] failing_apps = [] for app_name in apps: app = app_name.split('.')[-1] try: models.get_app(app) except ImproperlyConfigured: # This module fails to load, probably because it has no # models.py. Ignore it and move on continue try: migrations = Migrations(app, force_creation=False, verbose_creation=False) last_migration = migrations[-1] except (NoMigrations, IndexError): # No migrations for this app, probably doesnt have models continue if migrations.app_label() not in getattr( last_migration.migration_class(), "complete_apps", []): self.fail( "Automatic migrations checking failed, 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." % migrations.app_label()) # Alright, construct two model dicts to run the differ on. old_defs = dict( (k, v) for k, v in last_migration.migration_class().models.items() if k.split(".")[0] == migrations.app_label()) new_defs = dict((k, v) for k, v in freezer.freeze_apps( [migrations.app_label()]).items() if k.split(".")[0] == migrations.app_label()) change_source = changes.AutoChanges( migrations=migrations, old_defs=old_defs, old_orm=last_migration.orm(), new_defs=new_defs, ) name = 'test' # Get the actions, and then insert them into the actions lists if list(change_source.get_changes()): failing_apps.append(app_name) if failing_apps: self.fail( 'Model changes with no South migration detected in apps: %s' % (', '.join(failing_apps)))