--- a/ProjectPyramid/Project.py Tue Jun 01 19:37:46 2021 +0200 +++ b/ProjectPyramid/Project.py Sun Jun 06 16:30:37 2021 +0200 @@ -7,12 +7,17 @@ Module implementing the Pyramid project support. """ +import configparser +import contextlib +import glob import os import re -import configparser -import contextlib +import subprocess # secok +import sys -from PyQt6.QtCore import QObject, QFileInfo, QTimer, QUrl, QIODeviceBase +from PyQt6.QtCore import ( + pyqtSlot, QObject, QFileInfo, QTimer, QUrl, QIODeviceBase +) from PyQt6.QtGui import QDesktopServices from PyQt6.QtWidgets import QMenu, QDialog, QInputDialog, QLineEdit from PyQt6.QtCore import QProcess as QProcessPyQt @@ -125,6 +130,8 @@ self.__serverProc = None self.__pyramidVersion = "" + + self.__migrationSummaryDialog = None def initActions(self): """ @@ -184,21 +191,6 @@ self.runServerAct.triggered.connect(self.__runServer) self.actions.append(self.runServerAct) - self.runLoggingServerAct = EricAction( - self.tr('Run Server with Logging'), - self.tr('Run Server with &Logging'), - 0, 0, - self, 'pyramid_run_logging_server') - self.runLoggingServerAct.setStatusTip(self.tr( - 'Starts the Pyramid Web server with logging')) - self.runLoggingServerAct.setWhatsThis(self.tr( - """<b>Run Server with Logging</b>""" - """<p>Starts the Pyramid Web server with logging using""" - """ "pserve --log-file=server.log --reload development.ini".</p>""" - )) - self.runLoggingServerAct.triggered.connect(self.__runLoggingServer) - self.actions.append(self.runLoggingServerAct) - self.runBrowserAct = EricAction( self.tr('Run Web-Browser'), self.tr('Run &Web-Browser'), @@ -229,45 +221,6 @@ self.runPythonShellAct.triggered.connect(self.__runPythonShell) self.actions.append(self.runPythonShellAct) - ############################## - ## setup actions below ## - ############################## - - self.setupDevelopAct = EricAction( - self.tr('Setup Development Environment'), - self.tr('Setup &Development Environment'), - 0, 0, - self, 'pyramid_setup_development') - self.setupDevelopAct.setStatusTip(self.tr( - 'Setup the Pyramid project in development mode')) - self.setupDevelopAct.setWhatsThis(self.tr( - """<b>Setup Development Environment</b>""" - """<p>Setup the Pyramid project in development mode using""" - """ "python setup.py develop".</p>""" - )) - self.setupDevelopAct.triggered.connect(self.__setupDevelop) - self.actions.append(self.setupDevelopAct) - - ############################### - ## database actions below ## - ############################### - - self.initializeDbAct = EricAction( - self.tr('Initialize Database'), - self.tr('Initialize &Database'), - 0, 0, - self, 'pyramid_initialize_database') - self.initializeDbAct.setStatusTip(self.tr( - 'Initializes (or re-initializes) the database of the current' - ' Pyramid project')) - self.initializeDbAct.setWhatsThis(self.tr( - """<b>Initialize Database</b>""" - """<p>Initializes (or re-initializes) the database of the""" - """ current Pyramid project.</p>""" - )) - self.initializeDbAct.triggered.connect(self.__initializeDatabase) - self.actions.append(self.initializeDbAct) - ############################### ## show actions below ## ############################### @@ -372,8 +325,120 @@ self.aboutPyramidAct.triggered.connect(self.__pyramidInfo) self.actions.append(self.aboutPyramidAct) + self.__initDatabaseActions() + self.__setCurrentProject(None) + def __initDatabaseActions(self): + """ + Private method to initialize the database related actions. + """ + self.initializeDbAct = EricAction( + self.tr('Initialize Database'), + self.tr('Initialize &Database'), + 0, 0, + self, 'pyramid_initialize_database') + self.initializeDbAct.setStatusTip(self.tr( + 'Initializes (or re-initializes) the database of the current' + ' Pyramid project')) + self.initializeDbAct.setWhatsThis(self.tr( + """<b>Initialize Database</b>""" + """<p>Initializes (or re-initializes) the database of the""" + """ current Pyramid project.</p>""" + )) + self.initializeDbAct.triggered.connect(self.__initializeDatabase) + self.actions.append(self.initializeDbAct) + + ######################################################### + ## action to create a new database migration + ######################################################### + + self.migrateCreateAct = EricAction( + self.tr('Create Migration'), + self.tr('&Create Migration'), + 0, 0, + self, 'flask_create_migration') + self.migrateCreateAct.setStatusTip(self.tr( + 'Create a new migration for the current database')) + self.migrateCreateAct.setWhatsThis(self.tr( + """<b>Create Migration</b>""" + """<p>Creates a new migration for the current database""" + """ and stores it in the configured migrations directory.</p>""" + )) + self.migrateCreateAct.triggered.connect( + self.__createMigration) + self.actions.append(self.migrateCreateAct) + + ######################################################### + ## action to up- and downgrade a databse + ######################################################### + + self.upgradeDatabaseAct = EricAction( + self.tr('Upgrade Database'), + self.tr('&Upgrade Database'), + 0, 0, + self, 'flask_upgrade_database') + self.upgradeDatabaseAct.setStatusTip(self.tr( + 'Upgrade the database to the current migration')) + self.upgradeDatabaseAct.setWhatsThis(self.tr( + """<b>Upgrade Database</b>""" + """<p>Upgrades the database to the current migration.</p>""" + )) + self.upgradeDatabaseAct.triggered.connect( + self.upgradeDatabase) + self.actions.append(self.upgradeDatabaseAct) + + self.downgradeDatabaseAct = EricAction( + self.tr('Downgrade Database'), + self.tr('&Downgrade Database'), + 0, 0, + self, 'flask_downgrade_database') + self.downgradeDatabaseAct.setStatusTip(self.tr( + 'Downgrade the database to the previous version')) + self.downgradeDatabaseAct.setWhatsThis(self.tr( + """<b>Downgrade Database</b>""" + """<p>Downgrades the database to the previous version.</p>""" + )) + self.downgradeDatabaseAct.triggered.connect( + self.downgradeDatabase) + self.actions.append(self.downgradeDatabaseAct) + + ######################################################### + ## actions to show migrations history information + ######################################################### + + self.migrationSummaryAct = EricAction( + self.tr('Show Migrations Summary'), + self.tr('Show Migrations &Summary'), + 0, 0, + self, 'flask_show_migrations_summary') + self.migrationSummaryAct.setStatusTip(self.tr( + 'Show a summary of the created database migrations')) + self.migrationSummaryAct.setWhatsThis(self.tr( + """<b>Show Migrations Summary</b>""" + """<p>Shows a summary list of the created database""" + """ migrations.</p>""" + )) + self.migrationSummaryAct.triggered.connect( + self.__showMigrationsSummary) + self.actions.append(self.migrationSummaryAct) + + self.migrationHistoryAct = EricAction( + self.tr('Show Migrations History'), + self.tr('Show Migrations &History'), + 0, 0, + self, 'flask_show_migrations_history') + self.migrationHistoryAct.setStatusTip(self.tr( + 'Show the full history of the created database migrations')) + self.migrationHistoryAct.setWhatsThis(self.tr( + """<b>Show Migrations History</b>""" + """<p>Shows the full history of the created database""" + """ migrations.</p>""" + )) + self.migrationHistoryAct.triggered.connect( + self.__showMigrationsHistory) + self.actions.append(self.migrationHistoryAct) + def initMenu(self): """ Public slot to initialize the Pyramid menu. @@ -383,27 +448,39 @@ """ self.__menus = {} # clear menus references + # Database menu + dbMenu = QMenu(self.tr("Database")) + dbMenu.setTearOffEnabled(True) + + dbMenu.addAction(self.initializeDbAct) + dbMenu.addSeparator() + dbMenu.addAction(self.migrateCreateAct) + dbMenu.addSeparator() + dbMenu.addAction(self.upgradeDatabaseAct) + dbMenu.addAction(self.downgradeDatabaseAct) + dbMenu.addSeparator() + dbMenu.addAction(self.migrationSummaryAct) + dbMenu.addAction(self.migrationHistoryAct) + + # main Pyramid menu menu = QMenu(self.tr('P&yramid'), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.selectProjectAct) menu.addSeparator() menu.addAction(self.runServerAct) - menu.addAction(self.runLoggingServerAct) menu.addAction(self.runBrowserAct) menu.addSeparator() + menu.addAction(self.runPythonShellAct) + menu.addSeparator() menu.addAction(self.createProjectAct) menu.addSeparator() - menu.addAction(self.setupDevelopAct) - menu.addSeparator() - menu.addAction(self.initializeDbAct) + menu.addMenu(dbMenu) menu.addSeparator() menu.addAction(self.showViewsAct) menu.addAction(self.showRoutesAct) menu.addAction(self.showTweensAct) menu.addSeparator() - menu.addAction(self.runPythonShellAct) - menu.addSeparator() menu.addAction(self.buildDistroAct) menu.addSeparator() menu.addAction(self.documentationAct) @@ -411,6 +488,9 @@ menu.addAction(self.aboutPyramidAct) self.__menus["main"] = menu + self.__menus["database"] = dbMenu + + self.__setCurrentProject(None) return menu @@ -581,6 +661,10 @@ if self.__serverProc is not None: self.__serverProcFinished() self.__setCurrentProject(None) + + for dlg in (self.__migrationSummaryDialog,): + if dlg is not None: + dlg.close() def __getExecutablePaths(self, file): """ @@ -626,8 +710,7 @@ @rtype list of str """ variants = [] - # TODO: that doesn't exist anymore - cmd = "pcreate" + cmd = "cookiecutter" for variant in ['Python3']: virtEnv = self.__getVirtualEnvironment(variant) @@ -737,7 +820,16 @@ return "" - def getPyramidCommand(self, cmd, language=""): + def getProjectVirtualEnvironment(self): + """ + Public method to generate the path of the project virtual environment. + + @return path of the Pyramid project virtual environment + @rtype str + """ + return os.path.join(self.projectPath(), "env") + + def getPyramidCommand(self, cmd, language="", virtualEnv=""): """ Public method to build a Pyramid command. @@ -746,15 +838,18 @@ @param language Python variant to get the virtual environment for (one of '' or 'Python3') @type str + @param virtualEnv path of the project's Python virtual environment + @type str @return full pyramid command @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() - virtualEnv = self.__getVirtualEnvironment(language) - if isWindowsPlatform() and not virtualEnv: - virtualEnv = self.__getDebugEnvironment(language) + if not virtualEnv: + virtualEnv = self.__getVirtualEnvironment(language) + if not virtualEnv: + virtualEnv = self.__getDebugEnvironment(language) if isWindowsPlatform(): fullCmds = [ os.path.join(virtualEnv, "Scripts", cmd + '.exe'), @@ -776,6 +871,24 @@ break return cmd + def __assemblePyramidCommand(self, cmd, virtualEnv): + """ + Private method to assemble the full pyramid command for a given virtual + environment. + + @param cmd command + @type str + @param virtualEnv path of the project's Python virtual environment + @type str + @return assembled pyramid command + @rtype str + """ + return ( + os.path.join(virtualEnv, "Scripts", cmd + '.exe') + if isWindowsPlatform() else + os.path.join(virtualEnv, "bin", cmd) + ) + def getPythonCommand(self): """ Public method to build the Python command. @@ -835,19 +948,18 @@ @rtype str """ if not self.__pyramidVersion: - # TODO: that doesn't exist anymore - cmd = self.getPyramidCommand("pcreate") - if isWindowsPlatform(): - cmd = os.path.join(os.path.dirname(cmd), "pcreate-script.py") + cmd = self.getPyramidCommand( + "pdistreport", + virtualEnv=self.getProjectVirtualEnvironment() + ) try: - with open(cmd, 'r', encoding="utf-8") as f: - lines = f.read().splitlines() - for line in lines: - if line.startswith("__requires__"): - #- sample: __requires__ = 'pyramid==1.4' - vers = line.strip().split()[-1][1:-1].split("==")[1] - self.__pyramidVersion = vers - except OSError: + output = subprocess.check_output([cmd]) # secok + outputLines = output.decode().splitlines() + for line in outputLines: + if line.startswith("Pyramid version:"): + self.__pyramidVersion = line.rsplit(None, 1)[1] + break + except (OSError, subprocess.CalledProcessError): self.__pyramidVersion = "" return self.__pyramidVersion @@ -913,32 +1025,31 @@ """ from .CreateParametersDialog import CreateParametersDialog - dlg = CreateParametersDialog(self) + dlg = CreateParametersDialog(self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: - scaffold, project, overwrite, simulate = dlg.getData() + template, version, overwrite, contextData = dlg.getData() - # TODO: that doesn't exist anymore - cmd = self.getPyramidCommand("pcreate") - args = [] + cmd = self.getPyramidCommand("cookiecutter") + args = ["--no-input"] if overwrite: - args.append("--overwrite") - else: - args.append("--interactive") - if simulate: - args.append("--simulate") - args.append("--scaffold={0}".format(scaffold)) - args.append(project) + args.append("--overwrite-if-exists") + if version: + args += ["--checkout", version] + args.append(template) + for context, data in contextData.items(): + args.append("{0}={1}".format(context, data)) dlg = PyramidDialog(self.tr("Create Pyramid Project"), linewrap=False, parent=self.__ui) if dlg.startProcess( cmd, args, self.__ericProject.getProjectPath() ): dlg.exec() - if dlg.normalExit() and not simulate: - # search for files created by pcreate and add them to the - # project + if dlg.normalExit() and "repo_name" in contextData: + # search for files created by cookiecutter and add them + # to the project projectPath = os.path.join( - self.__ericProject.getProjectPath(), project) + self.__ericProject.getProjectPath(), + contextData["repo_name"]) for entry in os.walk(projectPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) @@ -946,12 +1057,63 @@ # create the base directory for translations i18nPath = os.path.join( - projectPath, project.lower(), "i18n") + projectPath, contextData["repo_name"].lower(), + "i18n") if not os.path.exists(i18nPath): os.makedirs(i18nPath) self.__ericProject.setDirty(True) - self.__setCurrentProject(project) + combinedOutput = False + argsLists = [] + + # 1. create a Python virtual environment for the project + argsLists.append([sys.executable, "-m", "venv", "env"]) + # 2. upgrade packaging tools + python = self.__assemblePyramidCommand( + "python", os.path.join(projectPath, "env")) + argsLists.append([python, "-m", "pip", "install", + "--upgrade", "pip", "setuptools"]) + # 3. install project in editable mode with testing + argsLists.append([python, "-m", "pip", "install", "-e", + ".[testing]"]) + + if ( + "backend" in contextData and + contextData["backend"] == "sqlalchemy" + ): + # only SQLAlchemy needs initialization of alembic + combinedOutput = True + + # 4. initialize database + alembic = self.__assemblePyramidCommand( + "alembic", os.path.join(projectPath, "env")) + argsLists.append([alembic, "-c", "development.ini", + "revision", "--autogenerate", + "--message", "initialized database"]) + # 5. upgrade database to initial version + argsLists.append([alembic, "-c", "development.ini", + "upgrade", "head"]) + + dlg = PyramidDialog( + self.tr("Initializing Pyramid Project"), + linewrap=False, combinedOutput=combinedOutput, + parent=self.__ui) + if dlg.startBatchProcesses(argsLists, + workingDir=projectPath): + dlg.exec() + + self.__setCurrentProject(contextData["repo_name"]) + + if ( + "backend" in contextData and + contextData["backend"] == "sqlalchemy" + ): + # add the alembic files created above to the project + migrationsPath = self.migrationsPath() + for entry in os.walk(migrationsPath): + for fileName in entry[2]: + fullName = os.path.join(entry[0], fileName) + self.__ericProject.appendFile(fullName) ################################################################## ## methods below implement site related functions @@ -973,7 +1135,7 @@ os.path.isdir(os.path.join(ppath, entry)) ): projects.append(entry) - return projects + return sorted(projects) def __selectProject(self): """ @@ -1002,9 +1164,9 @@ projects = None self.__setCurrentProject(project) - def __projectPath(self): + def projectPath(self): """ - Private method to calculate the full path of the Pyramid project. + Public method to calculate the full path of the Pyramid project. @return path of the project @rtype str @@ -1045,7 +1207,7 @@ else: lowerProject = self.__project().lower() config = configparser.ConfigParser() - config.read(os.path.join(self.__projectPath(), "setup.cfg")) + config.read(os.path.join(self.projectPath(), "setup.cfg")) try: outputDir = config.get("init_catalog", "output_dir") except (configparser.NoOptionError, configparser.NoSectionError): @@ -1061,9 +1223,16 @@ if self.__currentProject is None: self.initializeDbAct.setEnabled(False) + with contextlib.suppress(KeyError): + self.__menus["database"].setEnabled(False) else: initCmd = self.__getInitDbCommand() self.initializeDbAct.setEnabled(os.path.exists(initCmd)) + + alembicDir = os.path.join( + self.projectPath(), self.__currentProject, + "alembic", "versions") + self.__menus["database"].setEnabled(os.path.exists(alembicDir)) def __project(self): """ @@ -1086,18 +1255,15 @@ ## slots below implement run functions ################################################################## - def __runServer(self, logging=False): + def __runServer(self): """ Private slot to start the Pyramid Web server. - - @param logging flag indicating to enable logging - @type bool """ consoleCmd = self.isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand"))[1] if consoleCmd: try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1108,9 +1274,10 @@ args = Utilities.parseOptionString(consoleCmd) args[0] = Utilities.getExecutablePath(args[0]) - args.append(self.getPyramidCommand("pserve")) - if logging: - args.append("--log-file=server.log") + args.append(self.getPyramidCommand( + "pserve", + virtualEnv=self.getProjectVirtualEnvironment() + )) args.append("--reload") args.append(os.path.join(projectPath, "development.ini")) @@ -1132,12 +1299,6 @@ self.tr('Process Generation Error'), self.tr('The Pyramid server could not be started.')) - def __runLoggingServer(self): - """ - Private slot to start the Pyramid Web server with logging. - """ - self.__runServer(True) - def __serverProcFinished(self): """ Private slot connected to the finished signal. @@ -1156,7 +1317,7 @@ Private slot to start the default web browser with the server URL. """ try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1168,10 +1329,10 @@ config = configparser.ConfigParser() config.read(os.path.join(projectPath, "development.ini")) try: - port = config.get("server:main", "port") + listen = config.get("server:main", "listen") except (configparser.NoOptionError, configparser.NoSectionError): - port = "8080" - url = "http://localhost:{0}".format(port) + listen = "localhost:6543" + url = "http://{0}".format(listen) if self.__plugin.getPreferences("UseExternalBrowser"): res = QDesktopServices.openUrl(QUrl(url)) if not res: @@ -1191,7 +1352,7 @@ self.__plugin.getPreferences("ConsoleCommand"))[1] if consoleCmd: try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1202,7 +1363,10 @@ args = Utilities.parseOptionString(consoleCmd) args[0] = Utilities.getExecutablePath(args[0]) - args.append(self.getPyramidCommand("pshell")) + args.append(self.getPyramidCommand( + "pshell", + virtualEnv=self.getProjectVirtualEnvironment() + )) consoleType = self.__plugin.getPreferences("Python3ConsoleType") args.append("--python-shell={0}".format(consoleType)) args.append(os.path.join(projectPath, "development.ini")) @@ -1218,41 +1382,6 @@ ' started.')) ################################################################## - ## slots below implement setup functions - ################################################################## - - def __setupDevelop(self): - """ - Private slot to set up the development environment for the current - project. - """ - title = self.tr("Setup Development Environment") - try: - wd = self.__projectPath() - except PyramidNoProjectSelectedException: - EricMessageBox.warning( - self.__ui, - title, - self.tr('No current Pyramid project selected or no Pyramid' - ' project created yet. Aborting...')) - return - - cmd = self.getPythonCommand() - args = [] - args.append("setup.py") - args.append("develop") - - dia = PyramidDialog( - title, - msgSuccess=self.tr("Pyramid development environment setup" - " successfully.")) - res = dia.startProcess(cmd, args, wd) - if res: - dia.exec() - initCmd = self.__getInitDbCommand() - self.initializeDbAct.setEnabled(os.path.exists(initCmd)) - - ################################################################## ## slots below implement distribution functions ################################################################## @@ -1263,7 +1392,7 @@ """ title = self.tr("Build Distribution File") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1279,7 +1408,10 @@ dlg = DistributionTypeSelectionDialog(self, projectPath, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: formats = dlg.getFormats() - cmd = self.getPythonCommand() + cmd = self.getPyramidCommand( + "python", + virtualEnv=self.getProjectVirtualEnvironment() + ) args = [] args.append("setup.py") args.append("sdist") @@ -1295,54 +1427,6 @@ dia.exec() ################################################################## - ## slots below implement database functions - ################################################################## - - def __getInitDbCommand(self): - """ - Private method to create the path to the initialization script. - - @return path to the initialization script - @rtype str - """ - try: - cmd = "initialize_{0}_db".format(self.__project()) - return self.getPyramidCommand(cmd) - except PyramidNoProjectSelectedException: - EricMessageBox.warning( - self.__ui, - self.tr("Initialize Database"), - self.tr('No current Pyramid project selected or no Pyramid' - ' project created yet. Aborting...')) - return "" - - def __initializeDatabase(self): - """ - Private slot to initialize the database of the Pyramid project. - """ - title = self.tr("Initialize Database") - try: - projectPath = self.__projectPath() - except PyramidNoProjectSelectedException: - EricMessageBox.warning( - self.__ui, - title, - self.tr('No current Pyramid project selected or no Pyramid' - ' project created yet. Aborting...')) - return - - cmd = self.__getInitDbCommand() - args = [] - args.append("development.ini") - - dia = PyramidDialog( - title, - msgSuccess=self.tr("Database initialized successfully.")) - res = dia.startProcess(cmd, args, projectPath) - if res: - dia.exec() - - ################################################################## ## slots below implement various debugging functions ################################################################## @@ -1352,7 +1436,7 @@ """ title = self.tr("Show Matching Views") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1370,7 +1454,10 @@ if not ok or url == "": return - cmd = self.getPyramidCommand("pviews") + cmd = self.getPyramidCommand( + "pviews", + virtualEnv=self.getProjectVirtualEnvironment() + ) args = [] args.append("development.ini") args.append(url) @@ -1386,7 +1473,7 @@ """ title = self.tr("Show Routes") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1408,7 +1495,7 @@ """ title = self.tr("Show Tween Objects") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1417,7 +1504,10 @@ ' project created yet. Aborting...')) return - cmd = self.getPyramidCommand("ptweens") + cmd = self.getPyramidCommand( + "ptweens", + virtualEnv=self.getProjectVirtualEnvironment() + ) args = [] args.append("development.ini") @@ -1505,7 +1595,7 @@ """ title = self.tr("Extract messages") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1559,7 +1649,7 @@ title = self.tr( "Initializing message catalog for '{0}'").format(code) try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1596,7 +1686,7 @@ """ title = self.tr("Compiling message catalogs") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1633,7 +1723,7 @@ """ title = self.tr("Compiling message catalogs") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1686,7 +1776,7 @@ """ title = self.tr("Updating message catalogs") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1716,7 +1806,7 @@ """ title = self.tr("Updating message catalogs") try: - projectPath = self.__projectPath() + projectPath = self.projectPath() except PyramidNoProjectSelectedException: EricMessageBox.warning( self.__ui, @@ -1763,7 +1853,7 @@ editor = self.__plugin.getPreferences("TranslationsEditor") if poFile.endswith(".po") and editor: try: - wd = self.__projectPath() + wd = self.projectPath() except PyramidNoProjectSelectedException: wd = "" started, pid = QProcess.startDetached(editor, [poFile], wd) @@ -1774,3 +1864,199 @@ self.tr('The translations editor process ({0}) could' ' not be started.').format( os.path.basename(editor))) + + ####################################################################### + ## database related methods and slots below + ####################################################################### + + def getAlembicCommand(self): + """ + Public method to get the path to the alembic executable of the current + Pyramid project. + + @return path to the alembic executable + @rtype str + """ + return self.getPyramidCommand( + "alembic", + virtualEnv=self.getProjectVirtualEnvironment() + ) + + def migrationsPath(self): + """ + Public method to get the path to the migrations directory of the + current Pyramid project. + + @return pathof the directory containing the migrations + @rtype str + """ + return os.path.join(self.projectPath(), self.__currentProject, + "alembic", "versions") + + def __getInitDbCommand(self): + """ + Private method to create the path to the initialization script. + + @return path to the initialization script + @rtype str + """ + try: + cmd = "initialize_{0}_db".format(self.__project()) + return self.getPyramidCommand( + cmd, + virtualEnv=self.getProjectVirtualEnvironment() + ) + except PyramidNoProjectSelectedException: + EricMessageBox.warning( + self.__ui, + self.tr("Initialize Database"), + self.tr('No current Pyramid project selected or no Pyramid' + ' project created yet. Aborting...')) + return "" + + @pyqtSlot() + def __initializeDatabase(self): + """ + Private slot to initialize the database of the Pyramid project. + """ + title = self.tr("Initialize Database") + try: + projectPath = self.projectPath() + except PyramidNoProjectSelectedException: + EricMessageBox.warning( + self.__ui, + title, + self.tr('No current Pyramid project selected or no Pyramid' + ' project created yet. Aborting...')) + return + + cmd = self.__getInitDbCommand() + args = [] + args.append("development.ini") + + dia = PyramidDialog( + title, + msgSuccess=self.tr("Database initialized successfully.")) + res = dia.startProcess(cmd, args, projectPath) + if res: + dia.exec() + + @pyqtSlot() + def __createMigration(self): + """ + Private slot to create a new database migration. + """ + title = self.tr("Create Migration") + projectPath = self.projectPath() + migrations = self.migrationsPath() + + message, ok = QInputDialog.getText( + None, + title, + self.tr("Enter a short message for the migration:"), + QLineEdit.EchoMode.Normal) + if ok: + args = ["-c", "development.ini", "revision", "--autogenerate"] + if migrations: + args += ["--version-path", migrations] + if message: + args += ["--message", message] + + dlg = PyramidDialog( + title, + msgSuccess=self.tr("\nMigration created successfully."), + linewrap=False, combinedOutput=True, + parent=self.__ui + ) + if dlg.startProcess(self.getAlembicCommand(), args, + workingDir=projectPath): + dlg.exec() + if dlg.normalExit(): + versionsPattern = os.path.join(migrations, "*.py") + for fileName in glob.iglob(versionsPattern): + self.__ericProject.appendFile(fileName) + + @pyqtSlot() + def upgradeDatabase(self, revision=None): + """ + Public slot to upgrade the database to the head or a given version. + + @param revision migration revision to upgrade to + @type str + """ + title = self.tr("Upgrade Database") + projectPath = self.projectPath() + + args = ["-c", "development.ini", "upgrade"] + if revision: + args.append(revision) + else: + args.append("head") + + dlg = PyramidDialog( + title, + msgSuccess=self.tr("\nDatabase upgraded successfully."), + linewrap=False, combinedOutput=True, parent=self.__ui + ) + if dlg.startProcess(self.getAlembicCommand(), args, + workingDir=projectPath): + dlg.exec() + + @pyqtSlot() + def downgradeDatabase(self, revision=None): + """ + Public slot to downgrade the database to the previous or a given + version. + + @param revision migration revision to downgrade to + @type str + """ + title = self.tr("Downgrade Database") + projectPath = self.projectPath() + + args = ["-c", "development.ini", "downgrade"] + if revision: + args.append(revision) + else: + args.append("-1") + + dlg = PyramidDialog( + title, + msgSuccess=self.tr("\nDatabase downgraded successfully."), + linewrap=False, combinedOutput=True, parent=self.__ui + ) + if dlg.startProcess(self.getAlembicCommand(), args, + workingDir=projectPath): + dlg.exec() + + @pyqtSlot() + def __showMigrationsSummary(self): + """ + Private slot to show a migrations history summary. + """ + from .MigrateSummaryDialog import MigrateSummaryDialog + + if self.__migrationSummaryDialog is None: + self.__migrationSummaryDialog = MigrateSummaryDialog( + self, parent=self.__ui) + + self.__migrationSummaryDialog.showSummary() + + @pyqtSlot() + def __showMigrationsHistory(self): + """ + Private slot to show the full migrations history. + """ + title = self.tr("Migrations History") + projectPath = self.projectPath() + + args = ["-c", "development.ini", "history", "--indicate-current", + "--verbose"] + + dlg = PyramidDialog( + title, + linewrap=False, combinedOutput=True, parent=self.__ui + ) + if dlg.startProcess(self.getAlembicCommand(), args, + workingDir=projectPath): + dlg.exec()