--- a/ProjectDjango/Project.py Sat Dec 17 19:07:02 2016 +0100 +++ b/ProjectDjango/Project.py Mon Dec 19 18:44:07 2016 +0100 @@ -657,7 +657,6 @@ """ Private method to define the migration actions. """ - # TODO: showmigrations self.showMigrationsAct = E5Action( self.tr('Show Migrations'), self.tr('&Show Migrations'), @@ -688,9 +687,68 @@ self.showMigrationsPlanAct.triggered.connect(self.__showMigrationsPlan) self.actions.append(self.showMigrationsPlanAct) - # TODO: makemigrations - # TODO: migrate + self.migrateAllAct = E5Action( + self.tr('Apply All Migrations'), + self.tr('&Apply All Migrations'), + 0, 0, + self, 'django_migration_apply_all') + self.migrateAllAct.setStatusTip(self.tr( + 'Apply all available migrations')) + self.migrateAllAct.setWhatsThis(self.tr( + """<b>Apply All Migrations</b>""" + """<p>This applies all migrations of the Django project.</p>""" + )) + self.migrateAllAct.triggered.connect(self.__applyAllMigrations) + self.actions.append(self.migrateAllAct) + + self.migrateSelectedAct = E5Action( + self.tr('Apply Selected Migrations'), + self.tr('Apply Selected Migrations'), + 0, 0, + self, 'django_migration_apply_selected') + self.migrateSelectedAct.setStatusTip(self.tr( + 'Apply selected migrations')) + self.migrateSelectedAct.setWhatsThis(self.tr( + """<b>Apply Selected Migrations</b>""" + """<p>This applies selected migrations of the Django""" + """ project.</p>""" + )) + self.migrateSelectedAct.triggered.connect( + self.__applySelectedMigrations) + self.actions.append(self.migrateSelectedAct) + + self.unmigrateAct = E5Action( + self.tr('Unapply Migrations'), + self.tr('&Unapply Migrations'), + 0, 0, + self, 'django_migration_unapply') + self.unmigrateAct.setStatusTip(self.tr( + 'Unapply all migrations for an app')) + self.unmigrateAct.setWhatsThis(self.tr( + """<b>Unapply Migrations</b>""" + """<p>This unapplies all migrations for an app of the Django""" + """ project.</p>""" + )) + self.unmigrateAct.triggered.connect(self.__unapplyMigrations) + self.actions.append(self.unmigrateAct) + + self.makeMigrationsAct = E5Action( + self.tr('Make Migrations'), + self.tr('&Make Migrations'), + 0, 0, + self, 'django_migration_make') + self.makeMigrationsAct.setStatusTip(self.tr( + 'Generate migrations for the project')) + self.makeMigrationsAct.setWhatsThis(self.tr( + """<b>Make Migrations</b>""" + """<p>This generates migrations for the Django project.</p>""" + )) + self.makeMigrationsAct.triggered.connect(self.__makeMigrations) + self.actions.append(self.makeMigrationsAct) + # TODO: squashmigrations + + # TODO: sqlmigrate def initMenu(self): """ @@ -797,6 +855,12 @@ menu.addAction(self.showMigrationsAct) menu.addAction(self.showMigrationsPlanAct) + menu.addSeparator() + menu.addAction(self.migrateAllAct) + menu.addAction(self.migrateSelectedAct) + menu.addAction(self.unmigrateAct) + menu.addSeparator() + menu.addAction(self.makeMigrationsAct) self.__menus["migrations"] = menu @@ -2082,7 +2146,7 @@ from .DjangoMigrationsListDialog import DjangoMigrationsListDialog self.__migrationsListDialog = DjangoMigrationsListDialog( - DjangoMigrationsListDialog.MigrationsListMode, self.__ui) + DjangoMigrationsListDialog.MigrationsListMode, self, self.__ui) self.__migrationsListDialog.show() self.__migrationsListDialog.start(self.__getPythonExecutable(), path) @@ -2097,10 +2161,182 @@ from .DjangoMigrationsListDialog import DjangoMigrationsListDialog self.__migrationsPlanDialog = DjangoMigrationsListDialog( - DjangoMigrationsListDialog.MigrationsPlanMode, self.__ui) + DjangoMigrationsListDialog.MigrationsPlanMode, self, self.__ui) self.__migrationsPlanDialog.show() self.__migrationsPlanDialog.start(self.__getPythonExecutable(), path) + def __applyAllMigrations(self): + """ + Public slot to apply all migrations. + """ + self.applyMigrations() + + def __applySelectedMigrations(self): + """ + Public slot to apply selected migrations of a selected app. + """ + migrations = self.__getMigrations() + if not migrations: + E5MessageBox.information( + None, + self.tr("Apply Selected Migrations"), + self.tr("""No migrations available.""")) + return + + from .DjangoMigrationSelectionDialog import \ + DjangoMigrationSelectionDialog + dlg = DjangoMigrationSelectionDialog(migrations) + if dlg.exec_() == QDialog.Accepted: + app, migration = dlg.getData() + self.applyMigrations(app=app, migration=migration) + + def applyMigrations(self, app=None, migration=None): + """ + Public slot to apply migrations. + + @param app name of an application to apply migrations for + @type str + @param migration name of a migration to update to + @type str + """ + if migration == "zero": + title = self.tr("Unapply Migrations") + else: + title = self.tr("Apply Migrations") + + try: + path = self.__sitePath() + except DjangoNoSiteSelectedException: + return + + args = [] + args.append(self.__getPythonExecutable()) + args.append("manage.py") + args.append("migrate") + args.append("--noinput") + if app: + args.append(app) + if migration: + args.append(migration) + + dia = DjangoDialog(title) + res = dia.startProcess(args, path) + if res: + dia.exec_() + + def __unapplyMigrations(self): + """ + Private slot to un-apply all migrations of an application. + """ + apps = list(sorted(self.__getMigrations().keys())) + if not apps: + E5MessageBox.information( + None, + self.tr("Unapply Migrations"), + self.tr("""No migrations available.""")) + return + + app, ok = QInputDialog.getItem( + None, + self.tr("Unapply Migrations"), + self.tr("Select an application:"), + [""] + apps, + 0, False) + if ok and app: + self.applyMigrations(app=app, migration="zero") + + def __getMigrations(self): + """ + Private method to get the available migrations. + + @return dictionary containing the available migrations + @rtype dict with app name as key (str) and list of tuples of + applied indication (bool) and migration name (str) as value + """ + try: + path = self.__sitePath() + except DjangoNoSiteSelectedException: + return {} + + args = [] + args.append("manage.py") + args.append("showmigrations") + args.append("--list") + + migrations = {} + proc = QProcess() + if path: + proc.setWorkingDirectory(path) + proc.start(self.__getPythonExecutable(), args) + if proc.waitForStarted(): + if proc.waitForFinished(): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), 'replace') + if output: + recentApp = "" + for line in output.splitlines(): + if not line.startswith(" "): + # application name + recentApp = line.strip() + migrations[recentApp] = [] + else: + # migration name + line = line.strip() + applied = line[1] != " " + name = line[3:].strip() + if recentApp: + migrations[recentApp].append((applied, name)) + return migrations + + def __makeMigrations(self): + """ + Private slot to generate migrations for the Django project. + """ + from .DjangoMakeMigrationsDialog import DjangoMakeMigrationsDialog + dlg = DjangoMakeMigrationsDialog(self.getRecentApplications()) + if dlg.exec_() == QDialog.Accepted: + apps, migration, dryRun = dlg.getData() + if apps: + self.setMostRecentApplication(apps) + apps = apps.split() + self.makeMigrations(apps, migration, dryRun) + + def makeMigrations(self, apps, migration=None, dryRun=False): + """ + Public method to generate migrations. + + @param apps list of application names to generate migrations for + @type list of str + @param migration name of the migration to generate + @type str + @param dryRun flag indicating a dry run + @type bool + """ + title = self.tr("Make Migrations") + + try: + path = self.__sitePath() + except DjangoNoSiteSelectedException: + return + + args = [] + args.append(self.__getPythonExecutable()) + args.append("manage.py") + args.append("makemigrations") + args.append("--noinput") + if migration: + args.append("--name") + args.append(migration.replace(" ", "_")) + if dryRun: + args.append("--dry-run") + if apps: + args += apps + + dia = DjangoDialog(title) + res = dia.startProcess(args, path) + if res: + dia.exec_() + ################################################################## ## slots below implement some tool functions ##################################################################