ProjectDjango/Project.py

Thu, 30 Dec 2021 11:20:00 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 30 Dec 2021 11:20:00 +0100
branch
eric7
changeset 175
30cb5e553e7e
parent 172
ea7980ded4f3
child 176
1395e0583f4e
permissions
-rw-r--r--

Updated copyright for 2022.

# -*- coding: utf-8 -*-

# Copyright (c) 2013 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the Django project support.
"""

import os
import re
import shutil
import contextlib

from PyQt6.QtCore import QObject, QTimer, QUrl, QFileInfo, QIODeviceBase
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtWidgets import QMenu, QInputDialog, QLineEdit, QDialog
from PyQt6.QtCore import QProcess as QProcessPyQt

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox, EricFileDialog
from EricGui.EricAction import EricAction

from Globals import isWindowsPlatform

from .DjangoDialog import DjangoDialog

import Preferences
import Utilities
import UI.PixmapCache


class DjangoNoSiteSelectedException(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 = Utilities.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.py"),
                    # 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.py"),
                    os.path.join(virtualEnv, "bin", "django-admin"),
                    os.path.join(virtualEnv, "local", "bin",
                                 "django-admin.py"),
                    os.path.join(virtualEnv, "local", "bin", "django-admin"),
                ]
                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.py"),
                    # PyPy
                    os.path.join(debugEnv, "bin", "django-admin.py"),
                ]:
                    if os.path.exists(cmd):
                        break
                else:
                    cmd = ""
            else:
                if language == "Python3":
                    cmds = ["django-admin3.py", "django-admin3",
                            "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.py", "django-admin"])
                for cmd in cmds:
                    if Utilities.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(UI.PixmapCache.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 Utilities.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 Utilities.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 DjangoNoSiteSelectedException:
                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 DjangoNoSiteSelectedException:
                    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 DjangoNoSiteSelectedException raised, if no site is selected
        """
        if self.__currentSite is None:
            self.__selectSite()
        
        if self.__currentSite is None:
            raise DjangoNoSiteSelectedException
        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 DjangoNoSiteSelectedException raised, if no site is selected
        """
        if self.__currentSite is None:
            self.__selectSite()
        
        if self.__currentSite is None:
            raise DjangoNoSiteSelectedException
        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] = Utilities.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(DjangoNoSiteSelectedException):
                if Utilities.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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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] = Utilities.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(DjangoNoSiteSelectedException):
                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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            return
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("migrate")
        args.append("--noinput")
        if app:
            args.append(app)
            if migration:
                args.append(migration)
        
        dia = DjangoDialog(title)
        res = dia.startProcess(args, path)
        if res:
            dia.exec()
    
    def __unapplyMigrations(self):
        """
        Private slot to revert all migrations of an application.
        """
        apps = list(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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
                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 DjangoNoSiteSelectedException:
                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] = Utilities.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(DjangoNoSiteSelectedException):
                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 DjangoNoSiteSelectedException:
                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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
                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] = Utilities.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] = Utilities.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(DjangoNoSiteSelectedException):
                    if Utilities.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] = Utilities.getExecutablePath(args[0])
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("changepassword")
                args.append(userName)
                with contextlib.suppress(DjangoNoSiteSelectedException):
                    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] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("createsuperuser")
            with contextlib.suppress(DjangoNoSiteSelectedException):
                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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
            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 DjangoNoSiteSelectedException:
                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 DjangoNoSiteSelectedException:
            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()

eric ide

mercurial