Tue, 24 Nov 2020 19:23:28 +0100
Started implementing support for the 'flask-migrate' extension.
--- a/PluginFlask.e4p Tue Nov 24 18:03:40 2020 +0100 +++ b/PluginFlask.e4p Tue Nov 24 19:23:28 2020 +0100 @@ -24,6 +24,10 @@ <Source>ProjectFlask/FlaskBabelExtension/PyBabelProjectExtension.py</Source> <Source>ProjectFlask/FlaskBabelExtension/__init__.py</Source> <Source>ProjectFlask/FlaskCommandDialog.py</Source> + <Source>ProjectFlask/FlaskMigrateExtension/FlaskMigrateDetector.py</Source> + <Source>ProjectFlask/FlaskMigrateExtension/MigrateConfigDialog.py</Source> + <Source>ProjectFlask/FlaskMigrateExtension/MigrateProjectExtension.py</Source> + <Source>ProjectFlask/FlaskMigrateExtension/__init__.py</Source> <Source>ProjectFlask/FormSelectionDialog.py</Source> <Source>ProjectFlask/Project.py</Source> <Source>ProjectFlask/RoutesDialog.py</Source> @@ -36,6 +40,7 @@ <Form>ProjectFlask/ConfigurationPage/FlaskPage.ui</Form> <Form>ProjectFlask/FlaskBabelExtension/PyBabelConfigDialog.ui</Form> <Form>ProjectFlask/FlaskCommandDialog.ui</Form> + <Form>ProjectFlask/FlaskMigrateExtension/MigrateConfigDialog.ui</Form> <Form>ProjectFlask/FormSelectionDialog.ui</Form> <Form>ProjectFlask/RoutesDialog.ui</Form> <Form>ProjectFlask/RunServerDialog.ui</Form>
--- a/ProjectFlask/FlaskBabelExtension/PyBabelConfigDialog.py Tue Nov 24 18:03:40 2020 +0100 +++ b/ProjectFlask/FlaskBabelExtension/PyBabelConfigDialog.py Tue Nov 24 19:23:28 2020 +0100 @@ -4,7 +4,7 @@ # """ -Module implementing a dialog to edit the PyBabel configuration. +Module implementing a dialog to edit the flask-babel configuration. """ import os @@ -20,7 +20,7 @@ class PyBabelConfigDialog(QDialog, Ui_PyBabelConfigDialog): """ - Class implementing a dialog to edit the PyBabel configuration. + Class implementing a dialog to edit the flask-babel configuration. """ def __init__(self, configuration, parent=None): """
--- a/ProjectFlask/FlaskBabelExtension/PyBabelConfigDialog.ui Tue Nov 24 18:03:40 2020 +0100 +++ b/ProjectFlask/FlaskBabelExtension/PyBabelConfigDialog.ui Tue Nov 24 19:23:28 2020 +0100 @@ -7,11 +7,11 @@ <x>0</x> <y>0</y> <width>600</width> - <height>150</height> + <height>180</height> </rect> </property> <property name="windowTitle"> - <string>PyBabel Configuration</string> + <string>flask-babel Configuration</string> </property> <property name="sizeGripEnabled"> <bool>true</bool>
--- a/ProjectFlask/FlaskBabelExtension/PyBabelProjectExtension.py Tue Nov 24 18:03:40 2020 +0100 +++ b/ProjectFlask/FlaskBabelExtension/PyBabelProjectExtension.py Tue Nov 24 19:23:28 2020 +0100 @@ -24,7 +24,7 @@ class PyBabelProject(QObject): """ - Class implementing the Flask project support. + Class implementing the flask-babel project support. """ def __init__(self, plugin, project, parent=None): """ @@ -43,19 +43,20 @@ self.__project = project self.__e5project = e5App().getObject("Project") + # TODO: move virtual env stuff to Project self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") self.__hooksInstalled = False def initActions(self): """ - Public method to define the Flask actions. + Public method to define the flask-babel actions. """ self.actions = [] self.pybabelConfigAct = E5Action( self.tr('Configure PyBabel'), - self.tr('Configure Py&Babel'), + self.tr('&Configure PyBabel'), 0, 0, self, 'flask_config_pybabel') self.pybabelConfigAct.setStatusTip(self.tr( @@ -87,7 +88,7 @@ self.pybabelAvailabilityAct = E5Action( self.tr('Check flask-babel Availability'), - self.tr('&Check flask-babel Availability'), + self.tr('Check flask-babel &Availability'), 0, 0, self, 'flask_check_pybabel') self.pybabelAvailabilityAct.setStatusTip(self.tr( @@ -102,7 +103,7 @@ def initMenu(self): """ - Public method to initialize the Flask menu. + Public method to initialize the flask-babel menu. @return the menu generated @rtype QMenu @@ -200,7 +201,7 @@ """ Public method to determine the availability of flask-babel. """ - available = self.flaskBabelAvailable() + available = self.__flaskBabelAvailable() self.__project.setCapability("pybabel", available) self.pybabelConfigAct.setEnabled(available) @@ -223,9 +224,9 @@ ## slots and methods below implement i18n and l10n support ################################################################## - def flaskBabelAvailable(self): + def __flaskBabelAvailable(self): """ - Public method to check, if the 'flask-babel' package is available. + Private method to check, if the 'flask-babel' package is available. @return flag indicating the availability of 'flask-babel' @rtype bool
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskMigrateExtension/FlaskMigrateDetector.py Tue Nov 24 19:23:28 2020 +0100 @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module to check for the presence of 'flask-migrate' by importing it. +""" + +import sys + +if __name__ == "__main__": + try: + import flask_migrate # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + ret = 0 + except ImportError: + ret = 1 + + sys.exit(ret)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskMigrateExtension/MigrateConfigDialog.py Tue Nov 24 19:23:28 2020 +0100 @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to edit the flask-migrate configuration. +""" + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog + +from E5Gui.E5PathPicker import E5PathPickerModes +from E5Gui.E5Application import e5App + +from .Ui_MigrateConfigDialog import Ui_MigrateConfigDialog + + +class MigrateConfigDialog(QDialog, Ui_MigrateConfigDialog): + """ + Class implementing a dialog to edit the flask-migrate configuration. + """ + def __init__(self, configuration, parent=None): + """ + Constructor + + @param configuration current pybabel configuration + @type dict + @param parent reference to the parent widget + @type QWidget + """ + super(MigrateConfigDialog, self).__init__(parent) + self.setupUi(self) + + self.__e5project = e5App().getObject("Project") + + self.migrationsDirectoryPicker.setMode( + E5PathPickerModes.DirectoryMode) + self.migrationsDirectoryPicker.setDefaultDirectory( + self.__e5project.getProjectPath()) + + self.migrationsDirectoryPicker.setFocus(Qt.OtherFocusReason) + + if ( + "migrationsDirectory" in configuration and + configuration["migrationsDirectory"] + ): + self.migrationsDirectoryPicker.setText( + self.__e5project.getAbsoluteUniversalPath( + configuration["migrationsDirectory"])) + + def getConfiguration(self): + """ + Public method to get the entered configuration data. + + @return pybabel configuration + @rtype dict + """ + configuration = { + "migrationsDirectory": self.__e5project.getRelativeUniversalPath( + self.migrationsDirectoryPicker.text()), + } + + return configuration
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskMigrateExtension/MigrateConfigDialog.ui Tue Nov 24 19:23:28 2020 +0100 @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MigrateConfigDialog</class> + <widget class="QDialog" name="MigrateConfigDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>627</width> + <height>129</height> + </rect> + </property> + <property name="windowTitle"> + <string>flask-migrate Configuration</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Migrations Directory</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="E5PathPicker" name="migrationsDirectoryPicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the name of the directory containing the migrations</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string><b>Note:</b> Leave this entry empty to use the default of "migrations".</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>MigrateConfigDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MigrateConfigDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskMigrateExtension/MigrateProjectExtension.py Tue Nov 24 19:23:28 2020 +0100 @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the project support for flask-migrate. +""" + +import os + +from PyQt5.QtCore import pyqtSlot, QObject, QProcess +from PyQt5.QtWidgets import QMenu, QDialog + +from E5Gui import E5MessageBox +from E5Gui.E5Application import e5App +from E5Gui.E5Action import E5Action + +import Utilities + +from ..FlaskCommandDialog import FlaskCommandDialog + + +class MigrateProject(QObject): + """ + Class implementing the flask-migrate project support. + """ + def __init__(self, plugin, project, parent=None): + """ + Constructor + + @param plugin reference to the plugin object + @type ProjectFlaskPlugin + @param project reference to the project object + @type Project + @param parent parent + @type QObject + """ + super(MigrateProject, self).__init__(parent) + + self.__plugin = plugin + self.__project = project + + # TODO: move virtual env stuff to Project + self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") + + def initActions(self): + """ + Public method to define the flask-migrate actions. + """ + # TODO: implement flask-migrate actions + self.actions = [] + + self.migrateConfigAct = E5Action( + self.tr('Configure Migrate'), + self.tr('&Configure Migrate'), + 0, 0, + self, 'flask_config_migrate') + self.migrateConfigAct.setStatusTip(self.tr( + 'Shows a dialog to edit the configuration for flask-migrate')) + self.migrateConfigAct.setWhatsThis(self.tr( + """<b>Configure Migrate</b>""" + """<p>Shows a dialog to edit the configuration for""" + """ flask-migrate.</p>""" + )) + self.migrateConfigAct.triggered.connect( + self.__configureMigrate) + self.actions.append(self.migrateConfigAct) + + self.migrateInstallAct = E5Action( + self.tr('Install flask-migrate'), + self.tr('&Install flask-migrate'), + 0, 0, + self, 'flask_install_migrate') + self.migrateInstallAct.setStatusTip(self.tr( + 'Installs the flask-migrate extension into the configured' + ' environment')) + self.migrateInstallAct.setWhatsThis(self.tr( + """<b>Install flask-migrate</b>""" + """<p>Installs the flask-migrate extension into the configured""" + """ environment using the pip interface.</p>""" + )) + self.migrateInstallAct.triggered.connect( + self.__installFlaskMigrate) + self.actions.append(self.migrateInstallAct) + + self.migrateAvailabilityAct = E5Action( + self.tr('Check flask-migrate Availability'), + self.tr('Check flask-migrate &Availability'), + 0, 0, + self, 'flask_check_migrate') + self.migrateAvailabilityAct.setStatusTip(self.tr( + 'Check the availability of the flask-migrate extension')) + self.migrateAvailabilityAct.setWhatsThis(self.tr( + """<b>Check flask-migrate Availability</b>""" + """<p>Check the availability of the flask-migrate extension.</p>""" + )) + self.migrateAvailabilityAct.triggered.connect( + self.__checkAvailability) + self.actions.append(self.migrateAvailabilityAct) + + ######################################################### + ## action to initialize the database migration system + ######################################################### + # TODO: add action for flask db init + + ######################################################### + ## action to create a new database migration + ######################################################### + # TODO: add action for flask db migrate + + ######################################################### + ## action to up- and downgrade a databse + ######################################################### + # TODO: add action for flask db upgrade + # TODO: add action for flask db downgrade + + def initMenu(self): + """ + Public method to initialize the flask-migrate menu. + + @return the menu generated + @rtype QMenu + """ + menu = QMenu(self.tr("Database")) + menu.setTearOffEnabled(True) + + menu.addAction(self.migrateConfigAct) + menu.addSeparator() + menu.addAction(self.migrateAvailabilityAct) + menu.addAction(self.migrateInstallAct) + + return menu + + def determineCapability(self): + """ + Public method to determine the availability of flask-migrate. + """ + available = self.__flaskMigrateAvailable() + self.__project.setCapability("migrate", available) + + self.migrateConfigAct.setEnabled(available) + self.migrateInstallAct.setEnabled(not available) + + def __flaskMigrateAvailable(self): + """ + Private method to check, if the 'flask-babel' package is available. + + @return flag indicating the availability of 'flask-babel' + @rtype bool + """ + venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3") + interpreter = self.__virtualEnvManager.getVirtualenvInterpreter( + venvName) + if interpreter and Utilities.isinpath(interpreter): + detector = os.path.join( + os.path.dirname(__file__), "FlaskMigrateDetector.py") + proc = QProcess() + proc.setProcessChannelMode(QProcess.MergedChannels) + proc.start(interpreter, [detector]) + finished = proc.waitForFinished(30000) + if finished and proc.exitCode() == 0: + return True + + return False + + ######################################################## + ## Menu related slots below + ######################################################## + + @pyqtSlot() + def __configureMigrate(self): + """ + Private slot to show a dialog to edit the migrate configuration. + """ + # TODO: implement MigrateConfigDialog + from .MigrateConfigDialog import MigrateConfigDialog + + config = self.__project.getData("migrate", "") + dlg = MigrateConfigDialog(config) + if dlg.exec() == QDialog.Accepted: + config = dlg.getConfiguration() + self.__project.setData("migrate", "", config) + + def __ensureMigrateConfigured(self): + """ + Private method to ensure, that flask-migrate has been configured. + + @return flag indicating successful configuration + @rtype bool + """ + config = self.__project.getData("migrate", "") + if not config: + self.__configureMigrate() + return True + + return False + + @pyqtSlot() + def __installFlaskMigrate(self): + """ + Private slot to install the flask-migrate extension into the configured + environment. + """ + language = e5App().getObject("Project").getProjectLanguage() + if language == "Python3": + venvName = self.__plugin.getPreferences( + "VirtualEnvironmentNamePy3") + else: + venvName = "" + if venvName: + interpreter = self.__project.getFullCommand("python") + pip = e5App().getObject("Pip") + pip.installPackages(["flask-migrate"], interpreter=interpreter) + self.determineCapability() + else: + E5MessageBox.critical( + None, + self.tr("Install flask-migrate"), + self.tr("The 'flask-migrate' extension could not be installed" + " because no virtual environment has been" + " configured.")) + + @pyqtSlot() + def __checkAvailability(self): + """ + Private slot to check the availability of the 'flask-babel' extension. + """ + self.determineCapability() + if self.__project.hasCapability("migrate"): + msg = self.tr("The 'flask-migrate' extension is installed.") + else: + msg = self.tr("The 'flask-migrate' extension is not installed.") + E5MessageBox.information( + None, + self.tr("flask-migrate Availability"), + msg) + + ######################################################### + ## slot to initialize the database migration system + ######################################################### + + ######################################################### + ## slot to create a new database migration + ######################################################### + + ######################################################### + ## slots to up- and downgrade a databse + #########################################################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskMigrateExtension/__init__.py Tue Nov 24 19:23:28 2020 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package containing support for the flask-migrate extension. +"""
--- a/ProjectFlask/Project.py Tue Nov 24 18:03:40 2020 +0100 +++ b/ProjectFlask/Project.py Tue Nov 24 19:23:28 2020 +0100 @@ -23,12 +23,10 @@ import UI.PixmapCache import Utilities -from .FlaskCommandDialog import FlaskCommandDialog - from .FlaskBabelExtension.PyBabelProjectExtension import PyBabelProject +from .FlaskMigrateExtension.MigrateProjectExtension import MigrateProject -# TODO: move database related code to a separate package (FlaskMigrateExtension) class Project(QObject): """ Class implementing the Flask project support. @@ -76,6 +74,7 @@ self.__capabilities = {} self.__pybabelProject = PyBabelProject(self.__plugin, self, self) + self.__migrateProject = MigrateProject(self.__plugin, self, self) def initActions(self): """ @@ -168,26 +167,6 @@ self.actions.append(self.showRoutesAct) ################################## - ## database action below ## - ################################## - - # TODO: replace this with 'flask-migrate' - self.initDatabaseAct = E5Action( - self.tr('Initialize Database'), - self.tr('&Initialize Database'), - 0, 0, - self, 'flask_init_database') - self.initDatabaseAct.setStatusTip(self.tr( - 'Shows a dialog with the result of the database creation')) - self.initDatabaseAct.setWhatsThis(self.tr( - """<b>Initialize Database</b>""" - """<p>Shows a dialog with the result of the database""" - """ creation.</p>""" - )) - self.initDatabaseAct.triggered.connect(self.__initDatabase) - self.actions.append(self.initDatabaseAct) - - ################################## ## documentation action below ## ################################## @@ -224,6 +203,7 @@ self.actions.append(self.aboutFlaskAct) self.__pybabelProject.initActions() + self.__migrateProject.initActions() def initMenu(self): """ @@ -235,6 +215,7 @@ self.__menus = {} # clear menus references self.__menus["pybabel"] = self.__pybabelProject.initMenu() + self.__menus["migrate"] = self.__migrateProject.initMenu() menu = QMenu(self.tr('&Flask'), self.__ui) menu.setTearOffEnabled(True) @@ -247,7 +228,7 @@ menu.addSeparator() menu.addAction(self.showRoutesAct) menu.addSeparator() - menu.addAction(self.initDatabaseAct) + menu.addMenu(self.__menus["migrate"]) menu.addSeparator() menu.addMenu(self.__menus["pybabel"]) menu.addSeparator() @@ -659,7 +640,7 @@ self.__pybabelProject.determineCapability() # 2. support for flask-migrate - # TODO: add support for flask-migrate + self.__migrateProject.determineCapability() def hasCapability(self, key): """ @@ -808,13 +789,3 @@ if dlg.showRoutes(): dlg.show() self.__routesDialog = dlg - - # TODO: replace this by commands made by flask-migrate (flask db ...) - @pyqtSlot() - def __initDatabase(self): - """ - Private slot showing the result of the database creation. - """ - dlg = FlaskCommandDialog(self) - if dlg.startCommand("init-db"): - dlg.exec()