7 Module implementing the Flask project support. |
7 Module implementing the Flask project support. |
8 """ |
8 """ |
9 |
9 |
10 import os |
10 import os |
11 |
11 |
12 from PyQt5.QtCore import ( |
12 from PyQt6.QtCore import ( |
13 pyqtSlot, QObject, QProcess, QProcessEnvironment, QTimer, QFileInfo |
13 pyqtSlot, QObject, QProcess, QProcessEnvironment, QTimer, QFileInfo |
14 ) |
14 ) |
15 from PyQt5.QtWidgets import QMenu, QDialog |
15 from PyQt6.QtWidgets import QMenu, QDialog |
16 |
16 |
17 from E5Gui import E5MessageBox, E5FileDialog |
17 from EricWidgets import EricMessageBox, EricFileDialog |
18 from E5Gui.E5Action import E5Action |
18 from EricGui.EricAction import EricAction |
19 from E5Gui.E5Application import e5App |
19 from EricWidgets.EricApplication import ericApp |
20 |
20 |
21 from Globals import isWindowsPlatform |
21 from Globals import isWindowsPlatform |
22 |
22 |
23 import UI.PixmapCache |
23 import UI.PixmapCache |
24 import Utilities |
24 import Utilities |
46 |
46 |
47 self.__plugin = plugin |
47 self.__plugin = plugin |
48 self.__iconSuffix = iconSuffix |
48 self.__iconSuffix = iconSuffix |
49 self.__ui = parent |
49 self.__ui = parent |
50 |
50 |
51 self.__e5project = e5App().getObject("Project") |
51 self.__ericProject = ericApp().getObject("Project") |
52 self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") |
52 self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager") |
53 |
53 |
54 self.__menus = {} # dictionary with references to menus |
54 self.__menus = {} # dictionary with references to menus |
55 self.__formsBrowser = None |
55 self.__formsBrowser = None |
56 self.__hooksInstalled = False |
56 self.__hooksInstalled = False |
57 |
57 |
84 |
84 |
85 ############################## |
85 ############################## |
86 ## run actions below ## |
86 ## run actions below ## |
87 ############################## |
87 ############################## |
88 |
88 |
89 self.runServerAct = E5Action( |
89 self.runServerAct = EricAction( |
90 self.tr('Run Server'), |
90 self.tr('Run Server'), |
91 self.tr('Run &Server'), |
91 self.tr('Run &Server'), |
92 0, 0, |
92 0, 0, |
93 self, 'flask_run_server') |
93 self, 'flask_run_server') |
94 self.runServerAct.setStatusTip(self.tr( |
94 self.runServerAct.setStatusTip(self.tr( |
98 """<p>Starts the Flask Web server.</p>""" |
98 """<p>Starts the Flask Web server.</p>""" |
99 )) |
99 )) |
100 self.runServerAct.triggered.connect(self.__runServer) |
100 self.runServerAct.triggered.connect(self.__runServer) |
101 self.actions.append(self.runServerAct) |
101 self.actions.append(self.runServerAct) |
102 |
102 |
103 self.runDevServerAct = E5Action( |
103 self.runDevServerAct = EricAction( |
104 self.tr('Run Development Server'), |
104 self.tr('Run Development Server'), |
105 self.tr('Run &Development Server'), |
105 self.tr('Run &Development Server'), |
106 0, 0, |
106 0, 0, |
107 self, 'flask_run_dev_server') |
107 self, 'flask_run_dev_server') |
108 self.runDevServerAct.setStatusTip(self.tr( |
108 self.runDevServerAct.setStatusTip(self.tr( |
112 """<p>Starts the Flask Web server in development mode.</p>""" |
112 """<p>Starts the Flask Web server in development mode.</p>""" |
113 )) |
113 )) |
114 self.runDevServerAct.triggered.connect(self.__runDevelopmentServer) |
114 self.runDevServerAct.triggered.connect(self.__runDevelopmentServer) |
115 self.actions.append(self.runDevServerAct) |
115 self.actions.append(self.runDevServerAct) |
116 |
116 |
117 self.askForServerOptionsAct = E5Action( |
117 self.askForServerOptionsAct = EricAction( |
118 self.tr('Ask for Server Start Options'), |
118 self.tr('Ask for Server Start Options'), |
119 self.tr('Ask for Server Start Options'), |
119 self.tr('Ask for Server Start Options'), |
120 0, 0, |
120 0, 0, |
121 self, 'flask_ask_server_options') |
121 self, 'flask_ask_server_options') |
122 self.askForServerOptionsAct.setStatusTip(self.tr( |
122 self.askForServerOptionsAct.setStatusTip(self.tr( |
132 |
132 |
133 ############################### |
133 ############################### |
134 ## shell action below ## |
134 ## shell action below ## |
135 ############################### |
135 ############################### |
136 |
136 |
137 self.runPythonShellAct = E5Action( |
137 self.runPythonShellAct = EricAction( |
138 self.tr('Start Flask Python Console'), |
138 self.tr('Start Flask Python Console'), |
139 self.tr('Start Flask &Python Console'), |
139 self.tr('Start Flask &Python Console'), |
140 0, 0, |
140 0, 0, |
141 self, 'flask_python_console') |
141 self, 'flask_python_console') |
142 self.runPythonShellAct.setStatusTip(self.tr( |
142 self.runPythonShellAct.setStatusTip(self.tr( |
150 |
150 |
151 ################################ |
151 ################################ |
152 ## routes action below ## |
152 ## routes action below ## |
153 ################################ |
153 ################################ |
154 |
154 |
155 self.showRoutesAct = E5Action( |
155 self.showRoutesAct = EricAction( |
156 self.tr('Show Routes'), |
156 self.tr('Show Routes'), |
157 self.tr('Show &Routes'), |
157 self.tr('Show &Routes'), |
158 0, 0, |
158 0, 0, |
159 self, 'flask_show_routes') |
159 self, 'flask_show_routes') |
160 self.showRoutesAct.setStatusTip(self.tr( |
160 self.showRoutesAct.setStatusTip(self.tr( |
168 |
168 |
169 ################################## |
169 ################################## |
170 ## documentation action below ## |
170 ## documentation action below ## |
171 ################################## |
171 ################################## |
172 |
172 |
173 self.documentationAct = E5Action( |
173 self.documentationAct = EricAction( |
174 self.tr('Documentation'), |
174 self.tr('Documentation'), |
175 self.tr('D&ocumentation'), |
175 self.tr('D&ocumentation'), |
176 0, 0, |
176 0, 0, |
177 self, 'flask_documentation') |
177 self, 'flask_documentation') |
178 self.documentationAct.setStatusTip(self.tr( |
178 self.documentationAct.setStatusTip(self.tr( |
186 |
186 |
187 ############################## |
187 ############################## |
188 ## about action below ## |
188 ## about action below ## |
189 ############################## |
189 ############################## |
190 |
190 |
191 self.aboutFlaskAct = E5Action( |
191 self.aboutFlaskAct = EricAction( |
192 self.tr('About Flask'), |
192 self.tr('About Flask'), |
193 self.tr('About &Flask'), |
193 self.tr('About &Flask'), |
194 0, 0, |
194 0, 0, |
195 self, 'flask_about') |
195 self, 'flask_about') |
196 self.aboutFlaskAct.setStatusTip(self.tr( |
196 self.aboutFlaskAct.setStatusTip(self.tr( |
207 |
207 |
208 ###################################### |
208 ###################################### |
209 ## configuration action below ## |
209 ## configuration action below ## |
210 ###################################### |
210 ###################################### |
211 |
211 |
212 self.flaskConfigAct = E5Action( |
212 self.flaskConfigAct = EricAction( |
213 self.tr('Configure Flask for Project'), |
213 self.tr('Configure Flask for Project'), |
214 self.tr('Configure Flask for &Project'), |
214 self.tr('Configure Flask for &Project'), |
215 0, 0, |
215 0, 0, |
216 self, 'flask_config_for_project') |
216 self, 'flask_config_for_project') |
217 self.flaskConfigAct.setStatusTip(self.tr( |
217 self.flaskConfigAct.setStatusTip(self.tr( |
288 |
288 |
289 def projectOpenedHooks(self): |
289 def projectOpenedHooks(self): |
290 """ |
290 """ |
291 Public method to add our hook methods. |
291 Public method to add our hook methods. |
292 """ |
292 """ |
293 if self.__e5project.getProjectType() == "Flask": |
293 if self.__ericProject.getProjectType() == "Flask": |
294 self.__formsBrowser = ( |
294 self.__formsBrowser = ( |
295 e5App().getObject("ProjectBrowser") |
295 ericApp().getObject("ProjectBrowser") |
296 .getProjectBrowser("forms")) |
296 .getProjectBrowser("forms")) |
297 self.__formsBrowser.addHookMethodAndMenuEntry( |
297 self.__formsBrowser.addHookMethodAndMenuEntry( |
298 "newForm", self.newForm, self.tr("New template...")) |
298 "newForm", self.newForm, self.tr("New template...")) |
299 |
299 |
300 self.__determineCapabilities() |
300 self.__determineCapabilities() |
324 @type str |
324 @type str |
325 """ |
325 """ |
326 from .FormSelectionDialog import FormSelectionDialog |
326 from .FormSelectionDialog import FormSelectionDialog |
327 |
327 |
328 dlg = FormSelectionDialog() |
328 dlg = FormSelectionDialog() |
329 if dlg.exec() == QDialog.Accepted: |
329 if dlg.exec() == QDialog.DialogCode.Accepted: |
330 template = dlg.getTemplateText() |
330 template = dlg.getTemplateText() |
331 |
331 |
332 fileFilters = self.tr( |
332 fileFilters = self.tr( |
333 "HTML Files (*.html);;" |
333 "HTML Files (*.html);;" |
334 "HTML Files (*.htm);;" |
334 "HTML Files (*.htm);;" |
335 "All Files (*)") |
335 "All Files (*)") |
336 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( |
336 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
337 self.__ui, |
337 self.__ui, |
338 self.tr("New Form"), |
338 self.tr("New Form"), |
339 dirPath, |
339 dirPath, |
340 fileFilters, |
340 fileFilters, |
341 None, |
341 None, |
342 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) |
342 EricFileDialog.Options(EricFileDialog.DontConfirmOverwrite)) |
343 if fname: |
343 if fname: |
344 ext = QFileInfo(fname).suffix() |
344 ext = QFileInfo(fname).suffix() |
345 if not ext: |
345 if not ext: |
346 ex = selectedFilter.split("(*")[1].split(")")[0] |
346 ex = selectedFilter.split("(*")[1].split(")")[0] |
347 if ex: |
347 if ex: |
348 fname += ex |
348 fname += ex |
349 |
349 |
350 if os.path.exists(fname): |
350 if os.path.exists(fname): |
351 res = E5MessageBox.yesNo( |
351 res = EricMessageBox.yesNo( |
352 self.__ui, |
352 self.__ui, |
353 self.tr("New Form"), |
353 self.tr("New Form"), |
354 self.tr("""The file already exists! Overwrite""" |
354 self.tr("""The file already exists! Overwrite""" |
355 """ it?"""), |
355 """ it?"""), |
356 icon=E5MessageBox.Warning) |
356 icon=EricMessageBox.Warning) |
357 if not res: |
357 if not res: |
358 # user selected to not overwrite |
358 # user selected to not overwrite |
359 return |
359 return |
360 |
360 |
361 try: |
361 try: |
362 with open(fname, "w", encoding="utf-8") as f: |
362 with open(fname, "w", encoding="utf-8") as f: |
363 f.write(template) |
363 f.write(template) |
364 except OSError as err: |
364 except OSError as err: |
365 E5MessageBox.critical( |
365 EricMessageBox.critical( |
366 self.__ui, |
366 self.__ui, |
367 self.tr("New Form"), |
367 self.tr("New Form"), |
368 self.tr("<p>The new form file <b>{0}</b> could" |
368 self.tr("<p>The new form file <b>{0}</b> could" |
369 " not be created.</p><p>Problem: {1}</p>") |
369 " not be created.</p><p>Problem: {1}</p>") |
370 .format(fname, str(err))) |
370 .format(fname, str(err))) |
371 return |
371 return |
372 |
372 |
373 self.__e5project.appendFile(fname) |
373 self.__ericProject.appendFile(fname) |
374 self.__formsBrowser.sourceFile.emit(fname) |
374 self.__formsBrowser.sourceFile.emit(fname) |
375 |
375 |
376 ################################################################## |
376 ################################################################## |
377 ## methods below implement virtual environment handling |
377 ## methods below implement virtual environment handling |
378 ################################################################## |
378 ################################################################## |
382 Public method to get the path of the virtual environment. |
382 Public method to get the path of the virtual environment. |
383 |
383 |
384 @return path of the virtual environment |
384 @return path of the virtual environment |
385 @rtype str |
385 @rtype str |
386 """ |
386 """ |
387 language = self.__e5project.getProjectLanguage() |
387 language = self.__ericProject.getProjectLanguage() |
388 if language == "Python3": |
388 if language == "Python3": |
389 # get project specific virtual environment name |
389 # get project specific virtual environment name |
390 venvName = self.getData("flask", "virtual_environment_name") |
390 venvName = self.getData("flask", "virtual_environment_name") |
391 if not venvName: |
391 if not venvName: |
392 venvName = self.__plugin.getPreferences( |
392 venvName = self.__plugin.getPreferences( |
522 Private slot to show some info about Flask. |
522 Private slot to show some info about Flask. |
523 """ |
523 """ |
524 versions = self.getFlaskVersionStrings() |
524 versions = self.getFlaskVersionStrings() |
525 url = "https://palletsprojects.com/p/flask/" |
525 url = "https://palletsprojects.com/p/flask/" |
526 |
526 |
527 msgBox = E5MessageBox.E5MessageBox( |
527 msgBox = EricMessageBox.EricMessageBox( |
528 E5MessageBox.Question, |
528 EricMessageBox.Question, |
529 self.tr("About Flask"), |
529 self.tr("About Flask"), |
530 self.tr( |
530 self.tr( |
531 "<p>Flask is a lightweight WSGI web application framework." |
531 "<p>Flask is a lightweight WSGI web application framework." |
532 " It is designed to make getting started quick and easy," |
532 " It is designed to make getting started quick and easy," |
533 " with the ability to scale up to complex applications.</p>" |
533 " with the ability to scale up to complex applications.</p>" |
540 "</table></p>", |
540 "</table></p>", |
541 "Do not translate the program names." |
541 "Do not translate the program names." |
542 ).format(versions["flask"], versions["werkzeug"], |
542 ).format(versions["flask"], versions["werkzeug"], |
543 versions["python"], url), |
543 versions["python"], url), |
544 modal=True, |
544 modal=True, |
545 buttons=E5MessageBox.Ok) |
545 buttons=EricMessageBox.Ok) |
546 msgBox.setIconPixmap(UI.PixmapCache.getPixmap( |
546 msgBox.setIconPixmap(UI.PixmapCache.getPixmap( |
547 os.path.join("ProjectFlask", "icons", |
547 os.path.join("ProjectFlask", "icons", |
548 "flask64-{0}".format(self.__iconSuffix)))) |
548 "flask64-{0}".format(self.__iconSuffix)))) |
549 msgBox.exec() |
549 msgBox.exec() |
550 |
550 |
593 working directory. |
593 working directory. |
594 |
594 |
595 @return tuple containing the working directory and the application name |
595 @return tuple containing the working directory and the application name |
596 @rtype tuple of (str, str) |
596 @rtype tuple of (str, str) |
597 """ |
597 """ |
598 mainScript = self.__e5project.getMainScript(normalized=True) |
598 mainScript = self.__ericProject.getMainScript(normalized=True) |
599 if not mainScript: |
599 if not mainScript: |
600 E5MessageBox.critical( |
600 EricMessageBox.critical( |
601 self.__ui, |
601 self.__ui, |
602 self.tr("Prepare Environment"), |
602 self.tr("Prepare Environment"), |
603 self.tr("""The project has no configured main script""" |
603 self.tr("""The project has no configured main script""" |
604 """ (= Flask application). Aborting...""")) |
604 """ (= Flask application). Aborting...""")) |
605 return "", None |
605 return "", None |
624 """ |
624 """ |
625 if category not in self.__projectData: |
625 if category not in self.__projectData: |
626 self.__projectData[category] = {} |
626 self.__projectData[category] = {} |
627 |
627 |
628 if not self.__projectData[category]: |
628 if not self.__projectData[category]: |
629 data = self.__e5project.getData( |
629 data = self.__ericProject.getData( |
630 "PROJECTTYPESPECIFICDATA", category) |
630 "PROJECTTYPESPECIFICDATA", category) |
631 if data is not None: |
631 if data is not None: |
632 self.__projectData[category] = data |
632 self.__projectData[category] = data |
633 |
633 |
634 data = self.__projectData[category] |
634 data = self.__projectData[category] |
655 """ |
655 """ |
656 if category not in self.__projectData: |
656 if category not in self.__projectData: |
657 self.__projectData[category] = {} |
657 self.__projectData[category] = {} |
658 |
658 |
659 if not self.__projectData[category]: |
659 if not self.__projectData[category]: |
660 data = self.__e5project.getData( |
660 data = self.__ericProject.getData( |
661 "PROJECTTYPESPECIFICDATA", category) |
661 "PROJECTTYPESPECIFICDATA", category) |
662 if data is not None: |
662 if data is not None: |
663 self.__projectData[category] = data |
663 self.__projectData[category] = data |
664 |
664 |
665 if not key: |
665 if not key: |
667 self.__projectData[category] = value |
667 self.__projectData[category] = value |
668 else: |
668 else: |
669 # update individual entry |
669 # update individual entry |
670 self.__projectData[category][key] = value |
670 self.__projectData[category][key] = value |
671 |
671 |
672 self.__e5project.setData( |
672 self.__ericProject.setData( |
673 "PROJECTTYPESPECIFICDATA", category, self.__projectData[category]) |
673 "PROJECTTYPESPECIFICDATA", category, self.__projectData[category]) |
674 |
674 |
675 def __determineCapabilities(self): |
675 def __determineCapabilities(self): |
676 """ |
676 """ |
677 Private method to determine capabilities provided by supported |
677 Private method to determine capabilities provided by supported |
719 """ |
719 """ |
720 from .FlaskConfigDialog import FlaskConfigDialog |
720 from .FlaskConfigDialog import FlaskConfigDialog |
721 |
721 |
722 config = self.getData("flask", "") |
722 config = self.getData("flask", "") |
723 dlg = FlaskConfigDialog(config, self) |
723 dlg = FlaskConfigDialog(config, self) |
724 if dlg.exec() == QDialog.Accepted: |
724 if dlg.exec() == QDialog.DialogCode.Accepted: |
725 config = dlg.getConfiguration() |
725 config = dlg.getConfiguration() |
726 self.setData("flask", "", config) |
726 self.setData("flask", "", config) |
727 self.__setIgnoreVirtualEnvironment() |
727 self.__setIgnoreVirtualEnvironment() |
728 self.__setDebugEnvironment() |
728 self.__setDebugEnvironment() |
729 |
729 |
739 to the list of ignore files/directories. |
739 to the list of ignore files/directories. |
740 """ |
740 """ |
741 virtenvName = self.getData("flask", "virtual_environment_name") |
741 virtenvName = self.getData("flask", "virtual_environment_name") |
742 if virtenvName: |
742 if virtenvName: |
743 virtenvPath = self.getVirtualEnvironment() |
743 virtenvPath = self.getVirtualEnvironment() |
744 if self.__e5project.startswithProjectPath(virtenvPath): |
744 if self.__ericProject.startswithProjectPath(virtenvPath): |
745 relVirtenvPath = self.__e5project.getRelativeUniversalPath( |
745 relVirtenvPath = self.__ericProject.getRelativeUniversalPath( |
746 virtenvPath) |
746 virtenvPath) |
747 if relVirtenvPath not in self.__e5project.pdata["FILETYPES"]: |
747 if relVirtenvPath not in self.__ericProject.pdata["FILETYPES"]: |
748 self.__e5project.pdata["FILETYPES"][relVirtenvPath] = ( |
748 self.__ericProject.pdata["FILETYPES"][relVirtenvPath] = ( |
749 "__IGNORE__" |
749 "__IGNORE__" |
750 ) |
750 ) |
751 self.__e5project.setDirty(True) |
751 self.__ericProject.setDirty(True) |
752 |
752 |
753 def __setDebugEnvironment(self): |
753 def __setDebugEnvironment(self): |
754 """ |
754 """ |
755 Private method to set the virtual environment as the selected debug |
755 Private method to set the virtual environment as the selected debug |
756 environment. |
756 environment. |
757 """ |
757 """ |
758 language = self.__e5project.getProjectLanguage() |
758 language = self.__ericProject.getProjectLanguage() |
759 if language == "Python3": |
759 if language == "Python3": |
760 # get project specific virtual environment name |
760 # get project specific virtual environment name |
761 venvName = self.getData("flask", "virtual_environment_name") |
761 venvName = self.getData("flask", "virtual_environment_name") |
762 if not venvName: |
762 if not venvName: |
763 venvName = self.__plugin.getPreferences( |
763 venvName = self.__plugin.getPreferences( |
764 "VirtualEnvironmentNamePy3") |
764 "VirtualEnvironmentNamePy3") |
765 if venvName: |
765 if venvName: |
766 self.__e5project.debugProperties["VIRTUALENV"] = venvName |
766 self.__ericProject.debugProperties["VIRTUALENV"] = venvName |
767 |
767 |
768 ################################################################## |
768 ################################################################## |
769 ## slot below implements documentation function |
769 ## slot below implements documentation function |
770 ################################################################## |
770 ################################################################## |
771 |
771 |
848 """ |
848 """ |
849 Private method to terminate the current Python console. |
849 Private method to terminate the current Python console. |
850 """ |
850 """ |
851 if ( |
851 if ( |
852 self.__shellProcess is not None and |
852 self.__shellProcess is not None and |
853 self.__shellProcess.state() != QProcess.NotRunning |
853 self.__shellProcess.state() != QProcess.ProcessState.NotRunning |
854 ): |
854 ): |
855 self.__shellProcess.terminate() |
855 self.__shellProcess.terminate() |
856 QTimer.singleShot(2000, self.__shellProcess.kill) |
856 QTimer.singleShot(2000, self.__shellProcess.kill) |
857 self.__shellProcess.waitForFinished(3000) |
857 self.__shellProcess.waitForFinished(3000) |
858 |
858 |