Sat, 31 Dec 2022 16:27:48 +0100
Updated copyright for 2023.
# -*- coding: utf-8 -*- # Copyright (c) 2013 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the Django project support. """ import contextlib import os import re import shutil from PyQt6.QtCore import ( QFileInfo, QIODeviceBase, QObject, QProcess as QProcessPyQt, QTimer, QUrl, ) from PyQt6.QtGui import QDesktopServices from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu from eric7 import Preferences, Utilities try: from eric7.EricGui import EricPixmapCache except ImportError: from UI import PixmapCache as EricPixmapCache from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets import EricFileDialog, EricMessageBox from eric7.EricWidgets.EricApplication import ericApp try: from eric7.SystemUtilities.OSUtilities import isWindowsPlatform except ImportError: # imports for eric < 23.1 from eric7.Globals import isWindowsPlatform try: from eric7.SystemUtilities.FileSystemUtilities import ( getExecutablePath, getExecutablePaths, isinpath, ) except ImportError: # imports for eric < 23.1 from eric7.Utilities import getExecutablePath, getExecutablePaths, isinpath from .DjangoDialog import DjangoDialog class DjangoNoSiteSelectedError(Exception): """ Exception thrown to signal, that there is no current site. """ pass class QProcess(QProcessPyQt): """ Class transforming the call arguments in case of gnome-terminal. """ def start(self, cmd, args=None, mode=QIODeviceBase.OpenModeFlag.ReadWrite): """ Public method to start the given program (cmd) in a new process, if none is already running, passing the command line arguments in args. @param cmd start the given program cmd @type str @param args list of parameters @type list of str @param mode access mode @type QIODeviceBase.OpenMode """ if args is None: args = [] if ( cmd.endswith( ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal") ) and "-e" in args ): index = args.index("-e") + 1 cargs = " ".join(args[index:]) args[index:] = [cargs] super().start(cmd, args, mode) @staticmethod def startDetached(cmd, args=None, path=""): """ Public static method to start the given program (cmd) in a new process, if none is already running, passing the command line arguments in args. @param cmd start the given program cmd @type str @param args list of parameters @type list of str @param path new working directory @type str @return tuple of successful start and process id @rtype tuple of (bool, int) """ if args is None: args = [] if ( cmd.endswith( ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal") ) and "-e" in args ): index = args.index("-e") + 1 cargs = " ".join(args[index:]) args[index:] = [cargs] return QProcessPyQt.startDetached(cmd, args, path) class Project(QObject): """ Class implementing the Django project support. """ RecentApplicationsKey = "Django/RecentApplications" RecentDatabaseNamesKey = "Django/RecentDatabaseNames" def __init__(self, plugin, iconSuffix, parent=None): """ Constructor @param plugin reference to the plugin object @type ProjectDjangoPlugin @param iconSuffix suffix for the icons @type str @param parent parent @type QObject """ super().__init__(parent) self.__plugin = plugin self.__iconSuffix = iconSuffix self.__ui = parent self.__ericProject = ericApp().getObject("Project") self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager") self.__hooksInstalled = False self.__menus = {} # dictionary with references to menus self.__serverProc = None self.__testServerProc = None self.__recentApplications = [] self.__loadRecentApplications() self.__recentDatabaseNames = [] self.__loadRecentDatabaseNames() self.__recentTestData = { "RecentTestLabels": [], "RecentTestTags": [], "RecentTestExcludeTags": [], } self.__loadRecentTestData() def initActions(self): """ Public method to define the Django actions. """ self.actions = [] self.selectSiteAct = EricAction( self.tr("Current Project"), "", 0, 0, self, "django_current_project" ) self.selectSiteAct.setStatusTip(self.tr("Selects the current project")) self.selectSiteAct.setWhatsThis( self.tr( """<b>Current Project</b>""" """<p>Selects the current project. Used for multi-project """ """Django projects to switch between the projects.</p>""" ) ) self.selectSiteAct.triggered.connect(self.__selectSite) self.actions.append(self.selectSiteAct) self.__setCurrentSite(None) ############################## ## start actions below ## ############################## self.startProjectAct = EricAction( self.tr("Start Project"), self.tr("Start &Project"), 0, 0, self, "django_start_project", ) self.startProjectAct.setStatusTip(self.tr("Starts a new Django project")) self.startProjectAct.setWhatsThis( self.tr( """<b>Start Project</b>""" """<p>Starts a new Django project using "django-admin.py""" """ startproject".</p>""" ) ) self.startProjectAct.triggered.connect(self.__startProject) self.actions.append(self.startProjectAct) self.startGlobalApplicationAct = EricAction( self.tr("Start Application (global)"), self.tr("Start Application (&global)"), 0, 0, self, "django_start_global_application", ) self.startGlobalApplicationAct.setStatusTip( self.tr("Starts a new global Django application") ) self.startGlobalApplicationAct.setWhatsThis( self.tr( """<b>Start Application (global)</b>""" """<p>Starts a new global Django application using""" """ "django-admin.py startapp".</p>""" ) ) self.startGlobalApplicationAct.triggered.connect(self.__startGlobalApplication) self.actions.append(self.startGlobalApplicationAct) self.startLocalApplicationAct = EricAction( self.tr("Start Application (local)"), self.tr("Start Application (&local)"), 0, 0, self, "django_start_local_application", ) self.startLocalApplicationAct.setStatusTip( self.tr("Starts a new local Django application") ) self.startLocalApplicationAct.setWhatsThis( self.tr( """<b>Start Application (local)</b>""" """<p>Starts a new local Django application using""" """ "manage.py startapp".</p>""" ) ) self.startLocalApplicationAct.triggered.connect(self.__startLocalApplication) self.actions.append(self.startLocalApplicationAct) ############################## ## run actions below ## ############################## self.runServerAct = EricAction( self.tr("Run Server"), self.tr("Run &Server"), 0, 0, self, "django_run_server", ) self.runServerAct.setStatusTip(self.tr("Starts the Django Web server")) self.runServerAct.setWhatsThis( self.tr( """<b>Run Server</b>""" """<p>Starts the Django Web server using "manage.py""" """ runserver".</p>""" ) ) self.runServerAct.triggered.connect(self.__runServer) self.actions.append(self.runServerAct) self.runBrowserAct = EricAction( self.tr("Run Web-Browser"), self.tr("Run &Web-Browser"), 0, 0, self, "django_run_browser", ) self.runBrowserAct.setStatusTip( self.tr( "Starts the default Web-Browser with the URL of the Django Web" " server" ) ) self.runBrowserAct.setWhatsThis( self.tr( """<b>Run Web-Browser</b>""" """<p>Starts the default Web-Browser with the URL of the """ """Django Web server.</p>""" ) ) self.runBrowserAct.triggered.connect(self.__runBrowser) self.actions.append(self.runBrowserAct) ############################## ## caching actions below ## ############################## self.createCacheTableAct = EricAction( self.tr("Create Cache Tables"), self.tr("C&reate Cache Tables"), 0, 0, self, "django_create_cache_tables", ) self.createCacheTableAct.setStatusTip( self.tr("Creates the tables needed to use the SQL cache backend") ) self.createCacheTableAct.setWhatsThis( self.tr( """<b>Create Cache Tables</b>""" """<p>Creates the tables needed to use the SQL cache""" """ backend.</p>""" ) ) self.createCacheTableAct.triggered.connect(self.__createCacheTables) self.actions.append(self.createCacheTableAct) ############################## ## help action below ## ############################## self.helpAct = EricAction( self.tr("Help"), self.tr("&Help"), 0, 0, self, "django_help" ) self.helpAct.setStatusTip(self.tr("Shows the Django help index")) self.helpAct.setWhatsThis( self.tr("""<b>Help</b>""" """<p>Shows the Django help index page.</p>""") ) self.helpAct.triggered.connect(self.__showHelpIndex) self.actions.append(self.helpAct) ############################## ## about action below ## ############################## self.aboutDjangoAct = EricAction( self.tr("About Django"), self.tr("About D&jango"), 0, 0, self, "django_about", ) self.aboutDjangoAct.setStatusTip(self.tr("Shows some information about Django")) self.aboutDjangoAct.setWhatsThis( self.tr( """<b>About Django</b>""" """<p>Shows some information about Django.</p>""" ) ) self.aboutDjangoAct.triggered.connect(self.__djangoInfo) self.actions.append(self.aboutDjangoAct) ############################## ## check action below ## ############################## self.checkAct = EricAction( self.tr("Check Project"), self.tr("Check Project"), 0, 0, self, "django_check_project", ) self.checkAct.setStatusTip( self.tr("Inspects the Django project for common problems") ) self.checkAct.setWhatsThis( self.tr( """<b>Check Project</b>""" """<p>This inspects the Django project for common problems.</p>""" ) ) self.checkAct.triggered.connect(self.__performCheck) self.actions.append(self.checkAct) self.__initDatabaseActions() self.__initDatabaseSqlActions() self.__initMigrationActions() self.__initToolsActions() self.__initTestingActions() self.__initAuthorizationActions() self.__initSessionActions() def __initDatabaseActions(self): """ Private method to define the database related actions. """ self.selectDatabaseNameAct = EricAction( self.tr("Current Database"), "", 0, 0, self, "django_current_database" ) self.selectDatabaseNameAct.setStatusTip(self.tr("Selects the current database")) self.selectDatabaseNameAct.setWhatsThis( self.tr( """<b>Current Database</b>""" """<p>Selects the database name to be used by all database""" """ actions. An empty database name indicates to use the default""" """ name.</p>""" ) ) self.selectDatabaseNameAct.triggered.connect(self.__selectDatabaseName) self.actions.append(self.selectDatabaseNameAct) self.__setCurrentDatabase(None) self.inspectDatabaseAct = EricAction( self.tr("Introspect"), self.tr("&Introspect"), 0, 0, self, "django_database_inspect", ) self.inspectDatabaseAct.setStatusTip( self.tr( "Introspects the database tables and outputs a Django model" " module" ) ) self.inspectDatabaseAct.setWhatsThis( self.tr( """<b>Introspect</b>""" """<p>Introspects the database tables and outputs a """ """Django model module.</p>""" ) ) self.inspectDatabaseAct.triggered.connect(self.__databaseInspect) self.actions.append(self.inspectDatabaseAct) self.flushDatabaseAct = EricAction( self.tr("Flush"), self.tr("&Flush"), 0, 0, self, "django_database_flush" ) self.flushDatabaseAct.setStatusTip( self.tr( "Returns all database tables to the state just after their" " installation" ) ) self.flushDatabaseAct.setWhatsThis( self.tr( """<b>Flush</b>""" """<p>Returns all database tables to the state """ """just after their installation.</p>""" ) ) self.flushDatabaseAct.triggered.connect(self.__databaseFlush) self.actions.append(self.flushDatabaseAct) self.databaseClientAct = EricAction( self.tr("Start Client Console"), self.tr("Start &Client Console"), 0, 0, self, "django_database_client", ) self.databaseClientAct.setStatusTip( self.tr("Starts a console window for the database client") ) self.databaseClientAct.setWhatsThis( self.tr( """<b>Start Client Console</b>""" """<p>Starts a console window for the database client.</p>""" ) ) self.databaseClientAct.triggered.connect(self.__runDatabaseClient) self.actions.append(self.databaseClientAct) def __initDatabaseSqlActions(self): """ Private method to define the database SQL related actions. """ self.databaseSqlFlushAct = EricAction( self.tr("Flush Database"), self.tr("&Flush Database"), 0, 0, self, "django_database_sql_flush_database", ) self.databaseSqlFlushAct.setStatusTip( self.tr( "Prints a list of statements to return all database tables to the" " state just after their installation" ) ) self.databaseSqlFlushAct.setWhatsThis( self.tr( """<b>Flush Database</b>""" """<p>Prints a list of statements to return all database tables""" """ to the state just after their installation.</p>""" ) ) self.databaseSqlFlushAct.triggered.connect(self.__databaseSqlFlushDatabase) self.actions.append(self.databaseSqlFlushAct) self.databaseSqlResetSeqAct = EricAction( self.tr("Reset Sequences"), self.tr("Reset &Sequences"), 0, 0, self, "django_database_sql_reset_sequences", ) self.databaseSqlResetSeqAct.setStatusTip( self.tr( "Prints the SQL statements for resetting sequences for " "one or more applications" ) ) self.databaseSqlResetSeqAct.setWhatsThis( self.tr( """<b>Reset Sequences</b>""" """<p>Prints the SQL statements for resetting sequences for """ """one or more applications.</p>""" ) ) self.databaseSqlResetSeqAct.triggered.connect(self.__databaseSqlResetSequences) self.actions.append(self.databaseSqlResetSeqAct) self.databaseSqlMigrateAct = EricAction( self.tr("Apply Migration"), self.tr("&Apply Migration"), 0, 0, self, "django_database_sql_apply_migration", ) self.databaseSqlMigrateAct.setStatusTip( self.tr( "Prints the SQL statements to apply a migration of an" " application" ) ) self.databaseSqlMigrateAct.setWhatsThis( self.tr( """<b>Apply Migration</b>""" """<p>Prints the SQL statements to apply a migration of an""" """ application.</p>""" ) ) self.databaseSqlMigrateAct.triggered.connect(self.__databaseSqlMigrate) self.actions.append(self.databaseSqlMigrateAct) self.databaseSqlMigrateBackwardsAct = EricAction( self.tr("Unapply Migration"), self.tr("&Unapply Migration"), 0, 0, self, "django_database_sql_unapply_migration", ) self.databaseSqlMigrateBackwardsAct.setStatusTip( self.tr( "Prints the SQL statements to unapply a migration of an" " application" ) ) self.databaseSqlMigrateBackwardsAct.setWhatsThis( self.tr( """<b>Unapply Migration</b>""" """<p>Prints the SQL statements to unapply a migration of an""" """ application.</p>""" ) ) self.databaseSqlMigrateBackwardsAct.triggered.connect( lambda: self.__databaseSqlMigrate(backwards=True) ) self.actions.append(self.databaseSqlMigrateBackwardsAct) def __initToolsActions(self): """ Private method to define the tool actions. """ self.diffSettingsAct = EricAction( self.tr("Diff Settings"), self.tr("&Diff Settings"), 0, 0, self, "django_tools_diffsettings", ) self.diffSettingsAct.setStatusTip( self.tr("Shows the modification made to the settings") ) self.diffSettingsAct.setWhatsThis( self.tr( """<b>Diff Settings</b>""" """<p>Shows the modification made to the settings.</p>""" ) ) self.diffSettingsAct.triggered.connect(self.__diffSettings) self.actions.append(self.diffSettingsAct) self.runPythonShellAct = EricAction( self.tr("Start Python Console"), self.tr("Start &Python Console"), 0, 0, self, "django_tools_pythonconsole", ) self.runPythonShellAct.setStatusTip( self.tr("Starts a Python interactive interpreter") ) self.runPythonShellAct.setWhatsThis( self.tr( """<b>Start Python Console</b>""" """<p>Starts a Python interactive interpreter.</p>""" ) ) self.runPythonShellAct.triggered.connect(self.__runPythonShell) self.actions.append(self.runPythonShellAct) self.testEmailAct = EricAction( self.tr("Send Test Email"), self.tr("Send Test &Email"), 0, 0, self, "django_tools_sendtestemail", ) self.testEmailAct.setStatusTip(self.tr("Send a test email through Django")) self.testEmailAct.setWhatsThis( self.tr( """<b>Send Test Email</b>""" """<p>Sends a test email to confirm email sending through Django""" """ is working.</p>""" ) ) self.testEmailAct.triggered.connect(self.__sendTestEmail) self.actions.append(self.testEmailAct) def __initTestingActions(self): """ Private method to define the testing actions. """ self.dumpDataAct = EricAction( self.tr("Dump Data"), self.tr("&Dump Data"), 0, 0, self, "django_tools_dumpdata", ) self.dumpDataAct.setStatusTip(self.tr("Dump the database data to a fixture")) self.dumpDataAct.setWhatsThis( self.tr( """<b>Dump Data</b>""" """<p>Dump the database data to a fixture.</p>""" ) ) self.dumpDataAct.triggered.connect(self.__dumpData) self.actions.append(self.dumpDataAct) self.loadDataAct = EricAction( self.tr("Load Data"), self.tr("&Load Data"), 0, 0, self, "django_tools_loaddata", ) self.loadDataAct.setStatusTip(self.tr("Load data from fixture files")) self.loadDataAct.setWhatsThis( self.tr("""<b>Load Data</b>""" """<p>Load data from fixture files.</p>""") ) self.loadDataAct.triggered.connect(self.__loadData) self.actions.append(self.loadDataAct) self.runTestAct = EricAction( self.tr("Run Testsuite"), self.tr("Run &Testsuite"), 0, 0, self, "django_tools_run_test", ) self.runTestAct.setStatusTip( self.tr("Run the test suite for applications or the whole site") ) self.runTestAct.setWhatsThis( self.tr( """<b>Run Testsuite</b>""" """<p>Run the test suite for applications or the whole site.</p>""" ) ) self.runTestAct.triggered.connect(self.__runTestSuite) self.actions.append(self.runTestAct) self.runDeprecationTestAct = EricAction( self.tr("Run Testsuite (-Wall)"), self.tr("Run Testsuite (-Wall)"), 0, 0, self, "django_tools_run_deprecation_test", ) self.runDeprecationTestAct.setStatusTip( self.tr( "Run the test suite for applications or the whole site with" " activated deprecation warnings" ) ) self.runDeprecationTestAct.setWhatsThis( self.tr( """<b>Run Testsuite (-Wall)</b>""" """<p>Run the test suite for applications or the whole site""" """ with activated deprecation warnings.</p>""" ) ) self.runDeprecationTestAct.triggered.connect( lambda: self.__runTestSuite(deprecation=True) ) self.actions.append(self.runDeprecationTestAct) self.runTestServerAct = EricAction( self.tr("Run Testserver"), self.tr("Run Test&server"), 0, 0, self, "django_tools_run_test_server", ) self.runTestServerAct.setStatusTip( self.tr("Run a development server with data from a set of fixtures") ) self.runTestServerAct.setWhatsThis( self.tr( """<b>Run Testserver</b>""" """<p>Run a development server with data from a set of""" """ fixtures.</p>""" ) ) self.runTestServerAct.triggered.connect(self.__runTestServer) self.actions.append(self.runTestServerAct) def __initAuthorizationActions(self): """ Private method to define the authorization actions. """ self.changePasswordAct = EricAction( self.tr("Change Password"), self.tr("Change &Password"), 0, 0, self, "django_auth_changepassword", ) self.changePasswordAct.setStatusTip(self.tr("Change the password of a user")) self.changePasswordAct.setWhatsThis( self.tr( """<b>Change Password</b>""" """<p>Change the password of a user of the Django project.</p>""" ) ) self.changePasswordAct.triggered.connect(self.__changePassword) self.actions.append(self.changePasswordAct) self.createSuperUserAct = EricAction( self.tr("Create Superuser"), self.tr("Create &Superuser"), 0, 0, self, "django_auth_createsuperuser", ) self.createSuperUserAct.setStatusTip(self.tr("Create a superuser account")) self.createSuperUserAct.setWhatsThis( self.tr( """<b>Create Superuser</b>""" """<p>Create a superuser account for the Django project.</p>""" ) ) self.createSuperUserAct.triggered.connect(self.__createSuperUser) self.actions.append(self.createSuperUserAct) def __initSessionActions(self): """ Private method to define the session actions. """ self.clearSessionsAct = EricAction( self.tr("Clear Sessions"), self.tr("Clear &Sessions"), 0, 0, self, "django_session_clearsessions", ) self.clearSessionsAct.setStatusTip(self.tr("Clear expired sessions")) self.clearSessionsAct.setWhatsThis( self.tr( """<b>Clear Sessions</b>""" """<p>Clear expired sessions of the Django project.</p>""" ) ) self.clearSessionsAct.triggered.connect(self.__clearSessions) self.actions.append(self.clearSessionsAct) def __initMigrationActions(self): """ Private method to define the migration actions. """ self.showMigrationsAct = EricAction( self.tr("Show Migrations"), self.tr("&Show Migrations"), 0, 0, self, "django_migration_show", ) self.showMigrationsAct.setStatusTip( self.tr("Show a list of available migrations") ) self.showMigrationsAct.setWhatsThis( self.tr( """<b>Show Migrations</b>""" """<p>This shows a list of available migrations of the Django""" """ project and their status.</p>""" ) ) self.showMigrationsAct.triggered.connect(self.__showMigrationsList) self.actions.append(self.showMigrationsAct) self.showMigrationsPlanAct = EricAction( self.tr("Show Migrations Plan"), self.tr("Show Migrations &Plan"), 0, 0, self, "django_migration_show_plan", ) self.showMigrationsPlanAct.setStatusTip( self.tr("Show a list with the migrations plan") ) self.showMigrationsPlanAct.setWhatsThis( self.tr( """<b>Show Migrations Plan</b>""" """<p>This shows a list with the migrations plan of the Django""" """ project.</p>""" ) ) self.showMigrationsPlanAct.triggered.connect(self.__showMigrationsPlan) self.actions.append(self.showMigrationsPlanAct) self.migrateAllAct = EricAction( 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 = EricAction( 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 = EricAction( 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 = EricAction( 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) self.squashMigrationsAct = EricAction( self.tr("Squash Migrations"), self.tr("S&quash Migrations"), 0, 0, self, "django_migration_squash", ) self.squashMigrationsAct.setStatusTip( self.tr("Squash migrations of an application of the project") ) self.squashMigrationsAct.setWhatsThis( self.tr( """<b>Squash Migrations</b>""" """<p>This squashes migrations of an application of the""" """ Django project.</p>""" ) ) self.squashMigrationsAct.triggered.connect(self.__squashMigrations) self.actions.append(self.squashMigrationsAct) def initMenu(self): """ Public method to initialize the Django menu. @return the menu generated @rtype QMenu """ self.__menus = {} # clear menus references menu = QMenu(self.tr("D&jango"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.selectSiteAct) menu.addSeparator() menu.addAction(self.runServerAct) menu.addAction(self.runBrowserAct) menu.addSeparator() menu.addAction(self.startProjectAct) menu.addAction(self.startGlobalApplicationAct) menu.addAction(self.startLocalApplicationAct) menu.addSeparator() menu.addAction(self.checkAct) menu.addSeparator() menu.addMenu(self.__initDatabaseMenu()) menu.addMenu(self.__initMigrationsMenu()) menu.addSeparator() menu.addMenu(self.__initToolsMenu()) menu.addSeparator() menu.addAction(self.createCacheTableAct) menu.addSeparator() menu.addMenu(self.__initTestingMenu()) menu.addSeparator() menu.addMenu(self.__initAuthorizationMenu()) menu.addSeparator() menu.addMenu(self.__initSessionMenu()) menu.addSeparator() menu.addAction(self.aboutDjangoAct) menu.addSeparator() menu.addAction(self.helpAct) self.__menus["main"] = menu return menu def __initDatabaseMenu(self): """ Private method to initialize the database menu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("&Database"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.selectDatabaseNameAct) menu.addSeparator() menu.addAction(self.inspectDatabaseAct) menu.addSeparator() menu.addAction(self.flushDatabaseAct) menu.addSeparator() menu.addAction(self.databaseClientAct) menu.addSeparator() menu.addMenu(self.__initDatabaseSqlMenu()) self.__menus["database"] = menu return menu def __initDatabaseSqlMenu(self): """ Private method to initialize the database SQL submenu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("Show &SQL"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.databaseSqlFlushAct) menu.addAction(self.databaseSqlResetSeqAct) menu.addSeparator() menu.addAction(self.databaseSqlMigrateAct) menu.addAction(self.databaseSqlMigrateBackwardsAct) self.__menus["sql"] = menu return menu def __initMigrationsMenu(self): """ Private method to initialize the Migrations submenu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("&Migrations"), self.__ui) menu.setTearOffEnabled(True) 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) menu.addSeparator() menu.addAction(self.squashMigrationsAct) self.__menus["migrations"] = menu return menu def __initToolsMenu(self): """ Private method to initialize the tools menu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("&Tools"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.diffSettingsAct) menu.addSeparator() menu.addAction(self.runPythonShellAct) menu.addSeparator() menu.addAction(self.testEmailAct) self.__menus["tools"] = menu return menu def __initTestingMenu(self): """ Private method to initialize the testing menu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("T&esting"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.dumpDataAct) menu.addAction(self.loadDataAct) menu.addSeparator() menu.addAction(self.runTestAct) menu.addAction(self.runDeprecationTestAct) menu.addAction(self.runTestServerAct) self.__menus["testing"] = menu return menu def __initAuthorizationMenu(self): """ Private method to initialize the authorization menu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("&Authorization"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.changePasswordAct) menu.addAction(self.createSuperUserAct) self.__menus["authorization"] = menu return menu def __initSessionMenu(self): """ Private method to initialize the authorization menu. @return the menu generated @rtype QMenu """ menu = QMenu(self.tr("&Session"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.clearSessionsAct) self.__menus["session"] = menu return menu def getMenu(self, name): """ Public method to get a reference to the requested menu. @param name name of the menu @type str @return reference to the menu or None, if no menu with the given name exists @rtype QMenu """ if name in self.__menus: return self.__menus[name] else: return None def getMenuNames(self): """ Public method to get the names of all menus. @return menu names @rtype list of str """ return list(self.__menus.keys()) ################################################################## ## methods below implement the various hook related functions ################################################################## def registerOpenHook(self): """ Public method to register the open hook to open a translations file in a translations editor. """ if self.__hooksInstalled: editor = self.__plugin.getPreferences("TranslationsEditor") if editor: self.__translationsBrowser.addHookMethodAndMenuEntry( "open", self.openPOEditor, self.tr("Open with {0}").format(os.path.basename(editor)), ) else: self.__translationsBrowser.removeHookMethod("open") def projectOpenedHooks(self): """ Public method to add our hook methods. """ if self.__ericProject.getProjectType() == "Django": self.__formsBrowser = ( ericApp().getObject("ProjectBrowser").getProjectBrowser("forms") ) self.__formsBrowser.addHookMethodAndMenuEntry( "newForm", self.newForm, self.tr("New template...") ) self.__ericProject.projectLanguageAddedByCode.connect( self.__projectLanguageAdded ) self.__translationsBrowser = ( ericApp().getObject("ProjectBrowser").getProjectBrowser("translations") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateAll", self.updateCatalogs, self.tr("Update all catalogs") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateSelected", self.updateSelectedCatalogs, self.tr("Update selected catalogs"), ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateAllWithObsolete", self.updateCatalogsWithObsolete, self.tr("Update all catalogs (with obsolete)"), ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateSelectedWithObsolete", self.updateSelectedCatalogsWithObsolete, self.tr("Update selected catalogs (with obsolete)"), ) self.__translationsBrowser.addHookMethodAndMenuEntry( "releaseAll", self.compileCatalogs, self.tr("Compile all catalogs") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "releaseSelected", self.compileSelectedCatalogs, self.tr("Compile selected catalogs"), ) self.__hooksInstalled = True self.registerOpenHook() def projectClosedHooks(self): """ Public method to remove our hook methods. """ if self.__hooksInstalled: self.__formsBrowser.removeHookMethod("newForm") self.__formsBrowser = None self.__ericProject.projectLanguageAddedByCode.disconnect( self.__projectLanguageAdded ) self.__translationsBrowser.removeHookMethod("generateAll") self.__translationsBrowser.removeHookMethod("generateSelected") self.__translationsBrowser.removeHookMethod("generateAllWithObsolete") self.__translationsBrowser.removeHookMethod("generateSelectedWithObsolete") self.__translationsBrowser.removeHookMethod("releaseAll") self.__translationsBrowser.removeHookMethod("releaseSelected") self.__translationsBrowser.removeHookMethod("open") self.__translationsBrowser = None self.__hooksInstalled = False def newForm(self, path): """ Public method to create a new form. @param path full directory path for the new form file @type str """ fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( self.__ui, self.tr("New Form"), path, filter, None, EricFileDialog.DontConfirmOverwrite, ) if not fname: # user aborted or didn't enter a filename return ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if os.path.exists(fname): res = EricMessageBox.yesNo( self.__ui, self.tr("New Form"), self.tr("The file already exists! Overwrite it?"), icon=EricMessageBox.Warning, ) if not res: # user selected to not overwrite return try: with open(fname, "w") as f: f.write("<!DOCTYPE html>") f.write("<html>\n") f.write(" <head>\n") f.write(' <meta content="" />\n') f.write(" <title></title>\n") f.write( ' <link rel="stylesheet" type="text/css"' ' href="style.css"/>' ) f.write(" <!--[if lte IE 7]>") f.write( ' <link rel="stylesheet" type="text/css"' ' href="ie.css"/>' ) f.write(" <![endif]-->") f.write(" </head>\n") f.write("\n") f.write(' <body class="bodyclass">\n') f.write(' <div id="container">') f.write(" </div>") f.write(" </body>\n") f.close() f.write("</html>\n") except OSError as e: EricMessageBox.critical( self.__ui, self.tr("New Form"), self.tr( "<p>The new form file <b>{0}</b> could not be" " created.<br> Problem: {1}</p>" ).format(fname, str(e)), ) return self.__ericProject.appendFile(fname) self.__formsBrowser.sourceFile.emit(fname) ################################################################## ## slots below implement general functionality ################################################################## def projectClosed(self): """ Public method to handle the closing of a project. """ if self.__serverProc is not None: self.__serverProcFinished() self.__setCurrentSite(None) def __getExecutablePaths(self, file): """ Private method to build all full paths of an executable file from the environment. @param file filename of the executable @type str @return list of full executable names, if the executable file is accessible via the searchpath defined by the PATH environment variable, or an empty list otherwise. @rtype list of str """ paths = [] if os.path.isabs(file): if os.access(file, os.X_OK): return [file] else: return [] cur_path = os.path.join(os.curdir, file) if os.path.exists(cur_path) and os.access(cur_path, os.X_OK): paths.append(cur_path) path = os.getenv("PATH") # environment variable not defined if path is not None: dirs = path.split(os.pathsep) for directory in dirs: exe = os.path.join(directory, file) if os.access(exe, os.X_OK) and exe not in paths: paths.append(exe) return paths def supportedPythonVariants(self): """ Public method to get the supported Python variants. @return list of supported Python variants @rtype list of str """ variants = [] for variant in ["Python3"]: virtEnv = self.__getVirtualEnvironment(variant) if virtEnv: if self.__getDjangoAdminCommand(variant): variants.append(variant) else: cmd = self.__getDjangoAdminCommand(variant) if isWindowsPlatform(): if cmd: variants.append(variant) else: if cmd: try: fullCmds = getExecutablePaths(cmd) except AttributeError: fullCmds = self.__getExecutablePaths(cmd) for fullCmd in fullCmds: try: with open(fullCmd, "r", encoding="utf-8") as f: l0 = f.readline() except OSError: l0 = "" if self.__isSuitableForVariant(variant, l0): variants.append(variant) break return variants def __isSuitableForVariant(self, variant, line0): """ Private method to test, if a detected command file is suitable for the given Python variant. @param variant Python variant to test for @type str @param line0 first line of the executable @type str @return flag indicating a suitable file was found @rtype bool """ l0 = line0.lower() ok = variant.lower() in l0 or "{0}.".format(variant[-1]) in l0 ok |= "pypy3" in l0 return ok def __getVirtualEnvironment(self, language=""): """ Private method to get the path of the virtual environment. @param language Python variant to get the virtual environment for (one of '' or 'Python3') @type str @return path of the virtual environment @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() venvName = ( self.__plugin.getPreferences("VirtualEnvironmentNamePy3") if language == "Python3" else "" ) if venvName: virtEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName) if not virtEnv: virtEnv = os.path.dirname( self.__virtualEnvManager.getVirtualenvInterpreter(venvName) ) if virtEnv.endswith(("Scripts", "bin")): virtEnv = os.path.dirname(virtEnv) else: virtEnv = "" if virtEnv and not os.path.exists(virtEnv): virtEnv = "" return virtEnv def __getDebugEnvironment(self, language=""): """ Private method to get the path of the debugger environment. @param language Python variant to get the debugger environment for (one of '' or 'Python3') @type str @return path of the debugger environment @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() debugEnv = self.__getVirtualEnvironment(language) if not debugEnv: if language == "Python3": venvName = Preferences.getDebugger("Python3VirtualEnv") else: venvName = "" if venvName: debugEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName) else: debugEnv = "" return debugEnv def __getDjangoAdminCommand(self, language=""): """ Private method to build a django-admin.py command. @param language Python variant to get the django-admin.py command for (one of '' or 'Python3') @type str @return full django-admin.py command @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() virtualEnv = self.__getVirtualEnvironment(language) if virtualEnv: if isWindowsPlatform(): for cmd in [ # standard Python os.path.join(virtualEnv, "Scripts", "django-admin.exe"), # PyPy os.path.join(virtualEnv, "bin", "django-admin.py"), ]: if os.path.exists(cmd): break else: cmd = "" else: cmds = [ os.path.join(virtualEnv, "bin", "django-admin"), os.path.join(virtualEnv, "bin", "django-admin.py"), os.path.join(virtualEnv, "local", "bin", "django-admin"), os.path.join(virtualEnv, "local", "bin", "django-admin.py"), ] for cmd in cmds: if os.path.exists(cmd): break else: cmd = "" else: if isWindowsPlatform(): debugEnv = self.__getDebugEnvironment(language) for cmd in [ # standard Python os.path.join(debugEnv, "Scripts", "django-admin.exe"), # PyPy os.path.join(debugEnv, "bin", "django-admin.py"), ]: if os.path.exists(cmd): break else: cmd = "" else: if language == "Python3": cmds = [ "django-admin3", "django-admin3.py", "django-admin.py-3.10", "django-admin.py-3.9", "django-admin.py-3.8", "django-admin.py-3.7", "django-admin.py-3.6", ] else: cmds = [] cmds.extend(["django-admin", "django-admin.py"]) for cmd in cmds: if isinpath(cmd): break else: cmd = "" return cmd def __getPythonExecutable(self): """ Private method to build the Python command. @return python command @rtype str """ language = self.__ericProject.getProjectLanguage() if language == "Python3": venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3") if not venvName: # if none configured, use the global one venvName = Preferences.getDebugger("Python3VirtualEnv") else: venvName = "" python = ( self.__virtualEnvManager.getVirtualenvInterpreter(venvName) if venvName else "" ) return python def __djangoInfo(self): """ Private slot to show some info about Django. """ version = self.getDjangoVersionString() url = "https://www.djangoproject.com" msgBox = EricMessageBox.EricMessageBox( EricMessageBox.Question, self.tr("About Django"), self.tr( "<p>Django is a high-level Python Web framework that" " encourages rapid development and clean, pragmatic" " design.</p>" "<p><table>" "<tr><td>Version:</td><td>{0}</td></tr>" '<tr><td>URL:</td><td><a href="{1}">' "{1}</a></td></tr>" "</table></p>" ).format(version, url), modal=True, buttons=EricMessageBox.Ok, ) msgBox.setIconPixmap( EricPixmapCache.getPixmap( os.path.join( "ProjectDjango", "icons", "django64-{0}".format(self.__iconSuffix) ) ) ) msgBox.exec() def getDjangoVersionString(self): """ Public method to get the Django version as a string. @return Django version @rtype str """ djangoVersion = "" args = ["--version"] ioEncoding = Preferences.getSystem("IOEncoding") cmd = self.__getDjangoAdminCommand() if cmd: if isWindowsPlatform(): args.insert(0, cmd) cmd = self.__getPythonExecutable() process = QProcess() process.start(cmd, args) procStarted = process.waitForStarted() if procStarted: finished = process.waitForFinished(30000) if finished and process.exitCode() == 0: output = str(process.readAllStandardOutput(), ioEncoding, "replace") djangoVersion = output.splitlines()[0].strip() return djangoVersion def getDjangoVersion(self): """ Public method to get the Django version as a tuple. @return Django version @rtype tuple of int """ djangoVersionStr = self.getDjangoVersionString() djangoVersionList = [] if djangoVersionStr: for part in djangoVersionStr.split("."): try: djangoVersionList.append(int(part)) except ValueError: djangoVersionList.append(part) return tuple(djangoVersionList) def __getApplications(self): """ Private method to ask the user for a list of application names. @return list of application names @rtype list of str """ applStr, ok = QInputDialog.getItem( self.__ui, self.tr("Select Applications"), self.tr("Enter the list of applications separated by spaces."), self.getRecentApplications(), 0, True, ) if ok and applStr != "": self.setMostRecentApplication(applStr) return applStr.split() else: return [] def __loadRecentApplications(self): """ Private method to load the recently used applications list. """ self.__recentApplications = [] Preferences.Prefs.rsettings.sync() ra = Preferences.Prefs.rsettings.value(self.RecentApplicationsKey) if ra is not None: maxRecentApps = self.__plugin.getPreferences("RecentNumberApps") self.__recentApplications = ra[:maxRecentApps] def __saveRecentApplications(self): """ Private method to save the list of recently used applications list. """ Preferences.Prefs.rsettings.setValue( self.RecentApplicationsKey, self.__recentApplications ) Preferences.Prefs.rsettings.sync() def getRecentApplications(self): """ Public method to get the list of recent applications. @return list of recent applications entries (list of strings) """ self.__loadRecentApplications() return self.__recentApplications def setMostRecentApplication(self, applStr): """ Public method to set the most recently used applications entry. @param applStr applications entry @type str """ if applStr in self.__recentApplications: self.__recentApplications.remove(applStr) self.__recentApplications.insert(0, applStr) maxRecentApps = self.__plugin.getPreferences("RecentNumberApps") if len(self.__recentApplications) > maxRecentApps: self.__recentApplications = self.__recentApplications[:maxRecentApps] self.__saveRecentApplications() def __loadRecentTestData(self): """ Private method to load the recently used test data lists. """ self.__recentTestData = { "RecentTestLabels": [], "RecentTestTags": [], "RecentTestExcludeTags": [], } Preferences.Prefs.rsettings.sync() maxRecentTestData = self.__plugin.getPreferences("RecentNumberTestData") for key in self.__recentTestData: recent = Preferences.Prefs.rsettings.value("Django/" + key) if recent is not None: self.__recentTestData[key] = recent[:maxRecentTestData] def __saveRecentTestData(self): """ Private method to save the list of recently used test data. """ for key in self.__recentTestData: Preferences.Prefs.rsettings.setValue( "Django/" + key, self.__recentTestData[key] ) Preferences.Prefs.rsettings.sync() def getRecentTestData(self, key): """ Public method to get the list of recent test data. @param key key (name) of the test data to get @type str @return list of recent test data entries @rtype list of str """ self.__loadRecentTestData() return self.__recentTestData[key] def setMostRecentTestData(self, key, data): """ Public method to set the most recently used test data entry. @param key key (name) of the test data to set @type str @param data test data entry to be set @type str """ if data in self.__recentTestData[key]: self.__recentTestData[key].remove(data) self.__recentTestData[key].insert(0, data) maxRecentTestData = self.__plugin.getPreferences("RecentNumberTestData") if len(self.__recentTestData[key]) > maxRecentTestData: self.__recentTestData[key] = self.__recentTestData[key][:maxRecentTestData] self.__saveRecentTestData() def getProjectPath(self): """ Public method to get the path of the eric7 project. @return path of the eric7 project @rtype str """ return self.__ericProject.getProjectPath() def __showHelpIndex(self): """ Private slot to show the help index page. """ page = os.path.join( os.path.dirname(__file__), "Documentation", "help", "index.html" ) self.__ui.launchHelpViewer(page) def __isSpawningConsole(self, consoleCmd): """ Private method to check, if the given console is a spawning console. @param consoleCmd console command @type str @return tuple of two entries giving an indication, if the console is spawning and the (possibly) cleaned console command @rtype tuple of (bool, str) """ if consoleCmd and consoleCmd[0] == "@": return (True, consoleCmd[1:]) else: return (False, consoleCmd) def __adjustWorkingDirectory(self, args, wd): """ Private method to adjust the working directory in the arguments list. @param args list of arguments to be modified @type list of str @param wd working directory @type str """ if args[0].endswith("konsole") and "--workdir" in args: index = args.index("--workdir") args[index + 1] = wd elif args[0].endswith(("gnome-terminal", "mate-terminal")): for index in range(len(args)): if args[index].startswith("--working-directory="): args[index] = "--working-directory={0}".format(wd) break ################################################################## ## slots below implement creation functions ################################################################## def newProjectCreated(self): """ Public slot to finish up the newly generated project. """ if self.__ericProject.getProjectType() == "Django": ppath = self.__ericProject.getProjectPath() # get rid of an __init__.py file because it would be in our way initModule = os.path.join(ppath, "__init__.py") if os.path.exists(initModule): self.__ericProject.deleteFile("__init__.py") self.__ericProject.saveProject() def startProjectOrApplication(self): """ Public slot to start a new Django project or application. """ if self.__ericProject.getProjectType() == "Django": projectStr = self.tr("Project") applStr = self.tr("Application") selections = ["", projectStr, applStr] selection, ok = QInputDialog.getItem( self.__ui, self.tr("Start Django"), self.tr( "Select if this project should be a " "Django Project or Application.<br />" "Select the empty entry for none." ), selections, 0, False, ) if ok and bool(selection): if selection == projectStr: path, projectName = os.path.split( self.__ericProject.getProjectPath() ) self.__createProject(projectName, path) elif selection == applStr: path, applName = os.path.split(self.__ericProject.getProjectPath()) self.__createApplication(applName, path) def __createProject(self, projectName, path): """ Private slot to create a new Django project. @param projectName name of the new project @type str @param path the directory where the project should be created @type str @return flag indicating a successful creation @rtype bool """ title = self.tr("Start Django Project") # remove the project directory if it exists already ppath = os.path.join(path, projectName) if os.path.exists(ppath): okToRemove = EricMessageBox.yesNo( self.__ui, title, self.tr( """<p>The Django project path <b>{0}</b> exists""" """ already. Shall it be removed and recreated?""" """</p>""" ).format(ppath), ) if not okToRemove: EricMessageBox.information( self.__ui, title, self.tr( """<p>Please add the files to the eric project""" """ manually.</p>""" ), ) return True shutil.rmtree(ppath, ignore_errors=True) args = [] cmd = self.__getDjangoAdminCommand() if cmd: if isWindowsPlatform(): args.append(self.__getPythonExecutable()) args.append(cmd) else: EricMessageBox.critical( self.__ui, title, self.tr( """<p>The <b>django-admin.py</b> script is""" """ not in the path. Aborting...</p>""" ), ) return False args.append("startproject") args.append(projectName) dia = DjangoDialog( title, msgSuccess=self.tr("Django project created successfully.") ) res = dia.startProcess(args, path) if res: dia.exec() # create the base directory for translations i18nPath = os.path.join(path, projectName, "locale") if not os.path.exists(i18nPath): os.makedirs(i18nPath) if os.path.join(path, projectName) == self.__ericProject.getProjectPath(): self.__setCurrentSite("") else: self.__setCurrentSite(projectName) return res def __startProject(self): """ Private slot to start a new Django project. """ projectName, ok = QInputDialog.getText( self.__ui, self.tr("Start Django Project"), self.tr("Enter the name of the new Django project."), QLineEdit.EchoMode.Normal, ) if ok and projectName != "": res = self.__createProject(projectName, self.__ericProject.getProjectPath()) if res: # search for new files and add them to the project sitePath = os.path.join( self.__ericProject.getProjectPath(), projectName ) for entry in os.walk(sitePath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__ericProject.appendFile(fullName) def __createApplication(self, applName, path, isGlobal=True): """ Private slot to create a new Django application. @param applName name of the new application @type str @param path the directory where the application should be created @type str @param isGlobal flag indicating a standalone Django application @type bool @return flag indicating a successful creation @rtype bool """ title = self.tr("Start Django Application") # remove the application directory if it exists already apath = os.path.join(path, applName) if os.path.exists(apath): shutil.rmtree(apath, ignore_errors=True) args = [] if isGlobal: cmd = self.__getDjangoAdminCommand() if cmd: if isWindowsPlatform(): args.append(self.__getPythonExecutable()) args.append(cmd) else: EricMessageBox.critical( self.__ui, title, self.tr( """<p>The <b>django-admin.py</b> script""" """ is not in the path.""" """ Aborting...</p>""" ), ) return False else: args.append(self.__getPythonExecutable()) args.append("manage.py") try: path = self.__sitePath() except DjangoNoSiteSelectedError: return False args.append("startapp") args.append(applName) dia = DjangoDialog( title, msgSuccess=self.tr("Django application created successfully.") ) res = dia.startProcess(args, path) if res: dia.exec() return res def __startGlobalApplication(self): """ Private slot to start a new global Django application. """ applName, ok = QInputDialog.getText( self.__ui, self.tr("Start Global Django Application"), self.tr("Enter the name of the new global Django" " application."), QLineEdit.EchoMode.Normal, ) if ok and applName != "": res = self.__createApplication( applName, self.__ericProject.getProjectPath() ) if res: # search for new files and add them to the project appPath = os.path.join(self.__ericProject.getProjectPath(), applName) for entry in os.walk(appPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__ericProject.appendFile(fullName) def __startLocalApplication(self): """ Private slot to start a new local Django application. """ applName, ok = QInputDialog.getText( self.__ui, self.tr("Start Local Django Application"), self.tr("Enter the name of the new local Django application."), QLineEdit.EchoMode.Normal, ) if ok and applName != "": res = self.__createApplication(applName, "", False) if res: try: # search for new files and add them to the project appPath = os.path.join(self.__sitePath(), applName) for entry in os.walk(appPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__ericProject.appendFile(fullName) except DjangoNoSiteSelectedError: return ################################################################## ## methods below implement site related functions ################################################################## def __findSites(self): """ Private method to determine the relative path to all manage.py scripts. @return list of sites @rtype list of str """ sites = [] for file in sorted(self.__ericProject.getSources()): if os.path.basename(file) == "manage.py": sites.append(os.path.dirname(file)) return sites def __selectSite(self): """ Private method to select a site to work with. """ sites = self.__findSites() if len(sites) == 1: site = sites[0] else: if self.__currentSite is not None: if self.__currentSite in sites: cur = sites.index(self.__currentSite) else: cur = 0 else: cur = 0 site, ok = QInputDialog.getItem( self.__ui, self.tr("Select Project"), self.tr("Select the Django project to work with."), sites, cur, False, ) if not ok: site = None self.__setCurrentSite(site) def __sitePath(self): """ Private method to calculate the full path of the Django site. @return path of the site @rtype str @exception DjangoNoSiteSelectedError raised, if no site is selected """ if self.__currentSite is None: self.__selectSite() if self.__currentSite is None: raise DjangoNoSiteSelectedError else: path = os.path.join(self.__ericProject.getProjectPath(), self.__currentSite) return path def __setCurrentSite(self, site): """ Private slot to set the current site. @param site name of the site @type str """ self.__currentSite = site if self.__currentSite is None: curSite = self.tr("None") elif self.__currentSite == "": curSite = self.tr("Project") else: curSite = self.__currentSite self.selectSiteAct.setText( self.tr("&Current Django project ({0})").format(curSite) ) if self.__currentSite is None: self.__ericProject.setTranslationPattern("") else: self.__ericProject.setTranslationPattern( os.path.join(site, "locale", "%language%", "LC_MESSAGES", "django.po") ) def __site(self): """ Private method to get the name of the current site. @return name of the site @rtype str @exception DjangoNoSiteSelectedError raised, if no site is selected """ if self.__currentSite is None: self.__selectSite() if self.__currentSite is None: raise DjangoNoSiteSelectedError else: return self.__currentSite ################################################################## ## slots below implement run functions ################################################################## def __runServer(self): """ Private slot to start the Django Web server. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("runserver") if self.__plugin.getPreferences("UseIPv6"): args.append("--ipv6") if not self.__plugin.getPreferences("UseThreading"): args.append("--nothreading") addr = self.__plugin.getPreferences("ServerAddress") if addr: args.append(addr) with contextlib.suppress(DjangoNoSiteSelectedError): if isWindowsPlatform(): serverProcStarted, pid = QProcess.startDetached( args[0], args[1:], self.__sitePath() ) else: if self.__serverProc is not None: self.__serverProcFinished() self.__serverProc = QProcess() self.__serverProc.finished.connect(self.__serverProcFinished) self.__serverProc.setWorkingDirectory(self.__sitePath()) self.__serverProc.start(args[0], args[1:]) serverProcStarted = self.__serverProc.waitForStarted() if not serverProcStarted: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django server could not be started."), ) def __serverProcFinished(self): """ Private slot connected to the finished signal. """ if ( self.__serverProc is not None and self.__serverProc.state() != QProcess.ProcessState.NotRunning ): self.__serverProc.terminate() QTimer.singleShot(2000, self.__serverProc.kill) self.__serverProc.waitForFinished(3000) self.__serverProc = None def __runBrowser(self): """ Private slot to start the default web browser with the server URL. """ addr = self.__plugin.getPreferences("ServerAddress") ipv6 = self.__plugin.getPreferences("UseIPv6") if addr: # test for an IPv6 and port address if "]:" in addr: addr, port = addr.rsplit(":", 1) elif ":" in addr: addr, port = addr.split(":", 1) else: port = addr if ipv6: addr = "[::1]" else: addr = "127.0.0.1" else: port = "8000" if ipv6: addr = "[::1]" else: addr = "127.0.0.1" url = "http://{0}:{1}".format(addr, port) if self.__plugin.getPreferences("UseExternalBrowser"): res = QDesktopServices.openUrl(QUrl(url)) if not res: EricMessageBox.critical( None, self.tr("Run Web-Browser"), self.tr( "Could not start the web-browser for the" ' url "{0}".' ).format(url.toString()), ) else: self.__ui.launchHelpViewer(url) ################################################################## ## slots below implement functions to save and load recently used ## database names ################################################################## def __loadRecentDatabaseNames(self): """ Private method to load the list of recently used database names. """ self.__recentDatabaseNames = [""] Preferences.Prefs.rsettings.sync() rdb = Preferences.Prefs.rsettings.value(self.RecentDatabaseNamesKey) if rdb is not None: maxRecentDatabaseNames = self.__plugin.getPreferences( "RecentNumberDatabaseNames" ) self.__recentDatabaseNames = rdb[:maxRecentDatabaseNames] def __saveRecentDatabaseNames(self): """ Private method to save the list of recently used database names. """ Preferences.Prefs.rsettings.setValue( self.RecentDatabaseNamesKey, self.__recentDatabaseNames ) Preferences.Prefs.rsettings.sync() def getRecentDatabaseNames(self): """ Public method to get the list of recently used database names. @return list of recently used database names @rtype list of str """ self.__loadRecentDatabaseNames() return self.__recentDatabaseNames def setMostRecentDatabaseNames(self, dbName): """ Public method to set the most recently used database names. @param dbName database name @type str """ if dbName in self.__recentDatabaseNames: self.__recentDatabaseNames.remove(dbName) self.__recentDatabaseNames.insert(0, dbName) maxRecentDatabaseNames = self.__plugin.getPreferences( "RecentNumberDatabaseNames" ) if len(self.__recentDatabaseNames) > maxRecentDatabaseNames: self.__recentDatabaseNames = self.__recentDatabaseNames[ :maxRecentDatabaseNames ] self.__saveRecentDatabaseNames() def __selectDatabaseName(self): """ Private method to select the name of the database to work with. """ recentDatabases = self.getRecentDatabaseNames()[:] if "" not in recentDatabases: recentDatabases.insert(1, "") selectedDatabase, ok = QInputDialog.getItem( self.__ui, self.tr("Database Name"), self.tr("Select a database name (leave empty for default):"), recentDatabases, 0, True, ) if ok: self.setMostRecentDatabaseNames(selectedDatabase) self.__setCurrentDatabase(selectedDatabase) def __setCurrentDatabase(self, database): """ Private method to set the database name to be used. @param database name of the database @type str """ if database is None: database = self.getRecentDatabaseNames()[0] self.__currentDatabase = database curDb = database if database else self.tr("<default>") self.selectDatabaseNameAct.setText( self.tr("&Current Database ({0})").format(curDb) ) def currentDatabase(self): """ Public method to get the database name to be used. @return database name @rtype str """ return self.__currentDatabase ################################################################## ## slots below implement database related functions ################################################################## def __databaseInspect(self): """ Private slot to introspect the database and output a Django model module. """ title = self.tr("Introspect Database") args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("inspectdb") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) try: path = self.__sitePath() except DjangoNoSiteSelectedError: return dia = DjangoDialog(title, fixed=True, linewrap=False) res = dia.startProcess(args, path, False) if res: dia.exec() def __databaseFlush(self): """ Private slot to return all database tables to the state just after their installation. """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return title = self.tr("Flush Database") res = EricMessageBox.yesNo( self.__ui, title, self.tr( """Flushing the database will destroy all data.""" """ Are you sure?""" ), ) if res: args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("flush") args.append("--noinput") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) dia = DjangoDialog( title, msgSuccess=self.tr("Database tables flushed" " successfully.") ) res = dia.startProcess(args, path) if res: dia.exec() def __runDatabaseClient(self): """ Private slot to start a database client for a Django project. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("dbshell") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) with contextlib.suppress(DjangoNoSiteSelectedError): wd = self.__sitePath() self.__adjustWorkingDirectory(args, wd) started, pid = QProcess.startDetached(args[0], args[1:], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django process could not be started."), ) ####################################################################### ## slots below implement database functions outputting SQL statements ####################################################################### def __sqlCommand(self, title, command, requestApps=True): """ Private method to perform an SQL creation function. @param title dialog title @type str @param command Django sql... command @type str @param requestApps flag indicating to request a list of applications to work on @type bool """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return if requestApps: apps = self.__getApplications() if not apps: return else: apps = [] args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append(command) if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) args += apps fileFilter = self.tr("SQL Files (*.sql)") dia = DjangoDialog(title, fixed=True, linewrap=False, saveFilters=fileFilter) res = dia.startProcess(args, path, False) if res: dia.exec() def __databaseSqlFlushDatabase(self): """ Private slot to print a list of statements to return all database tables to their initial state. """ self.__sqlCommand(self.tr("Flush Database"), "sqlflush", False) def __databaseSqlResetSequences(self): """ Private slot to print the SQL statements for resetting sequences for one or more applications. """ self.__sqlCommand(self.tr("Reset Sequences"), "sqlsequencereset") def __databaseSqlMigrate(self, backwards=False): """ Private slot to print the SQL statements for a migration of an application. @param backwards flag indicating to generate the SQL code to revert a migration @type bool """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return migrations = self.__getMigrations() if not migrations: EricMessageBox.information( None, self.tr("SQL Migrate"), self.tr("""No migrations available.""") ) return title = self.tr("SQL Migrate") from .DjangoMigrationSelectionDialog import DjangoMigrationSelectionDialog dlg = DjangoMigrationSelectionDialog( migrations, migrationRequired=True, suffix=self.__iconSuffix ) if dlg.exec() == QDialog.DialogCode.Accepted: app, migration = dlg.getData() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("sqlmigrate") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) if backwards: args.append("--backwards") args.append(app) args.append(migration) fileFilter = self.tr("SQL Files (*.sql)") dia = DjangoDialog( title, fixed=True, linewrap=False, saveFilters=fileFilter ) res = dia.startProcess(args, path, False) if res: dia.exec() ################################################################## ## slots below implement migration related functions ################################################################## def __showMigrationsList(self): """ Private slot to show the available migrations and their status. """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoMigrationsListDialog import DjangoMigrationsListDialog self.__migrationsListDialog = DjangoMigrationsListDialog( DjangoMigrationsListDialog.MigrationsListMode, self, self.__ui ) self.__migrationsListDialog.show() self.__migrationsListDialog.start( self.__getPythonExecutable(), path, self.__currentDatabase ) def __showMigrationsPlan(self): """ Private slot to show the migrations plan. """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoMigrationsListDialog import DjangoMigrationsListDialog self.__migrationsPlanDialog = DjangoMigrationsListDialog( DjangoMigrationsListDialog.MigrationsPlanMode, self, self.__ui ) self.__migrationsPlanDialog.show() self.__migrationsPlanDialog.start( self.__getPythonExecutable(), path, self.__currentDatabase ) def __applyAllMigrations(self): """ Private slot to apply all migrations. """ self.applyMigrations() def __applySelectedMigrations(self): """ Private slot to apply selected migrations of a selected app. """ migrations = self.__getMigrations() if not migrations: EricMessageBox.information( None, self.tr("Apply Selected Migrations"), self.tr("""No migrations available."""), ) return from .DjangoMigrationSelectionDialog import DjangoMigrationSelectionDialog dlg = DjangoMigrationSelectionDialog(migrations, suffix=self.__iconSuffix) if dlg.exec() == QDialog.DialogCode.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 """ title = ( self.tr("Unapply Migrations") if migration == "zero" else self.tr("Apply Migrations") ) try: path = self.__sitePath() except DjangoNoSiteSelectedError: 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 revert all migrations of an application. """ apps = sorted(self.__getMigrations().keys()) if not apps: EricMessageBox.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 DjangoNoSiteSelectedError: return {} args = [] args.append("manage.py") args.append("showmigrations") args.append("--list") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) migrations = {} proc = QProcess() if path: proc.setWorkingDirectory(path) proc.start(self.__getPythonExecutable(), args) if proc.waitForStarted() and 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.DialogCode.Accepted: apps, migration, dryRun, empty, merge = dlg.getData() if apps: self.setMostRecentApplication(apps) apps = apps.split() self.makeMigrations(apps, migration, dryRun, empty, merge) def makeMigrations( self, apps, migration=None, dryRun=False, empty=False, merge=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 @param empty flag indicating the creation of an empty migration @type bool @param merge flag indicating to fix migration conflicts @type bool """ title = self.tr("Make Migrations") try: path = self.__sitePath() except DjangoNoSiteSelectedError: return args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemigrations") if migration: args.append("--name") args.append(migration.replace(" ", "_")) if dryRun: args.append("--dry-run") if empty: args.append("--empty") if merge: args.append("--merge") if apps: args += apps dia = DjangoDialog(title, showInput=True) res = dia.startProcess(args, path) if res: dia.exec() def __squashMigrations(self): """ Private slot to squash migrations. """ migrations = self.__getMigrations() if not migrations: EricMessageBox.information( None, self.tr("Squash Migrations"), self.tr("""No migrations available."""), ) return from .DjangoSquashMigrationSelectionDialog import ( DjangoSquashMigrationSelectionDialog, ) dlg = DjangoSquashMigrationSelectionDialog(migrations, self, self.__iconSuffix) if dlg.exec() == QDialog.DialogCode.Accepted: app, start, end, noOptimize, name = dlg.getData() title = self.tr("Squash Migrations") try: path = self.__sitePath() except DjangoNoSiteSelectedError: return args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("squashmigrations") args.append("--noinput") if noOptimize: args.append("--no-optimize") if name: args.append("--squashed-name={0}".format(name)) args.append(app) if start: args.append(start) args.append(end) dia = DjangoDialog(title) res = dia.startProcess(args, path) if res: dia.exec() ################################################################## ## slots below implement some tool functions ################################################################## def __diffSettings(self): """ Private slot to show the changes made to the settings.py file. """ title = self.tr("Diff Settings") from .DjangoDiffsettingsDataDialog import DjangoDiffsettingsDataDialog dlg = DjangoDiffsettingsDataDialog(self, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: showAll, defaultModule, outputFormat = dlg.getData() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("diffsettings") if showAll: args.append("--all") if defaultModule: args.append("--default={0}".format(defaultModule)) if outputFormat: args.append("--output={0}".format(outputFormat)) try: path = self.__sitePath() except DjangoNoSiteSelectedError: return dia = DjangoDialog(title, fixed=True, linewrap=False) res = dia.startProcess(args, path, False) if res: dia.exec() def __runPythonShell(self): """ Private slot to start a Python console for a Django project. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("shell") args.append( "--interface={0}".format( self.__plugin.getPreferences("Python3ConsoleType") ) ) with contextlib.suppress(DjangoNoSiteSelectedError): wd = self.__sitePath() self.__adjustWorkingDirectory(args, wd) started, pid = QProcess.startDetached(args[0], args[1:], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django process could not be started."), ) def __sendTestEmail(self): """ Private slot to send a test email through Django. """ title = self.tr("Send Test Email") from .DjangoSendTestEmailDataDialog import DjangoSendTestEmailDataDialog dlg = DjangoSendTestEmailDataDialog(self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: managers, admins, recipients = dlg.getData() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("sendtestemail") if managers: args.append("--managers") if admins: args.append("--admins") args.extend(recipients) try: path = self.__sitePath() except DjangoNoSiteSelectedError: return dia = DjangoDialog( title, linewrap=False, msgSuccess=self.tr("Test Email sent successfully."), msgError=self.tr("Test Email could not be sent."), ) res = dia.startProcess(args, path, False) if res: dia.exec() ################################################################## ## slots below implement caching functions ################################################################## def __createCacheTables(self): """ Private slot to create the tables for the SQL caching backend. """ title = self.tr("Create Cache Tables") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: return args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("createcachetable") if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) dia = DjangoDialog( title, msgSuccess=self.tr("Cache tables created successfully.") ) res = dia.startProcess(args, wd) if res: dia.exec() ################################################################## ## slots below implement testing functions ################################################################## def __dumpData(self): """ Private slot to dump the database data to a fixture. """ title = self.tr("Dump Data") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoDumpdataDataDialog import DjangoDumpdataDataDialog dlg = DjangoDumpdataDataDialog(self, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: appls, excls, dumpFormat, indent = dlg.getData() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("dumpdata") args.append("--format={0}".format(dumpFormat)) args.append("--indent={0}".format(indent)) for excl in excls: args.append("--exclude={0}".format(excl)) if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) args += appls if dumpFormat == "json": fileFilters = self.tr("JSON Files (*.json)") elif dumpFormat == "xml": fileFilters = self.tr("XML Files (*.xml)") elif dumpFormat == "yaml": fileFilters = self.tr("YAML Files (*.yaml)") dia = DjangoDialog( title, fixed=True, linewrap=False, saveFilters=fileFilters ) res = dia.startProcess(args, wd, showCommand=False) if res: dia.exec() def __loadData(self): """ Private slot to load data from fixture files. """ title = self.tr("Load Data") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoLoaddataDataDialog import DjangoLoaddataDataDialog dlg = DjangoLoaddataDataDialog(self, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: fixtures, excludes, appLabel, ignore = dlg.getData() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("loaddata") for excl in excludes: args.append("--exclude={0}".format(excl)) if ignore: args.append("--ignorenonexistent") if appLabel: args.append("--app={0}".format(appLabel)) if self.__currentDatabase: args.append("--database={0}".format(self.__currentDatabase)) args += fixtures dia = DjangoDialog(title) res = dia.startProcess(args, wd) if res: dia.exec() def __runTestSuite(self, deprecation=False): """ Private slot to run the test suite for applications or the whole site. @param deprecation flag indicating to test for deprecation warnings @type bool """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommandNoClose") )[1] if consoleCmd: try: wd = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoTestDataDialog import DjangoTestDataDialog dlg = DjangoTestDataDialog( self, self.__plugin.getPreferences("KeepTestDatabase"), self.__ui ) if dlg.exec() == QDialog.DialogCode.Accepted: labels, pattern, tags, excludeTags, keep, reverse = dlg.getData() self.__plugin.setPreferences("KeepTestDatabase", keep) args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) if deprecation: args.append("-Wall") args.append("manage.py") args.append("test") if pattern: args.append("--pattern=" + pattern) for tag in tags: args.append("--tag=" + tag) for tag in excludeTags: args.append("--exclude-tag=" + tag) if keep: args.append("--keepdb") if reverse: args.append("--reverse") args.extend(labels) self.__adjustWorkingDirectory(args, wd) started, pid = QProcess.startDetached(args[0], args[1:], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django process could not be started."), ) def __runTestServer(self): """ Private slot to run a development server with data from a set of fixtures. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: from .DjangoRunTestServerDataDialog import DjangoRunTestServerDataDialog dlg = DjangoRunTestServerDataDialog(self, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: fixtures = dlg.getData() args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("testserver") if self.__plugin.getPreferences("UseIPv6"): args.append("--ipv6") addr = self.__plugin.getPreferences("ServerAddress") if addr: args.append("--addrport={0}".format(addr)) args += fixtures with contextlib.suppress(DjangoNoSiteSelectedError): if isWindowsPlatform(): serverProcStarted, pid = QProcess.startDetached( args[0], args[1:], self.__sitePath() ) else: if self.__testServerProc is not None: self.__testServerProcFinished() self.__testServerProc = QProcess() self.__testServerProc.finished.connect( self.__serverProcFinished ) self.__testServerProc.setWorkingDirectory(self.__sitePath()) self.__testServerProc.start(args[0], args[1:]) serverProcStarted = self.__testServerProc.waitForStarted() if not serverProcStarted: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django test server could not be" " started."), ) def __testServerProcFinished(self): """ Private slot connected to the finished signal of the test server. """ if ( self.__testServerProc is not None and self.__testServerProc.state() != QProcess.ProcessState.NotRunning ): self.__testServerProc.terminate() QTimer.singleShot(2000, self.__testServerProc.kill) self.__testServerProc.waitForFinished(3000) self.__testServerProc = None ################################################################## ## slots below implement authorization functions ################################################################## def __changePassword(self): """ Private slot to change the password of a user. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommandNoClose") )[1] if consoleCmd: userName, ok = QInputDialog.getText( self.__ui, self.tr("Change Password"), self.tr("Enter the name of the user:"), QLineEdit.EchoMode.Normal, ) if ok and userName != "": args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("changepassword") args.append(userName) with contextlib.suppress(DjangoNoSiteSelectedError): wd = self.__sitePath() self.__adjustWorkingDirectory(args, wd) started, pid = QProcess.startDetached(args[0], args[1:], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django process could not be" " started."), ) def __createSuperUser(self): """ Private slot to create a super user account. """ consoleCmd = self.__isSpawningConsole( self.__plugin.getPreferences("ConsoleCommandNoClose") )[1] if consoleCmd: args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("createsuperuser") with contextlib.suppress(DjangoNoSiteSelectedError): wd = self.__sitePath() self.__adjustWorkingDirectory(args, wd) started, pid = QProcess.startDetached(args[0], args[1:], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr("The Django process could not be started."), ) ################################################################## ## slots below implement session functions ################################################################## def __clearSessions(self): """ Private slot to clear expired sessions. """ title = self.tr("Clear Sessions") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: return args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("clearsessions") dia = DjangoDialog( title, msgSuccess=self.tr("Expired sessions cleared successfully.") ) res = dia.startProcess(args, wd) if res: dia.exec() ################################################################## ## slots below implement translation functions ################################################################## def __getLocale(self, filename): """ Private method to extract the locale out of a file name. @param filename name of the file used for extraction @type str @return extracted locale or None @rtype str """ if self.__ericProject.getTranslationPattern(): pattern = self.__ericProject.getTranslationPattern().replace( "%language%", "(.*?)" ) match = re.search(pattern, filename) if match is not None: loc = match.group(1) return loc else: loc = None else: loc = None return loc def __normalizeList(self, filenames): """ Private method to normalize a list of file names. @param filenames list of file names to normalize @type list of str @return normalized file names @rtype list of str """ nfilenames = [] for filename in filenames: if filename.endswith(".mo"): filename = filename.replace(".mo", ".po") if filename not in nfilenames: nfilenames.append(filename) return nfilenames def __siteFilteredList(self, filenames): """ Private method to filter a list of file names by site. @param filenames list of file names to be filtered @type list of str @return file names belonging to the current site @rtype list of str """ site = self.__site() nfilenames = [] for filename in filenames: if site == "" or filename.startswith(site + os.sep): nfilenames.append(filename) return nfilenames def __projectLanguageAdded(self, code): """ Private slot handling the addition of a new language. @param code language code of the new language @type str """ title = self.tr("Initializing message catalog for '{0}'").format(code) args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemessages") args.append("--locale={0}".format(code)) try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalog initialized successfully.") ) res = dia.startProcess(args, wd) if res: dia.exec() langFile = self.__ericProject.getTranslationPattern().replace( "%language%", code ) self.__ericProject.appendFile(langFile) def updateSelectedCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of file names @type list of str """ title = self.tr("Updating message catalogs") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return argsLists = [] for filename in self.__normalizeList(self.__siteFilteredList(filenames)): locale = self.__getLocale(filename) if locale: args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemessages") args.append("--no-obsolete") args.append("--locale={0}".format(locale)) argsLists.append(args) if len(argsLists) == 0: EricMessageBox.warning( None, title, self.tr("No locales detected. Aborting...") ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startBatchProcesses(argsLists, wd) if res: dia.exec() def updateSelectedCatalogsWithObsolete(self, filenames): """ Public method to update the message catalogs keeping obsolete messages. @param filenames list of filenames @type list of str """ title = self.tr("Updating message catalogs (keeping obsolete" " messages)") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return argsLists = [] for filename in self.__normalizeList(self.__siteFilteredList(filenames)): locale = self.__getLocale(filename) if locale: args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemessages") args.append("--locale={0}".format(locale)) argsLists.append(args) if len(argsLists) == 0: EricMessageBox.warning( None, title, self.tr("No locales detected. Aborting...") ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startBatchProcesses(argsLists, wd) if res: dia.exec() def updateCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of filenames (not used) @type list of str """ title = self.tr("Updating message catalogs") args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemessages") args.append("--all") args.append("--no-obsolete") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startProcess(args, wd) if res: dia.exec() def updateCatalogsWithObsolete(self, filenames): """ Public method to update the message catalogs keeping obsolete messages. @param filenames list of filenames (not used) @type list of str """ title = self.tr("Updating message catalogs (keeping obsolete" " messages)") args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("makemessages") args.append("--all") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startProcess(args, wd) if res: dia.exec() def compileSelectedCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of filenames @type list of str """ title = self.tr("Compiling message catalogs") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return argsLists = [] for filename in self.__normalizeList(self.__siteFilteredList(filenames)): locale = self.__getLocale(filename) if locale: args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("compilemessages") args.append("--locale={0}".format(locale)) if self.__plugin.getPreferences("FuzzyTranslations"): args.append("--use-fuzzy") argsLists.append(args) if len(argsLists) == 0: EricMessageBox.warning( None, title, self.tr("No locales detected. Aborting...") ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs compiled successfully.") ) res = dia.startBatchProcesses(argsLists, wd, mergedOutput=True) if res: dia.exec() for entry in os.walk(self.__sitePath()): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) if fullName.endswith(".mo"): self.__ericProject.appendFile(fullName) def compileCatalogs(self, filenames): """ Public method to compile the message catalogs. @param filenames list of filenames (not used) @type list of str """ title = self.tr("Compiling message catalogs") args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("compilemessages") if self.__plugin.getPreferences("FuzzyTranslations"): args.append("--use-fuzzy") try: wd = self.__sitePath() except DjangoNoSiteSelectedError: EricMessageBox.warning( None, title, self.tr( "No current site selected or no site created yet." " Aborting..." ), ) return dia = DjangoDialog( title, msgSuccess=self.tr("\nMessage catalogs compiled successfully.") ) res = dia.startProcess(args, wd, mergedOutput=True) if res: dia.exec() for entry in os.walk(self.__sitePath()): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) if fullName.endswith(".mo"): self.__ericProject.appendFile(fullName) def openPOEditor(self, poFile): """ Public method to edit the given file in an external .po editor. @param poFile name of the .po file @type str """ editor = self.__plugin.getPreferences("TranslationsEditor") if poFile.endswith(".po") and editor: try: wd = self.__sitePath() except DjangoNoSiteSelectedError: wd = "" started, pid = QProcess.startDetached(editor, [poFile], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr( "The translations editor process ({0}) could" " not be started." ).format(os.path.basename(editor)), ) ################################################################## ## slots below implement check functions ################################################################## def __performCheck(self): """ Private slot to inspect the project for common problems. """ try: path = self.__sitePath() except DjangoNoSiteSelectedError: return from .DjangoCheckOptionsDialog import DjangoCheckOptionsDialog dlg = DjangoCheckOptionsDialog( self.__getPythonExecutable(), path, self.getRecentApplications(), self.__plugin.getPreferences("CheckDeployMode"), ) if dlg.exec() == QDialog.DialogCode.Accepted: deploy, tags, appsStr, settingsFile = dlg.getData() self.__plugin.setPreferences("CheckDeployMode", deploy) if appsStr != "": self.setMostRecentApplication(appsStr) apps = appsStr.split() args = [] args.append(self.__getPythonExecutable()) args.append("manage.py") args.append("check") for tag in tags: args.append("--tag") args.append(tag) if deploy: args.append("--deploy") if settingsFile: args.append("--settings={0}".format(settingsFile)) args += apps dia = DjangoDialog(self.tr("Check Project")) res = dia.startProcess(args, path, mergedOutput=True) if res: dia.exec()