ProjectDjango/Project.py

Wed, 21 Dec 2022 09:27:28 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 21 Dec 2022 09:27:28 +0100
branch
eric7
changeset 188
b08c2cb2e0a0
parent 184
9585686bc532
child 190
f394b11f9f31
permissions
-rw-r--r--

Adapted some import statements to eric 23.1 and newer.

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

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

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

import contextlib
import os
import re
import shutil

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

from eric7 import Preferences, Utilities

try:
    from eric7.EricGui import EricPixmapCache
except ImportError:
    from UI import PixmapCache as EricPixmapCache

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

try:
    from eric7.SystemUtilities.OSUtilities import isWindowsPlatform
except ImportError:
    # imports for eric < 23.1
    from eric7.Globals import isWindowsPlatform
try:
    from eric7.SystemUtilities.FileSystemUtilities import (
        getExecutablePath,
        getExecutablePaths,
        isinpath,
    )
except ImportError:
    # imports for eric < 23.1
    from eric7.Utilities import getExecutablePath, getExecutablePaths, isinpath

from .DjangoDialog import DjangoDialog


class DjangoNoSiteSelectedError(Exception):
    """
    Exception thrown to signal, that there is no current site.
    """

    pass


class QProcess(QProcessPyQt):
    """
    Class transforming the call arguments in case of gnome-terminal.
    """

    def start(self, cmd, args=None, mode=QIODeviceBase.OpenModeFlag.ReadWrite):
        """
        Public method to start the given program (cmd) in a new process, if
        none is already running, passing the command line arguments in args.

        @param cmd start the given program cmd
        @type str
        @param args list of parameters
        @type list of str
        @param mode access mode
        @type QIODeviceBase.OpenMode
        """
        if args is None:
            args = []

        if (
            cmd.endswith(
                ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal")
            )
            and "-e" in args
        ):
            index = args.index("-e") + 1
            cargs = " ".join(args[index:])
            args[index:] = [cargs]

        super().start(cmd, args, mode)

    @staticmethod
    def startDetached(cmd, args=None, path=""):
        """
        Public static method to start the given program (cmd) in a new process,
        if none is already running, passing the command line arguments in args.

        @param cmd start the given program cmd
        @type str
        @param args list of parameters
        @type list of str
        @param path new working directory
        @type str
        @return tuple of successful start and process id
        @rtype tuple of (bool, int)
        """
        if args is None:
            args = []

        if (
            cmd.endswith(
                ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal")
            )
            and "-e" in args
        ):
            index = args.index("-e") + 1
            cargs = " ".join(args[index:])
            args[index:] = [cargs]

        return QProcessPyQt.startDetached(cmd, args, path)


class Project(QObject):
    """
    Class implementing the Django project support.
    """

    RecentApplicationsKey = "Django/RecentApplications"
    RecentDatabaseNamesKey = "Django/RecentDatabaseNames"

    def __init__(self, plugin, iconSuffix, parent=None):
        """
        Constructor

        @param plugin reference to the plugin object
        @type ProjectDjangoPlugin
        @param iconSuffix suffix for the icons
        @type str
        @param parent parent
        @type QObject
        """
        super().__init__(parent)

        self.__plugin = plugin
        self.__iconSuffix = iconSuffix
        self.__ui = parent

        self.__ericProject = ericApp().getObject("Project")
        self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager")
        self.__hooksInstalled = False

        self.__menus = {}  # dictionary with references to menus

        self.__serverProc = None
        self.__testServerProc = None

        self.__recentApplications = []
        self.__loadRecentApplications()

        self.__recentDatabaseNames = []
        self.__loadRecentDatabaseNames()

        self.__recentTestData = {
            "RecentTestLabels": [],
            "RecentTestTags": [],
            "RecentTestExcludeTags": [],
        }
        self.__loadRecentTestData()

    def initActions(self):
        """
        Public method to define the Django actions.
        """
        self.actions = []

        self.selectSiteAct = EricAction(
            self.tr("Current Project"), "", 0, 0, self, "django_current_project"
        )
        self.selectSiteAct.setStatusTip(self.tr("Selects the current project"))
        self.selectSiteAct.setWhatsThis(
            self.tr(
                """<b>Current Project</b>"""
                """<p>Selects the current project. Used for multi-project """
                """Django projects to switch between the projects.</p>"""
            )
        )
        self.selectSiteAct.triggered.connect(self.__selectSite)
        self.actions.append(self.selectSiteAct)
        self.__setCurrentSite(None)

        ##############################
        ## start actions below      ##
        ##############################

        self.startProjectAct = EricAction(
            self.tr("Start Project"),
            self.tr("Start &Project"),
            0,
            0,
            self,
            "django_start_project",
        )
        self.startProjectAct.setStatusTip(self.tr("Starts a new Django project"))
        self.startProjectAct.setWhatsThis(
            self.tr(
                """<b>Start Project</b>"""
                """<p>Starts a new Django project using "django-admin.py"""
                """ startproject".</p>"""
            )
        )
        self.startProjectAct.triggered.connect(self.__startProject)
        self.actions.append(self.startProjectAct)

        self.startGlobalApplicationAct = EricAction(
            self.tr("Start Application (global)"),
            self.tr("Start Application (&global)"),
            0,
            0,
            self,
            "django_start_global_application",
        )
        self.startGlobalApplicationAct.setStatusTip(
            self.tr("Starts a new global Django application")
        )
        self.startGlobalApplicationAct.setWhatsThis(
            self.tr(
                """<b>Start Application (global)</b>"""
                """<p>Starts a new global Django application using"""
                """ "django-admin.py startapp".</p>"""
            )
        )
        self.startGlobalApplicationAct.triggered.connect(self.__startGlobalApplication)
        self.actions.append(self.startGlobalApplicationAct)

        self.startLocalApplicationAct = EricAction(
            self.tr("Start Application (local)"),
            self.tr("Start Application (&local)"),
            0,
            0,
            self,
            "django_start_local_application",
        )
        self.startLocalApplicationAct.setStatusTip(
            self.tr("Starts a new local Django application")
        )
        self.startLocalApplicationAct.setWhatsThis(
            self.tr(
                """<b>Start Application (local)</b>"""
                """<p>Starts a new local Django application using"""
                """ "manage.py startapp".</p>"""
            )
        )
        self.startLocalApplicationAct.triggered.connect(self.__startLocalApplication)
        self.actions.append(self.startLocalApplicationAct)

        ##############################
        ## run actions below        ##
        ##############################

        self.runServerAct = EricAction(
            self.tr("Run Server"),
            self.tr("Run &Server"),
            0,
            0,
            self,
            "django_run_server",
        )
        self.runServerAct.setStatusTip(self.tr("Starts the Django Web server"))
        self.runServerAct.setWhatsThis(
            self.tr(
                """<b>Run Server</b>"""
                """<p>Starts the Django Web server using "manage.py"""
                """ runserver".</p>"""
            )
        )
        self.runServerAct.triggered.connect(self.__runServer)
        self.actions.append(self.runServerAct)

        self.runBrowserAct = EricAction(
            self.tr("Run Web-Browser"),
            self.tr("Run &Web-Browser"),
            0,
            0,
            self,
            "django_run_browser",
        )
        self.runBrowserAct.setStatusTip(
            self.tr(
                "Starts the default Web-Browser with the URL of the Django Web"
                " server"
            )
        )
        self.runBrowserAct.setWhatsThis(
            self.tr(
                """<b>Run Web-Browser</b>"""
                """<p>Starts the default Web-Browser with the URL of the """
                """Django Web server.</p>"""
            )
        )
        self.runBrowserAct.triggered.connect(self.__runBrowser)
        self.actions.append(self.runBrowserAct)

        ##############################
        ## caching actions below    ##
        ##############################

        self.createCacheTableAct = EricAction(
            self.tr("Create Cache Tables"),
            self.tr("C&reate Cache Tables"),
            0,
            0,
            self,
            "django_create_cache_tables",
        )
        self.createCacheTableAct.setStatusTip(
            self.tr("Creates the tables needed to use the SQL cache backend")
        )
        self.createCacheTableAct.setWhatsThis(
            self.tr(
                """<b>Create Cache Tables</b>"""
                """<p>Creates the tables needed to use the SQL cache"""
                """ backend.</p>"""
            )
        )
        self.createCacheTableAct.triggered.connect(self.__createCacheTables)
        self.actions.append(self.createCacheTableAct)

        ##############################
        ## help action below        ##
        ##############################

        self.helpAct = EricAction(
            self.tr("Help"), self.tr("&Help"), 0, 0, self, "django_help"
        )
        self.helpAct.setStatusTip(self.tr("Shows the Django help index"))
        self.helpAct.setWhatsThis(
            self.tr("""<b>Help</b>""" """<p>Shows the Django help index page.</p>""")
        )
        self.helpAct.triggered.connect(self.__showHelpIndex)
        self.actions.append(self.helpAct)

        ##############################
        ## about action below       ##
        ##############################

        self.aboutDjangoAct = EricAction(
            self.tr("About Django"),
            self.tr("About D&jango"),
            0,
            0,
            self,
            "django_about",
        )
        self.aboutDjangoAct.setStatusTip(self.tr("Shows some information about Django"))
        self.aboutDjangoAct.setWhatsThis(
            self.tr(
                """<b>About Django</b>"""
                """<p>Shows some information about Django.</p>"""
            )
        )
        self.aboutDjangoAct.triggered.connect(self.__djangoInfo)
        self.actions.append(self.aboutDjangoAct)

        ##############################
        ## check action below       ##
        ##############################

        self.checkAct = EricAction(
            self.tr("Check Project"),
            self.tr("Check Project"),
            0,
            0,
            self,
            "django_check_project",
        )
        self.checkAct.setStatusTip(
            self.tr("Inspects the Django project for common problems")
        )
        self.checkAct.setWhatsThis(
            self.tr(
                """<b>Check Project</b>"""
                """<p>This inspects the Django project for common problems.</p>"""
            )
        )
        self.checkAct.triggered.connect(self.__performCheck)
        self.actions.append(self.checkAct)

        self.__initDatabaseActions()
        self.__initDatabaseSqlActions()
        self.__initMigrationActions()
        self.__initToolsActions()
        self.__initTestingActions()
        self.__initAuthorizationActions()
        self.__initSessionActions()

    def __initDatabaseActions(self):
        """
        Private method to define the database related actions.
        """
        self.selectDatabaseNameAct = EricAction(
            self.tr("Current Database"), "", 0, 0, self, "django_current_database"
        )
        self.selectDatabaseNameAct.setStatusTip(self.tr("Selects the current database"))
        self.selectDatabaseNameAct.setWhatsThis(
            self.tr(
                """<b>Current Database</b>"""
                """<p>Selects the database name to be used by all database"""
                """ actions. An empty database name indicates to use the default"""
                """ name.</p>"""
            )
        )
        self.selectDatabaseNameAct.triggered.connect(self.__selectDatabaseName)
        self.actions.append(self.selectDatabaseNameAct)
        self.__setCurrentDatabase(None)

        self.inspectDatabaseAct = EricAction(
            self.tr("Introspect"),
            self.tr("&Introspect"),
            0,
            0,
            self,
            "django_database_inspect",
        )
        self.inspectDatabaseAct.setStatusTip(
            self.tr(
                "Introspects the database tables and outputs a Django model" " module"
            )
        )
        self.inspectDatabaseAct.setWhatsThis(
            self.tr(
                """<b>Introspect</b>"""
                """<p>Introspects the database tables and outputs a """
                """Django model module.</p>"""
            )
        )
        self.inspectDatabaseAct.triggered.connect(self.__databaseInspect)
        self.actions.append(self.inspectDatabaseAct)

        self.flushDatabaseAct = EricAction(
            self.tr("Flush"), self.tr("&Flush"), 0, 0, self, "django_database_flush"
        )
        self.flushDatabaseAct.setStatusTip(
            self.tr(
                "Returns all database tables to the state just after their"
                " installation"
            )
        )
        self.flushDatabaseAct.setWhatsThis(
            self.tr(
                """<b>Flush</b>"""
                """<p>Returns all database tables to the state """
                """just after their installation.</p>"""
            )
        )
        self.flushDatabaseAct.triggered.connect(self.__databaseFlush)
        self.actions.append(self.flushDatabaseAct)

        self.databaseClientAct = EricAction(
            self.tr("Start Client Console"),
            self.tr("Start &Client Console"),
            0,
            0,
            self,
            "django_database_client",
        )
        self.databaseClientAct.setStatusTip(
            self.tr("Starts a console window for the database client")
        )
        self.databaseClientAct.setWhatsThis(
            self.tr(
                """<b>Start Client Console</b>"""
                """<p>Starts a console window for the database client.</p>"""
            )
        )
        self.databaseClientAct.triggered.connect(self.__runDatabaseClient)
        self.actions.append(self.databaseClientAct)

    def __initDatabaseSqlActions(self):
        """
        Private method to define the database SQL related actions.
        """
        self.databaseSqlFlushAct = EricAction(
            self.tr("Flush Database"),
            self.tr("&Flush Database"),
            0,
            0,
            self,
            "django_database_sql_flush_database",
        )
        self.databaseSqlFlushAct.setStatusTip(
            self.tr(
                "Prints a list of statements to return all database tables to the"
                " state just after their installation"
            )
        )
        self.databaseSqlFlushAct.setWhatsThis(
            self.tr(
                """<b>Flush Database</b>"""
                """<p>Prints a list of statements to return all database tables"""
                """ to the state just after their installation.</p>"""
            )
        )
        self.databaseSqlFlushAct.triggered.connect(self.__databaseSqlFlushDatabase)
        self.actions.append(self.databaseSqlFlushAct)

        self.databaseSqlResetSeqAct = EricAction(
            self.tr("Reset Sequences"),
            self.tr("Reset &Sequences"),
            0,
            0,
            self,
            "django_database_sql_reset_sequences",
        )
        self.databaseSqlResetSeqAct.setStatusTip(
            self.tr(
                "Prints the SQL statements for resetting sequences for "
                "one or more applications"
            )
        )
        self.databaseSqlResetSeqAct.setWhatsThis(
            self.tr(
                """<b>Reset Sequences</b>"""
                """<p>Prints the SQL statements for resetting sequences for """
                """one or more applications.</p>"""
            )
        )
        self.databaseSqlResetSeqAct.triggered.connect(self.__databaseSqlResetSequences)
        self.actions.append(self.databaseSqlResetSeqAct)

        self.databaseSqlMigrateAct = EricAction(
            self.tr("Apply Migration"),
            self.tr("&Apply Migration"),
            0,
            0,
            self,
            "django_database_sql_apply_migration",
        )
        self.databaseSqlMigrateAct.setStatusTip(
            self.tr(
                "Prints the SQL statements to apply a migration of an" " application"
            )
        )
        self.databaseSqlMigrateAct.setWhatsThis(
            self.tr(
                """<b>Apply Migration</b>"""
                """<p>Prints the SQL statements to apply a migration of an"""
                """ application.</p>"""
            )
        )
        self.databaseSqlMigrateAct.triggered.connect(self.__databaseSqlMigrate)
        self.actions.append(self.databaseSqlMigrateAct)

        self.databaseSqlMigrateBackwardsAct = EricAction(
            self.tr("Unapply Migration"),
            self.tr("&Unapply Migration"),
            0,
            0,
            self,
            "django_database_sql_unapply_migration",
        )
        self.databaseSqlMigrateBackwardsAct.setStatusTip(
            self.tr(
                "Prints the SQL statements to unapply a migration of an" " application"
            )
        )
        self.databaseSqlMigrateBackwardsAct.setWhatsThis(
            self.tr(
                """<b>Unapply Migration</b>"""
                """<p>Prints the SQL statements to unapply a migration of an"""
                """ application.</p>"""
            )
        )
        self.databaseSqlMigrateBackwardsAct.triggered.connect(
            lambda: self.__databaseSqlMigrate(backwards=True)
        )
        self.actions.append(self.databaseSqlMigrateBackwardsAct)

    def __initToolsActions(self):
        """
        Private method to define the tool actions.
        """
        self.diffSettingsAct = EricAction(
            self.tr("Diff Settings"),
            self.tr("&Diff Settings"),
            0,
            0,
            self,
            "django_tools_diffsettings",
        )
        self.diffSettingsAct.setStatusTip(
            self.tr("Shows the modification made to the settings")
        )
        self.diffSettingsAct.setWhatsThis(
            self.tr(
                """<b>Diff Settings</b>"""
                """<p>Shows the modification made to the settings.</p>"""
            )
        )
        self.diffSettingsAct.triggered.connect(self.__diffSettings)
        self.actions.append(self.diffSettingsAct)

        self.runPythonShellAct = EricAction(
            self.tr("Start Python Console"),
            self.tr("Start &Python Console"),
            0,
            0,
            self,
            "django_tools_pythonconsole",
        )
        self.runPythonShellAct.setStatusTip(
            self.tr("Starts a Python interactive interpreter")
        )
        self.runPythonShellAct.setWhatsThis(
            self.tr(
                """<b>Start Python Console</b>"""
                """<p>Starts a Python interactive interpreter.</p>"""
            )
        )
        self.runPythonShellAct.triggered.connect(self.__runPythonShell)
        self.actions.append(self.runPythonShellAct)

        self.testEmailAct = EricAction(
            self.tr("Send Test Email"),
            self.tr("Send Test &Email"),
            0,
            0,
            self,
            "django_tools_sendtestemail",
        )
        self.testEmailAct.setStatusTip(self.tr("Send a test email through Django"))
        self.testEmailAct.setWhatsThis(
            self.tr(
                """<b>Send Test Email</b>"""
                """<p>Sends a test email to confirm email sending through Django"""
                """ is working.</p>"""
            )
        )
        self.testEmailAct.triggered.connect(self.__sendTestEmail)
        self.actions.append(self.testEmailAct)

    def __initTestingActions(self):
        """
        Private method to define the testing actions.
        """
        self.dumpDataAct = EricAction(
            self.tr("Dump Data"),
            self.tr("&Dump Data"),
            0,
            0,
            self,
            "django_tools_dumpdata",
        )
        self.dumpDataAct.setStatusTip(self.tr("Dump the database data to a fixture"))
        self.dumpDataAct.setWhatsThis(
            self.tr(
                """<b>Dump Data</b>""" """<p>Dump the database data to a fixture.</p>"""
            )
        )
        self.dumpDataAct.triggered.connect(self.__dumpData)
        self.actions.append(self.dumpDataAct)

        self.loadDataAct = EricAction(
            self.tr("Load Data"),
            self.tr("&Load Data"),
            0,
            0,
            self,
            "django_tools_loaddata",
        )
        self.loadDataAct.setStatusTip(self.tr("Load data from fixture files"))
        self.loadDataAct.setWhatsThis(
            self.tr("""<b>Load Data</b>""" """<p>Load data from fixture files.</p>""")
        )
        self.loadDataAct.triggered.connect(self.__loadData)
        self.actions.append(self.loadDataAct)

        self.runTestAct = EricAction(
            self.tr("Run Testsuite"),
            self.tr("Run &Testsuite"),
            0,
            0,
            self,
            "django_tools_run_test",
        )
        self.runTestAct.setStatusTip(
            self.tr("Run the test suite for applications or the whole site")
        )
        self.runTestAct.setWhatsThis(
            self.tr(
                """<b>Run Testsuite</b>"""
                """<p>Run the test suite for applications or the whole site.</p>"""
            )
        )
        self.runTestAct.triggered.connect(self.__runTestSuite)
        self.actions.append(self.runTestAct)

        self.runDeprecationTestAct = EricAction(
            self.tr("Run Testsuite (-Wall)"),
            self.tr("Run Testsuite (-Wall)"),
            0,
            0,
            self,
            "django_tools_run_deprecation_test",
        )
        self.runDeprecationTestAct.setStatusTip(
            self.tr(
                "Run the test suite for applications or the whole site with"
                " activated deprecation warnings"
            )
        )
        self.runDeprecationTestAct.setWhatsThis(
            self.tr(
                """<b>Run Testsuite (-Wall)</b>"""
                """<p>Run the test suite for applications or the whole site"""
                """ with activated deprecation warnings.</p>"""
            )
        )
        self.runDeprecationTestAct.triggered.connect(
            lambda: self.__runTestSuite(deprecation=True)
        )
        self.actions.append(self.runDeprecationTestAct)

        self.runTestServerAct = EricAction(
            self.tr("Run Testserver"),
            self.tr("Run Test&server"),
            0,
            0,
            self,
            "django_tools_run_test_server",
        )
        self.runTestServerAct.setStatusTip(
            self.tr("Run a development server with data from a set of fixtures")
        )
        self.runTestServerAct.setWhatsThis(
            self.tr(
                """<b>Run Testserver</b>"""
                """<p>Run a development server with data from a set of"""
                """ fixtures.</p>"""
            )
        )
        self.runTestServerAct.triggered.connect(self.__runTestServer)
        self.actions.append(self.runTestServerAct)

    def __initAuthorizationActions(self):
        """
        Private method to define the authorization actions.
        """
        self.changePasswordAct = EricAction(
            self.tr("Change Password"),
            self.tr("Change &Password"),
            0,
            0,
            self,
            "django_auth_changepassword",
        )
        self.changePasswordAct.setStatusTip(self.tr("Change the password of a user"))
        self.changePasswordAct.setWhatsThis(
            self.tr(
                """<b>Change Password</b>"""
                """<p>Change the password of a user of the Django project.</p>"""
            )
        )
        self.changePasswordAct.triggered.connect(self.__changePassword)
        self.actions.append(self.changePasswordAct)

        self.createSuperUserAct = EricAction(
            self.tr("Create Superuser"),
            self.tr("Create &Superuser"),
            0,
            0,
            self,
            "django_auth_createsuperuser",
        )
        self.createSuperUserAct.setStatusTip(self.tr("Create a superuser account"))
        self.createSuperUserAct.setWhatsThis(
            self.tr(
                """<b>Create Superuser</b>"""
                """<p>Create a superuser account for the Django project.</p>"""
            )
        )
        self.createSuperUserAct.triggered.connect(self.__createSuperUser)
        self.actions.append(self.createSuperUserAct)

    def __initSessionActions(self):
        """
        Private method to define the session actions.
        """
        self.clearSessionsAct = EricAction(
            self.tr("Clear Sessions"),
            self.tr("Clear &Sessions"),
            0,
            0,
            self,
            "django_session_clearsessions",
        )
        self.clearSessionsAct.setStatusTip(self.tr("Clear expired sessions"))
        self.clearSessionsAct.setWhatsThis(
            self.tr(
                """<b>Clear Sessions</b>"""
                """<p>Clear expired sessions of the Django project.</p>"""
            )
        )
        self.clearSessionsAct.triggered.connect(self.__clearSessions)
        self.actions.append(self.clearSessionsAct)

    def __initMigrationActions(self):
        """
        Private method to define the migration actions.
        """
        self.showMigrationsAct = EricAction(
            self.tr("Show Migrations"),
            self.tr("&Show Migrations"),
            0,
            0,
            self,
            "django_migration_show",
        )
        self.showMigrationsAct.setStatusTip(
            self.tr("Show a list of available migrations")
        )
        self.showMigrationsAct.setWhatsThis(
            self.tr(
                """<b>Show Migrations</b>"""
                """<p>This shows a list of available migrations of the Django"""
                """ project and their status.</p>"""
            )
        )
        self.showMigrationsAct.triggered.connect(self.__showMigrationsList)
        self.actions.append(self.showMigrationsAct)

        self.showMigrationsPlanAct = EricAction(
            self.tr("Show Migrations Plan"),
            self.tr("Show Migrations &Plan"),
            0,
            0,
            self,
            "django_migration_show_plan",
        )
        self.showMigrationsPlanAct.setStatusTip(
            self.tr("Show a list with the migrations plan")
        )
        self.showMigrationsPlanAct.setWhatsThis(
            self.tr(
                """<b>Show Migrations Plan</b>"""
                """<p>This shows a list with the migrations plan of the Django"""
                """ project.</p>"""
            )
        )
        self.showMigrationsPlanAct.triggered.connect(self.__showMigrationsPlan)
        self.actions.append(self.showMigrationsPlanAct)

        self.migrateAllAct = EricAction(
            self.tr("Apply All Migrations"),
            self.tr("&Apply All Migrations"),
            0,
            0,
            self,
            "django_migration_apply_all",
        )
        self.migrateAllAct.setStatusTip(self.tr("Apply all available migrations"))
        self.migrateAllAct.setWhatsThis(
            self.tr(
                """<b>Apply All Migrations</b>"""
                """<p>This applies all migrations of the Django project.</p>"""
            )
        )
        self.migrateAllAct.triggered.connect(self.__applyAllMigrations)
        self.actions.append(self.migrateAllAct)

        self.migrateSelectedAct = EricAction(
            self.tr("Apply Selected Migrations"),
            self.tr("Apply Selected Migrations"),
            0,
            0,
            self,
            "django_migration_apply_selected",
        )
        self.migrateSelectedAct.setStatusTip(self.tr("Apply selected migrations"))
        self.migrateSelectedAct.setWhatsThis(
            self.tr(
                """<b>Apply Selected Migrations</b>"""
                """<p>This applies selected migrations of the Django"""
                """ project.</p>"""
            )
        )
        self.migrateSelectedAct.triggered.connect(self.__applySelectedMigrations)
        self.actions.append(self.migrateSelectedAct)

        self.unmigrateAct = EricAction(
            self.tr("Unapply Migrations"),
            self.tr("&Unapply Migrations"),
            0,
            0,
            self,
            "django_migration_unapply",
        )
        self.unmigrateAct.setStatusTip(self.tr("Unapply all migrations for an app"))
        self.unmigrateAct.setWhatsThis(
            self.tr(
                """<b>Unapply Migrations</b>"""
                """<p>This unapplies all migrations for an app of the Django"""
                """ project.</p>"""
            )
        )
        self.unmigrateAct.triggered.connect(self.__unapplyMigrations)
        self.actions.append(self.unmigrateAct)

        self.makeMigrationsAct = EricAction(
            self.tr("Make Migrations"),
            self.tr("&Make Migrations"),
            0,
            0,
            self,
            "django_migration_make",
        )
        self.makeMigrationsAct.setStatusTip(
            self.tr("Generate migrations for the project")
        )
        self.makeMigrationsAct.setWhatsThis(
            self.tr(
                """<b>Make Migrations</b>"""
                """<p>This generates migrations for the Django project.</p>"""
            )
        )
        self.makeMigrationsAct.triggered.connect(self.__makeMigrations)
        self.actions.append(self.makeMigrationsAct)

        self.squashMigrationsAct = EricAction(
            self.tr("Squash Migrations"),
            self.tr("S&quash Migrations"),
            0,
            0,
            self,
            "django_migration_squash",
        )
        self.squashMigrationsAct.setStatusTip(
            self.tr("Squash migrations of an application of the project")
        )
        self.squashMigrationsAct.setWhatsThis(
            self.tr(
                """<b>Squash Migrations</b>"""
                """<p>This squashes migrations of an application of the"""
                """ Django project.</p>"""
            )
        )
        self.squashMigrationsAct.triggered.connect(self.__squashMigrations)
        self.actions.append(self.squashMigrationsAct)

    def initMenu(self):
        """
        Public method to initialize the Django menu.

        @return the menu generated
        @rtype QMenu
        """
        self.__menus = {}  # clear menus references

        menu = QMenu(self.tr("D&jango"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.selectSiteAct)
        menu.addSeparator()
        menu.addAction(self.runServerAct)
        menu.addAction(self.runBrowserAct)
        menu.addSeparator()
        menu.addAction(self.startProjectAct)
        menu.addAction(self.startGlobalApplicationAct)
        menu.addAction(self.startLocalApplicationAct)
        menu.addSeparator()
        menu.addAction(self.checkAct)
        menu.addSeparator()
        menu.addMenu(self.__initDatabaseMenu())
        menu.addMenu(self.__initMigrationsMenu())
        menu.addSeparator()
        menu.addMenu(self.__initToolsMenu())
        menu.addSeparator()
        menu.addAction(self.createCacheTableAct)
        menu.addSeparator()
        menu.addMenu(self.__initTestingMenu())
        menu.addSeparator()
        menu.addMenu(self.__initAuthorizationMenu())
        menu.addSeparator()
        menu.addMenu(self.__initSessionMenu())
        menu.addSeparator()
        menu.addAction(self.aboutDjangoAct)
        menu.addSeparator()
        menu.addAction(self.helpAct)

        self.__menus["main"] = menu

        return menu

    def __initDatabaseMenu(self):
        """
        Private method to initialize the database menu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("&Database"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.selectDatabaseNameAct)
        menu.addSeparator()
        menu.addAction(self.inspectDatabaseAct)
        menu.addSeparator()
        menu.addAction(self.flushDatabaseAct)
        menu.addSeparator()
        menu.addAction(self.databaseClientAct)
        menu.addSeparator()
        menu.addMenu(self.__initDatabaseSqlMenu())

        self.__menus["database"] = menu

        return menu

    def __initDatabaseSqlMenu(self):
        """
        Private method to initialize the database SQL submenu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("Show &SQL"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.databaseSqlFlushAct)
        menu.addAction(self.databaseSqlResetSeqAct)
        menu.addSeparator()
        menu.addAction(self.databaseSqlMigrateAct)
        menu.addAction(self.databaseSqlMigrateBackwardsAct)

        self.__menus["sql"] = menu

        return menu

    def __initMigrationsMenu(self):
        """
        Private method to initialize the Migrations submenu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("&Migrations"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.showMigrationsAct)
        menu.addAction(self.showMigrationsPlanAct)
        menu.addSeparator()
        menu.addAction(self.migrateAllAct)
        menu.addAction(self.migrateSelectedAct)
        menu.addAction(self.unmigrateAct)
        menu.addSeparator()
        menu.addAction(self.makeMigrationsAct)
        menu.addSeparator()
        menu.addAction(self.squashMigrationsAct)

        self.__menus["migrations"] = menu

        return menu

    def __initToolsMenu(self):
        """
        Private method to initialize the tools menu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("&Tools"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.diffSettingsAct)
        menu.addSeparator()
        menu.addAction(self.runPythonShellAct)
        menu.addSeparator()
        menu.addAction(self.testEmailAct)

        self.__menus["tools"] = menu

        return menu

    def __initTestingMenu(self):
        """
        Private method to initialize the testing menu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("T&esting"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.dumpDataAct)
        menu.addAction(self.loadDataAct)
        menu.addSeparator()
        menu.addAction(self.runTestAct)
        menu.addAction(self.runDeprecationTestAct)
        menu.addAction(self.runTestServerAct)

        self.__menus["testing"] = menu

        return menu

    def __initAuthorizationMenu(self):
        """
        Private method to initialize the authorization menu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("&Authorization"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.changePasswordAct)
        menu.addAction(self.createSuperUserAct)

        self.__menus["authorization"] = menu

        return menu

    def __initSessionMenu(self):
        """
        Private method to initialize the authorization menu.

        @return the menu generated
        @rtype QMenu
        """
        menu = QMenu(self.tr("&Session"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.clearSessionsAct)

        self.__menus["session"] = menu

        return menu

    def getMenu(self, name):
        """
        Public method to get a reference to the requested menu.

        @param name name of the menu
        @type str
        @return reference to the menu or None, if no menu with the given
            name exists
        @rtype QMenu
        """
        if name in self.__menus:
            return self.__menus[name]
        else:
            return None

    def getMenuNames(self):
        """
        Public method to get the names of all menus.

        @return menu names
        @rtype list of str
        """
        return list(self.__menus.keys())

    ##################################################################
    ## methods below implement the various hook related functions
    ##################################################################

    def registerOpenHook(self):
        """
        Public method to register the open hook to open a translations file
        in a translations editor.
        """
        if self.__hooksInstalled:
            editor = self.__plugin.getPreferences("TranslationsEditor")
            if editor:
                self.__translationsBrowser.addHookMethodAndMenuEntry(
                    "open",
                    self.openPOEditor,
                    self.tr("Open with {0}").format(os.path.basename(editor)),
                )
            else:
                self.__translationsBrowser.removeHookMethod("open")

    def projectOpenedHooks(self):
        """
        Public method to add our hook methods.
        """
        if self.__ericProject.getProjectType() == "Django":
            self.__formsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("forms")
            )
            self.__formsBrowser.addHookMethodAndMenuEntry(
                "newForm", self.newForm, self.tr("New template...")
            )

            self.__ericProject.projectLanguageAddedByCode.connect(
                self.__projectLanguageAdded
            )
            self.__translationsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("translations")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateAll", self.updateCatalogs, self.tr("Update all catalogs")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateSelected",
                self.updateSelectedCatalogs,
                self.tr("Update selected catalogs"),
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateAllWithObsolete",
                self.updateCatalogsWithObsolete,
                self.tr("Update all catalogs (with obsolete)"),
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateSelectedWithObsolete",
                self.updateSelectedCatalogsWithObsolete,
                self.tr("Update selected catalogs (with obsolete)"),
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "releaseAll", self.compileCatalogs, self.tr("Compile all catalogs")
            )
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "releaseSelected",
                self.compileSelectedCatalogs,
                self.tr("Compile selected catalogs"),
            )

            self.__hooksInstalled = True

            self.registerOpenHook()

    def projectClosedHooks(self):
        """
        Public method to remove our hook methods.
        """
        if self.__hooksInstalled:
            self.__formsBrowser.removeHookMethod("newForm")
            self.__formsBrowser = None

            self.__ericProject.projectLanguageAddedByCode.disconnect(
                self.__projectLanguageAdded
            )
            self.__translationsBrowser.removeHookMethod("generateAll")
            self.__translationsBrowser.removeHookMethod("generateSelected")
            self.__translationsBrowser.removeHookMethod("generateAllWithObsolete")
            self.__translationsBrowser.removeHookMethod("generateSelectedWithObsolete")
            self.__translationsBrowser.removeHookMethod("releaseAll")
            self.__translationsBrowser.removeHookMethod("releaseSelected")
            self.__translationsBrowser.removeHookMethod("open")
            self.__translationsBrowser = None

        self.__hooksInstalled = False

    def newForm(self, path):
        """
        Public method to create a new form.

        @param path full directory path for the new form file
        @type str
        """
        fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
            self.__ui,
            self.tr("New Form"),
            path,
            filter,
            None,
            EricFileDialog.DontConfirmOverwrite,
        )

        if not fname:
            # user aborted or didn't enter a filename
            return

        ext = QFileInfo(fname).suffix()
        if not ext:
            ex = selectedFilter.split("(*")[1].split(")")[0]
            if ex:
                fname += ex

        if os.path.exists(fname):
            res = EricMessageBox.yesNo(
                self.__ui,
                self.tr("New Form"),
                self.tr("The file already exists! Overwrite it?"),
                icon=EricMessageBox.Warning,
            )

            if not res:
                # user selected to not overwrite
                return

        try:
            with open(fname, "w") as f:
                f.write("<!DOCTYPE html>")
                f.write("<html>\n")
                f.write("  <head>\n")
                f.write('    <meta content="" />\n')
                f.write("    <title></title>\n")
                f.write(
                    '    <link rel="stylesheet" type="text/css"' ' href="style.css"/>'
                )
                f.write("    <!--[if lte IE 7]>")
                f.write(
                    '      <link rel="stylesheet" type="text/css"' ' href="ie.css"/>'
                )
                f.write("    <![endif]-->")
                f.write("  </head>\n")
                f.write("\n")
                f.write('  <body  class="bodyclass">\n')
                f.write('    <div id="container">')
                f.write("    </div>")
                f.write("  </body>\n")
                f.close()
                f.write("</html>\n")
        except OSError as e:
            EricMessageBox.critical(
                self.__ui,
                self.tr("New Form"),
                self.tr(
                    "<p>The new form file <b>{0}</b> could not be"
                    " created.<br> Problem: {1}</p>"
                ).format(fname, str(e)),
            )
            return

        self.__ericProject.appendFile(fname)
        self.__formsBrowser.sourceFile.emit(fname)

    ##################################################################
    ## slots below implement general functionality
    ##################################################################

    def projectClosed(self):
        """
        Public method to handle the closing of a project.
        """
        if self.__serverProc is not None:
            self.__serverProcFinished()
        self.__setCurrentSite(None)

    def __getExecutablePaths(self, file):
        """
        Private method to build all full paths of an executable file from
        the environment.

        @param file filename of the executable
        @type str
        @return list of full executable names, if the executable file is
            accessible via the searchpath defined by the PATH environment
            variable, or an empty list otherwise.
        @rtype list of str
        """
        paths = []

        if os.path.isabs(file):
            if os.access(file, os.X_OK):
                return [file]
            else:
                return []

        cur_path = os.path.join(os.curdir, file)
        if os.path.exists(cur_path) and os.access(cur_path, os.X_OK):
            paths.append(cur_path)

        path = os.getenv("PATH")

        # environment variable not defined
        if path is not None:
            dirs = path.split(os.pathsep)
            for directory in dirs:
                exe = os.path.join(directory, file)
                if os.access(exe, os.X_OK) and exe not in paths:
                    paths.append(exe)

        return paths

    def supportedPythonVariants(self):
        """
        Public method to get the supported Python variants.

        @return list of supported Python variants
        @rtype list of str
        """
        variants = []
        for variant in ["Python3"]:
            virtEnv = self.__getVirtualEnvironment(variant)
            if virtEnv:
                if self.__getDjangoAdminCommand(variant):
                    variants.append(variant)
            else:
                cmd = self.__getDjangoAdminCommand(variant)
                if isWindowsPlatform():
                    if cmd:
                        variants.append(variant)
                else:
                    if cmd:
                        try:
                            fullCmds = getExecutablePaths(cmd)
                        except AttributeError:
                            fullCmds = self.__getExecutablePaths(cmd)
                        for fullCmd in fullCmds:
                            try:
                                with open(fullCmd, "r", encoding="utf-8") as f:
                                    l0 = f.readline()
                            except OSError:
                                l0 = ""
                            if self.__isSuitableForVariant(variant, l0):
                                variants.append(variant)
                                break

        return variants

    def __isSuitableForVariant(self, variant, line0):
        """
        Private method to test, if a detected command file is suitable for the
        given Python variant.

        @param variant Python variant to test for
        @type str
        @param line0 first line of the executable
        @type str
        @return flag indicating a suitable file was found
        @rtype bool
        """
        l0 = line0.lower()
        ok = variant.lower() in l0 or "{0}.".format(variant[-1]) in l0
        ok |= "pypy3" in l0

        return ok

    def __getVirtualEnvironment(self, language=""):
        """
        Private method to get the path of the virtual environment.

        @param language Python variant to get the virtual environment
            for (one of '' or 'Python3')
        @type str
        @return path of the virtual environment
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()
        venvName = (
            self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
            if language == "Python3"
            else ""
        )
        if venvName:
            virtEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName)
            if not virtEnv:
                virtEnv = os.path.dirname(
                    self.__virtualEnvManager.getVirtualenvInterpreter(venvName)
                )
                if virtEnv.endswith(("Scripts", "bin")):
                    virtEnv = os.path.dirname(virtEnv)
        else:
            virtEnv = ""
        if virtEnv and not os.path.exists(virtEnv):
            virtEnv = ""
        return virtEnv

    def __getDebugEnvironment(self, language=""):
        """
        Private method to get the path of the debugger environment.

        @param language Python variant to get the debugger environment
            for (one of '' or 'Python3')
        @type str
        @return path of the debugger environment
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()
        debugEnv = self.__getVirtualEnvironment(language)
        if not debugEnv:
            if language == "Python3":
                venvName = Preferences.getDebugger("Python3VirtualEnv")
            else:
                venvName = ""

            if venvName:
                debugEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName)
            else:
                debugEnv = ""
        return debugEnv

    def __getDjangoAdminCommand(self, language=""):
        """
        Private method to build a django-admin.py command.

        @param language Python variant to get the django-admin.py
            command for (one of '' or 'Python3')
        @type str
        @return full django-admin.py command
        @rtype str
        """
        if not language:
            language = self.__ericProject.getProjectLanguage()

        virtualEnv = self.__getVirtualEnvironment(language)
        if virtualEnv:
            if isWindowsPlatform():
                for cmd in [
                    # standard Python
                    os.path.join(virtualEnv, "Scripts", "django-admin.exe"),
                    # PyPy
                    os.path.join(virtualEnv, "bin", "django-admin.py"),
                ]:
                    if os.path.exists(cmd):
                        break
                else:
                    cmd = ""
            else:
                cmds = [
                    os.path.join(virtualEnv, "bin", "django-admin"),
                    os.path.join(virtualEnv, "bin", "django-admin.py"),
                    os.path.join(virtualEnv, "local", "bin", "django-admin"),
                    os.path.join(virtualEnv, "local", "bin", "django-admin.py"),
                ]
                for cmd in cmds:
                    if os.path.exists(cmd):
                        break
                else:
                    cmd = ""
        else:
            if isWindowsPlatform():
                debugEnv = self.__getDebugEnvironment(language)
                for cmd in [
                    # standard Python
                    os.path.join(debugEnv, "Scripts", "django-admin.exe"),
                    # PyPy
                    os.path.join(debugEnv, "bin", "django-admin.py"),
                ]:
                    if os.path.exists(cmd):
                        break
                else:
                    cmd = ""
            else:
                if language == "Python3":
                    cmds = [
                        "django-admin3",
                        "django-admin3.py",
                        "django-admin.py-3.10",
                        "django-admin.py-3.9",
                        "django-admin.py-3.8",
                        "django-admin.py-3.7",
                        "django-admin.py-3.6",
                    ]
                else:
                    cmds = []
                cmds.extend(["django-admin", "django-admin.py"])
                for cmd in cmds:
                    if isinpath(cmd):
                        break
                else:
                    cmd = ""

        return cmd

    def __getPythonExecutable(self):
        """
        Private method to build the Python command.

        @return python command
        @rtype str
        """
        language = self.__ericProject.getProjectLanguage()
        if language == "Python3":
            venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
            if not venvName:
                # if none configured, use the global one
                venvName = Preferences.getDebugger("Python3VirtualEnv")
        else:
            venvName = ""
        python = (
            self.__virtualEnvManager.getVirtualenvInterpreter(venvName)
            if venvName
            else ""
        )

        return python

    def __djangoInfo(self):
        """
        Private slot to show some info about Django.
        """
        version = self.getDjangoVersionString()
        url = "https://www.djangoproject.com"

        msgBox = EricMessageBox.EricMessageBox(
            EricMessageBox.Question,
            self.tr("About Django"),
            self.tr(
                "<p>Django is a high-level Python Web framework that"
                " encourages rapid development and clean, pragmatic"
                " design.</p>"
                "<p><table>"
                "<tr><td>Version:</td><td>{0}</td></tr>"
                '<tr><td>URL:</td><td><a href="{1}">'
                "{1}</a></td></tr>"
                "</table></p>"
            ).format(version, url),
            modal=True,
            buttons=EricMessageBox.Ok,
        )
        msgBox.setIconPixmap(
            EricPixmapCache.getPixmap(
                os.path.join(
                    "ProjectDjango", "icons", "django64-{0}".format(self.__iconSuffix)
                )
            )
        )
        msgBox.exec()

    def getDjangoVersionString(self):
        """
        Public method to get the Django version as a string.

        @return Django version
        @rtype str
        """
        djangoVersion = ""

        args = ["--version"]
        ioEncoding = Preferences.getSystem("IOEncoding")
        cmd = self.__getDjangoAdminCommand()
        if cmd:
            if isWindowsPlatform():
                args.insert(0, cmd)
                cmd = self.__getPythonExecutable()

            process = QProcess()
            process.start(cmd, args)
            procStarted = process.waitForStarted()
            if procStarted:
                finished = process.waitForFinished(30000)
                if finished and process.exitCode() == 0:
                    output = str(process.readAllStandardOutput(), ioEncoding, "replace")
                    djangoVersion = output.splitlines()[0].strip()

        return djangoVersion

    def getDjangoVersion(self):
        """
        Public method to get the Django version as a tuple.

        @return Django version
        @rtype tuple of int
        """
        djangoVersionStr = self.getDjangoVersionString()
        djangoVersionList = []
        if djangoVersionStr:
            for part in djangoVersionStr.split("."):
                try:
                    djangoVersionList.append(int(part))
                except ValueError:
                    djangoVersionList.append(part)

        return tuple(djangoVersionList)

    def __getApplications(self):
        """
        Private method to ask the user for a list of application names.

        @return list of application names
        @rtype list of str
        """
        applStr, ok = QInputDialog.getItem(
            self.__ui,
            self.tr("Select Applications"),
            self.tr("Enter the list of applications separated by spaces."),
            self.getRecentApplications(),
            0,
            True,
        )
        if ok and applStr != "":
            self.setMostRecentApplication(applStr)
            return applStr.split()
        else:
            return []

    def __loadRecentApplications(self):
        """
        Private method to load the recently used applications list.
        """
        self.__recentApplications = []
        Preferences.Prefs.rsettings.sync()
        ra = Preferences.Prefs.rsettings.value(self.RecentApplicationsKey)
        if ra is not None:
            maxRecentApps = self.__plugin.getPreferences("RecentNumberApps")
            self.__recentApplications = ra[:maxRecentApps]

    def __saveRecentApplications(self):
        """
        Private method to save the list of recently used applications list.
        """
        Preferences.Prefs.rsettings.setValue(
            self.RecentApplicationsKey, self.__recentApplications
        )
        Preferences.Prefs.rsettings.sync()

    def getRecentApplications(self):
        """
        Public method to get the list of recent applications.

        @return list of recent applications entries (list of strings)
        """
        self.__loadRecentApplications()
        return self.__recentApplications

    def setMostRecentApplication(self, applStr):
        """
        Public method to set the most recently used applications entry.

        @param applStr applications entry
        @type str
        """
        if applStr in self.__recentApplications:
            self.__recentApplications.remove(applStr)
        self.__recentApplications.insert(0, applStr)

        maxRecentApps = self.__plugin.getPreferences("RecentNumberApps")
        if len(self.__recentApplications) > maxRecentApps:
            self.__recentApplications = self.__recentApplications[:maxRecentApps]
        self.__saveRecentApplications()

    def __loadRecentTestData(self):
        """
        Private method to load the recently used test data lists.
        """
        self.__recentTestData = {
            "RecentTestLabels": [],
            "RecentTestTags": [],
            "RecentTestExcludeTags": [],
        }
        Preferences.Prefs.rsettings.sync()
        maxRecentTestData = self.__plugin.getPreferences("RecentNumberTestData")
        for key in self.__recentTestData:
            recent = Preferences.Prefs.rsettings.value("Django/" + key)
            if recent is not None:
                self.__recentTestData[key] = recent[:maxRecentTestData]

    def __saveRecentTestData(self):
        """
        Private method to save the list of recently used test data.
        """
        for key in self.__recentTestData:
            Preferences.Prefs.rsettings.setValue(
                "Django/" + key, self.__recentTestData[key]
            )
        Preferences.Prefs.rsettings.sync()

    def getRecentTestData(self, key):
        """
        Public method to get the list of recent test data.

        @param key key (name) of the test data to get
        @type str
        @return list of recent test data entries
        @rtype list of str
        """
        self.__loadRecentTestData()
        return self.__recentTestData[key]

    def setMostRecentTestData(self, key, data):
        """
        Public method to set the most recently used test data entry.

        @param key key (name) of the test data to set
        @type str
        @param data test data entry to be set
        @type str
        """
        if data in self.__recentTestData[key]:
            self.__recentTestData[key].remove(data)
        self.__recentTestData[key].insert(0, data)

        maxRecentTestData = self.__plugin.getPreferences("RecentNumberTestData")
        if len(self.__recentTestData[key]) > maxRecentTestData:
            self.__recentTestData[key] = self.__recentTestData[key][:maxRecentTestData]
        self.__saveRecentTestData()

    def getProjectPath(self):
        """
        Public method to get the path of the eric7 project.

        @return path of the eric7 project
        @rtype str
        """
        return self.__ericProject.getProjectPath()

    def __showHelpIndex(self):
        """
        Private slot to show the help index page.
        """
        page = os.path.join(
            os.path.dirname(__file__), "Documentation", "help", "index.html"
        )
        self.__ui.launchHelpViewer(page)

    def __isSpawningConsole(self, consoleCmd):
        """
        Private method to check, if the given console is a spawning console.

        @param consoleCmd console command
        @type str
        @return tuple of two entries giving an indication, if the console
            is spawning and the (possibly) cleaned console command
        @rtype tuple of (bool, str)
        """
        if consoleCmd and consoleCmd[0] == "@":
            return (True, consoleCmd[1:])
        else:
            return (False, consoleCmd)

    def __adjustWorkingDirectory(self, args, wd):
        """
        Private method to adjust the working directory in the arguments list.

        @param args list of arguments to be modified
        @type list of str
        @param wd working directory
        @type str
        """
        if args[0].endswith("konsole") and "--workdir" in args:
            index = args.index("--workdir")
            args[index + 1] = wd
        elif args[0].endswith(("gnome-terminal", "mate-terminal")):
            for index in range(len(args)):
                if args[index].startswith("--working-directory="):
                    args[index] = "--working-directory={0}".format(wd)
                    break

    ##################################################################
    ## slots below implement creation functions
    ##################################################################

    def newProjectCreated(self):
        """
        Public slot to finish up the newly generated project.
        """
        if self.__ericProject.getProjectType() == "Django":
            ppath = self.__ericProject.getProjectPath()

            # get rid of an __init__.py file because it would be in our way
            initModule = os.path.join(ppath, "__init__.py")
            if os.path.exists(initModule):
                self.__ericProject.deleteFile("__init__.py")
                self.__ericProject.saveProject()

    def startProjectOrApplication(self):
        """
        Public slot to start a new Django project or application.
        """
        if self.__ericProject.getProjectType() == "Django":
            projectStr = self.tr("Project")
            applStr = self.tr("Application")
            selections = ["", projectStr, applStr]
            selection, ok = QInputDialog.getItem(
                self.__ui,
                self.tr("Start Django"),
                self.tr(
                    "Select if this project should be a "
                    "Django Project or Application.<br />"
                    "Select the empty entry for none."
                ),
                selections,
                0,
                False,
            )
            if ok and bool(selection):
                if selection == projectStr:
                    path, projectName = os.path.split(
                        self.__ericProject.getProjectPath()
                    )
                    self.__createProject(projectName, path)
                elif selection == applStr:
                    path, applName = os.path.split(self.__ericProject.getProjectPath())
                    self.__createApplication(applName, path)

    def __createProject(self, projectName, path):
        """
        Private slot to create a new Django project.

        @param projectName name of the new project
        @type str
        @param path the directory where the project should be created
        @type str
        @return flag indicating a successful creation
        @rtype bool
        """
        title = self.tr("Start Django Project")

        # remove the project directory if it exists already
        ppath = os.path.join(path, projectName)
        if os.path.exists(ppath):
            okToRemove = EricMessageBox.yesNo(
                self.__ui,
                title,
                self.tr(
                    """<p>The Django project path <b>{0}</b> exists"""
                    """ already. Shall it be removed and recreated?"""
                    """</p>"""
                ).format(ppath),
            )
            if not okToRemove:
                EricMessageBox.information(
                    self.__ui,
                    title,
                    self.tr(
                        """<p>Please add the files to the eric project"""
                        """ manually.</p>"""
                    ),
                )
                return True

            shutil.rmtree(ppath, ignore_errors=True)

        args = []
        cmd = self.__getDjangoAdminCommand()
        if cmd:
            if isWindowsPlatform():
                args.append(self.__getPythonExecutable())
            args.append(cmd)
        else:
            EricMessageBox.critical(
                self.__ui,
                title,
                self.tr(
                    """<p>The <b>django-admin.py</b> script is"""
                    """ not in the path. Aborting...</p>"""
                ),
            )
            return False

        args.append("startproject")
        args.append(projectName)

        dia = DjangoDialog(
            title, msgSuccess=self.tr("Django project created successfully.")
        )
        res = dia.startProcess(args, path)
        if res:
            dia.exec()

            # create the base directory for translations
            i18nPath = os.path.join(path, projectName, "locale")
            if not os.path.exists(i18nPath):
                os.makedirs(i18nPath)

            if os.path.join(path, projectName) == self.__ericProject.getProjectPath():
                self.__setCurrentSite("")
            else:
                self.__setCurrentSite(projectName)

        return res

    def __startProject(self):
        """
        Private slot to start a new Django project.
        """
        projectName, ok = QInputDialog.getText(
            self.__ui,
            self.tr("Start Django Project"),
            self.tr("Enter the name of the new Django project."),
            QLineEdit.EchoMode.Normal,
        )
        if ok and projectName != "":
            res = self.__createProject(projectName, self.__ericProject.getProjectPath())
            if res:
                # search for new files and add them to the project
                sitePath = os.path.join(
                    self.__ericProject.getProjectPath(), projectName
                )
                for entry in os.walk(sitePath):
                    for fileName in entry[2]:
                        fullName = os.path.join(entry[0], fileName)
                        self.__ericProject.appendFile(fullName)

    def __createApplication(self, applName, path, isGlobal=True):
        """
        Private slot to create a new Django application.

        @param applName name of the new application
        @type str
        @param path the directory where the application should be created
        @type str
        @param isGlobal flag indicating a standalone Django application
        @type bool
        @return flag indicating a successful creation
        @rtype bool
        """
        title = self.tr("Start Django Application")

        # remove the application directory if it exists already
        apath = os.path.join(path, applName)
        if os.path.exists(apath):
            shutil.rmtree(apath, ignore_errors=True)

        args = []
        if isGlobal:
            cmd = self.__getDjangoAdminCommand()
            if cmd:
                if isWindowsPlatform():
                    args.append(self.__getPythonExecutable())
                args.append(cmd)
            else:
                EricMessageBox.critical(
                    self.__ui,
                    title,
                    self.tr(
                        """<p>The <b>django-admin.py</b> script"""
                        """ is not in the path."""
                        """ Aborting...</p>"""
                    ),
                )
                return False
        else:
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            try:
                path = self.__sitePath()
            except DjangoNoSiteSelectedError:
                return False
        args.append("startapp")
        args.append(applName)

        dia = DjangoDialog(
            title, msgSuccess=self.tr("Django application created successfully.")
        )
        res = dia.startProcess(args, path)
        if res:
            dia.exec()
        return res

    def __startGlobalApplication(self):
        """
        Private slot to start a new global Django application.
        """
        applName, ok = QInputDialog.getText(
            self.__ui,
            self.tr("Start Global Django Application"),
            self.tr("Enter the name of the new global Django" " application."),
            QLineEdit.EchoMode.Normal,
        )
        if ok and applName != "":
            res = self.__createApplication(
                applName, self.__ericProject.getProjectPath()
            )
            if res:
                # search for new files and add them to the project
                appPath = os.path.join(self.__ericProject.getProjectPath(), applName)
                for entry in os.walk(appPath):
                    for fileName in entry[2]:
                        fullName = os.path.join(entry[0], fileName)
                        self.__ericProject.appendFile(fullName)

    def __startLocalApplication(self):
        """
        Private slot to start a new local Django application.
        """
        applName, ok = QInputDialog.getText(
            self.__ui,
            self.tr("Start Local Django Application"),
            self.tr("Enter the name of the new local Django application."),
            QLineEdit.EchoMode.Normal,
        )
        if ok and applName != "":
            res = self.__createApplication(applName, "", False)
            if res:
                try:
                    # search for new files and add them to the project
                    appPath = os.path.join(self.__sitePath(), applName)
                    for entry in os.walk(appPath):
                        for fileName in entry[2]:
                            fullName = os.path.join(entry[0], fileName)
                            self.__ericProject.appendFile(fullName)
                except DjangoNoSiteSelectedError:
                    return

    ##################################################################
    ## methods below implement site related functions
    ##################################################################

    def __findSites(self):
        """
        Private method to determine the relative path to all manage.py scripts.

        @return list of sites
        @rtype list of str
        """
        sites = []
        for file in sorted(self.__ericProject.getSources()):
            if os.path.basename(file) == "manage.py":
                sites.append(os.path.dirname(file))
        return sites

    def __selectSite(self):
        """
        Private method to select a site to work with.
        """
        sites = self.__findSites()
        if len(sites) == 1:
            site = sites[0]
        else:
            if self.__currentSite is not None:
                if self.__currentSite in sites:
                    cur = sites.index(self.__currentSite)
                else:
                    cur = 0
            else:
                cur = 0
            site, ok = QInputDialog.getItem(
                self.__ui,
                self.tr("Select Project"),
                self.tr("Select the Django project to work with."),
                sites,
                cur,
                False,
            )
            if not ok:
                site = None
        self.__setCurrentSite(site)

    def __sitePath(self):
        """
        Private method to calculate the full path of the Django site.

        @return path of the site
        @rtype str
        @exception DjangoNoSiteSelectedError raised, if no site is selected
        """
        if self.__currentSite is None:
            self.__selectSite()

        if self.__currentSite is None:
            raise DjangoNoSiteSelectedError
        else:
            path = os.path.join(self.__ericProject.getProjectPath(), self.__currentSite)
            return path

    def __setCurrentSite(self, site):
        """
        Private slot to set the current site.

        @param site name of the site
        @type str
        """
        self.__currentSite = site
        if self.__currentSite is None:
            curSite = self.tr("None")
        elif self.__currentSite == "":
            curSite = self.tr("Project")
        else:
            curSite = self.__currentSite
        self.selectSiteAct.setText(
            self.tr("&Current Django project ({0})").format(curSite)
        )

        if self.__currentSite is None:
            self.__ericProject.setTranslationPattern("")
        else:
            self.__ericProject.setTranslationPattern(
                os.path.join(site, "locale", "%language%", "LC_MESSAGES", "django.po")
            )

    def __site(self):
        """
        Private method to get the name of the current site.

        @return name of the site
        @rtype str
        @exception DjangoNoSiteSelectedError raised, if no site is selected
        """
        if self.__currentSite is None:
            self.__selectSite()

        if self.__currentSite is None:
            raise DjangoNoSiteSelectedError
        else:
            return self.__currentSite

    ##################################################################
    ## slots below implement run functions
    ##################################################################

    def __runServer(self):
        """
        Private slot to start the Django Web server.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("runserver")
            if self.__plugin.getPreferences("UseIPv6"):
                args.append("--ipv6")
            if not self.__plugin.getPreferences("UseThreading"):
                args.append("--nothreading")
            addr = self.__plugin.getPreferences("ServerAddress")
            if addr:
                args.append(addr)

            with contextlib.suppress(DjangoNoSiteSelectedError):
                if isWindowsPlatform():
                    serverProcStarted, pid = QProcess.startDetached(
                        args[0], args[1:], self.__sitePath()
                    )
                else:
                    if self.__serverProc is not None:
                        self.__serverProcFinished()

                    self.__serverProc = QProcess()
                    self.__serverProc.finished.connect(self.__serverProcFinished)
                    self.__serverProc.setWorkingDirectory(self.__sitePath())
                    self.__serverProc.start(args[0], args[1:])
                    serverProcStarted = self.__serverProc.waitForStarted()
                if not serverProcStarted:
                    EricMessageBox.critical(
                        None,
                        self.tr("Process Generation Error"),
                        self.tr("The Django server could not be started."),
                    )

    def __serverProcFinished(self):
        """
        Private slot connected to the finished signal.
        """
        if (
            self.__serverProc is not None
            and self.__serverProc.state() != QProcess.ProcessState.NotRunning
        ):
            self.__serverProc.terminate()
            QTimer.singleShot(2000, self.__serverProc.kill)
            self.__serverProc.waitForFinished(3000)
        self.__serverProc = None

    def __runBrowser(self):
        """
        Private slot to start the default web browser with the server URL.
        """
        addr = self.__plugin.getPreferences("ServerAddress")
        ipv6 = self.__plugin.getPreferences("UseIPv6")
        if addr:
            # test for an IPv6 and port address
            if "]:" in addr:
                addr, port = addr.rsplit(":", 1)
            elif ":" in addr:
                addr, port = addr.split(":", 1)
            else:
                port = addr
                if ipv6:
                    addr = "[::1]"
                else:
                    addr = "127.0.0.1"
        else:
            port = "8000"
            if ipv6:
                addr = "[::1]"
            else:
                addr = "127.0.0.1"
        url = "http://{0}:{1}".format(addr, port)
        if self.__plugin.getPreferences("UseExternalBrowser"):
            res = QDesktopServices.openUrl(QUrl(url))
            if not res:
                EricMessageBox.critical(
                    None,
                    self.tr("Run Web-Browser"),
                    self.tr(
                        "Could not start the web-browser for the" ' url "{0}".'
                    ).format(url.toString()),
                )
        else:
            self.__ui.launchHelpViewer(url)

    ##################################################################
    ## slots below implement functions to save and load recently used
    ## database names
    ##################################################################

    def __loadRecentDatabaseNames(self):
        """
        Private method to load the list of recently used database names.
        """
        self.__recentDatabaseNames = [""]
        Preferences.Prefs.rsettings.sync()
        rdb = Preferences.Prefs.rsettings.value(self.RecentDatabaseNamesKey)
        if rdb is not None:
            maxRecentDatabaseNames = self.__plugin.getPreferences(
                "RecentNumberDatabaseNames"
            )
            self.__recentDatabaseNames = rdb[:maxRecentDatabaseNames]

    def __saveRecentDatabaseNames(self):
        """
        Private method to save the list of recently used database names.
        """
        Preferences.Prefs.rsettings.setValue(
            self.RecentDatabaseNamesKey, self.__recentDatabaseNames
        )
        Preferences.Prefs.rsettings.sync()

    def getRecentDatabaseNames(self):
        """
        Public method to get the list of recently used database names.

        @return list of recently used database names
        @rtype list of str
        """
        self.__loadRecentDatabaseNames()
        return self.__recentDatabaseNames

    def setMostRecentDatabaseNames(self, dbName):
        """
        Public method to set the most recently used database names.

        @param dbName database name
        @type str
        """
        if dbName in self.__recentDatabaseNames:
            self.__recentDatabaseNames.remove(dbName)
        self.__recentDatabaseNames.insert(0, dbName)

        maxRecentDatabaseNames = self.__plugin.getPreferences(
            "RecentNumberDatabaseNames"
        )
        if len(self.__recentDatabaseNames) > maxRecentDatabaseNames:
            self.__recentDatabaseNames = self.__recentDatabaseNames[
                :maxRecentDatabaseNames
            ]
        self.__saveRecentDatabaseNames()

    def __selectDatabaseName(self):
        """
        Private method to select the name of the database to work with.
        """
        recentDatabases = self.getRecentDatabaseNames()[:]
        if "" not in recentDatabases:
            recentDatabases.insert(1, "")

        selectedDatabase, ok = QInputDialog.getItem(
            self.__ui,
            self.tr("Database Name"),
            self.tr("Select a database name (leave empty for default):"),
            recentDatabases,
            0,
            True,
        )

        if ok:
            self.setMostRecentDatabaseNames(selectedDatabase)
            self.__setCurrentDatabase(selectedDatabase)

    def __setCurrentDatabase(self, database):
        """
        Private method to set the database name to be used.

        @param database name of the database
        @type str
        """
        if database is None:
            database = self.getRecentDatabaseNames()[0]

        self.__currentDatabase = database
        curDb = database if database else self.tr("<default>")
        self.selectDatabaseNameAct.setText(
            self.tr("&Current Database ({0})").format(curDb)
        )

    def currentDatabase(self):
        """
        Public method to get the database name to be used.

        @return database name
        @rtype str
        """
        return self.__currentDatabase

    ##################################################################
    ## slots below implement database related functions
    ##################################################################

    def __databaseInspect(self):
        """
        Private slot to introspect the database and output a Django model
        module.
        """
        title = self.tr("Introspect Database")

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("inspectdb")
        if self.__currentDatabase:
            args.append("--database={0}".format(self.__currentDatabase))

        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        dia = DjangoDialog(title, fixed=True, linewrap=False)
        res = dia.startProcess(args, path, False)
        if res:
            dia.exec()

    def __databaseFlush(self):
        """
        Private slot to return all database tables to the state just after
        their installation.
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        title = self.tr("Flush Database")

        res = EricMessageBox.yesNo(
            self.__ui,
            title,
            self.tr(
                """Flushing the database will destroy all data.""" """ Are you sure?"""
            ),
        )
        if res:
            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("flush")
            args.append("--noinput")
            if self.__currentDatabase:
                args.append("--database={0}".format(self.__currentDatabase))

            dia = DjangoDialog(
                title, msgSuccess=self.tr("Database tables flushed" " successfully.")
            )
            res = dia.startProcess(args, path)
            if res:
                dia.exec()

    def __runDatabaseClient(self):
        """
        Private slot to start a database client for a Django project.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("dbshell")
            if self.__currentDatabase:
                args.append("--database={0}".format(self.__currentDatabase))
            with contextlib.suppress(DjangoNoSiteSelectedError):
                wd = self.__sitePath()
                self.__adjustWorkingDirectory(args, wd)
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    EricMessageBox.critical(
                        None,
                        self.tr("Process Generation Error"),
                        self.tr("The Django process could not be started."),
                    )

    #######################################################################
    ## slots below implement database functions outputting SQL statements
    #######################################################################

    def __sqlCommand(self, title, command, requestApps=True):
        """
        Private method to perform an SQL creation function.

        @param title dialog title
        @type str
        @param command Django sql... command
        @type str
        @param requestApps flag indicating to request a list of applications
            to work on
        @type bool
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        if requestApps:
            apps = self.__getApplications()
            if not apps:
                return
        else:
            apps = []

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append(command)
        if self.__currentDatabase:
            args.append("--database={0}".format(self.__currentDatabase))
        args += apps

        fileFilter = self.tr("SQL Files (*.sql)")

        dia = DjangoDialog(title, fixed=True, linewrap=False, saveFilters=fileFilter)
        res = dia.startProcess(args, path, False)
        if res:
            dia.exec()

    def __databaseSqlFlushDatabase(self):
        """
        Private slot to print a list of statements to return all database
        tables to their initial state.
        """
        self.__sqlCommand(self.tr("Flush Database"), "sqlflush", False)

    def __databaseSqlResetSequences(self):
        """
        Private slot to print the SQL statements for resetting sequences for
        one or more applications.
        """
        self.__sqlCommand(self.tr("Reset Sequences"), "sqlsequencereset")

    def __databaseSqlMigrate(self, backwards=False):
        """
        Private slot to print the SQL statements for a migration of an
        application.

        @param backwards flag indicating to generate the SQL code to revert
            a migration
        @type bool
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        migrations = self.__getMigrations()
        if not migrations:
            EricMessageBox.information(
                None, self.tr("SQL Migrate"), self.tr("""No migrations available.""")
            )
            return

        title = self.tr("SQL Migrate")

        from .DjangoMigrationSelectionDialog import DjangoMigrationSelectionDialog

        dlg = DjangoMigrationSelectionDialog(
            migrations, migrationRequired=True, suffix=self.__iconSuffix
        )
        if dlg.exec() == QDialog.DialogCode.Accepted:
            app, migration = dlg.getData()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("sqlmigrate")
            if self.__currentDatabase:
                args.append("--database={0}".format(self.__currentDatabase))
            if backwards:
                args.append("--backwards")
            args.append(app)
            args.append(migration)

            fileFilter = self.tr("SQL Files (*.sql)")

            dia = DjangoDialog(
                title, fixed=True, linewrap=False, saveFilters=fileFilter
            )
            res = dia.startProcess(args, path, False)
            if res:
                dia.exec()

    ##################################################################
    ## slots below implement migration related functions
    ##################################################################

    def __showMigrationsList(self):
        """
        Private slot to show the available migrations and their status.
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        from .DjangoMigrationsListDialog import DjangoMigrationsListDialog

        self.__migrationsListDialog = DjangoMigrationsListDialog(
            DjangoMigrationsListDialog.MigrationsListMode, self, self.__ui
        )
        self.__migrationsListDialog.show()
        self.__migrationsListDialog.start(
            self.__getPythonExecutable(), path, self.__currentDatabase
        )

    def __showMigrationsPlan(self):
        """
        Private slot to show the migrations plan.
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        from .DjangoMigrationsListDialog import DjangoMigrationsListDialog

        self.__migrationsPlanDialog = DjangoMigrationsListDialog(
            DjangoMigrationsListDialog.MigrationsPlanMode, self, self.__ui
        )
        self.__migrationsPlanDialog.show()
        self.__migrationsPlanDialog.start(
            self.__getPythonExecutable(), path, self.__currentDatabase
        )

    def __applyAllMigrations(self):
        """
        Private slot to apply all migrations.
        """
        self.applyMigrations()

    def __applySelectedMigrations(self):
        """
        Private slot to apply selected migrations of a selected app.
        """
        migrations = self.__getMigrations()
        if not migrations:
            EricMessageBox.information(
                None,
                self.tr("Apply Selected Migrations"),
                self.tr("""No migrations available."""),
            )
            return

        from .DjangoMigrationSelectionDialog import DjangoMigrationSelectionDialog

        dlg = DjangoMigrationSelectionDialog(migrations, suffix=self.__iconSuffix)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            app, migration = dlg.getData()
            self.applyMigrations(app=app, migration=migration)

    def applyMigrations(self, app=None, migration=None):
        """
        Public slot to apply migrations.

        @param app name of an application to apply migrations for
        @type str
        @param migration name of a migration to update to
        @type str
        """
        title = (
            self.tr("Unapply Migrations")
            if migration == "zero"
            else self.tr("Apply Migrations")
        )

        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("migrate")
        args.append("--noinput")
        if app:
            args.append(app)
            if migration:
                args.append(migration)

        dia = DjangoDialog(title)
        res = dia.startProcess(args, path)
        if res:
            dia.exec()

    def __unapplyMigrations(self):
        """
        Private slot to revert all migrations of an application.
        """
        apps = sorted(self.__getMigrations().keys())
        if not apps:
            EricMessageBox.information(
                None,
                self.tr("Unapply Migrations"),
                self.tr("""No migrations available."""),
            )
            return

        app, ok = QInputDialog.getItem(
            None,
            self.tr("Unapply Migrations"),
            self.tr("Select an application:"),
            [""] + apps,
            0,
            False,
        )
        if ok and app:
            self.applyMigrations(app=app, migration="zero")

    def __getMigrations(self):
        """
        Private method to get the available migrations.

        @return dictionary containing the available migrations
        @rtype dict with app name as key (str) and list of tuples of
            applied indication (bool) and migration name (str) as value
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return {}

        args = []
        args.append("manage.py")
        args.append("showmigrations")
        args.append("--list")
        if self.__currentDatabase:
            args.append("--database={0}".format(self.__currentDatabase))

        migrations = {}
        proc = QProcess()
        if path:
            proc.setWorkingDirectory(path)
        proc.start(self.__getPythonExecutable(), args)
        if proc.waitForStarted() and proc.waitForFinished():
            output = str(
                proc.readAllStandardOutput(),
                Preferences.getSystem("IOEncoding"),
                "replace",
            )
            if output:
                recentApp = ""
                for line in output.splitlines():
                    if not line.startswith(" "):
                        # application name
                        recentApp = line.strip()
                        migrations[recentApp] = []
                    else:
                        # migration name
                        line = line.strip()
                        applied = line[1] != " "
                        name = line[3:].strip()
                        if recentApp:
                            migrations[recentApp].append((applied, name))
        return migrations

    def __makeMigrations(self):
        """
        Private slot to generate migrations for the Django project.
        """
        from .DjangoMakeMigrationsDialog import DjangoMakeMigrationsDialog

        dlg = DjangoMakeMigrationsDialog(self.getRecentApplications())
        if dlg.exec() == QDialog.DialogCode.Accepted:
            apps, migration, dryRun, empty, merge = dlg.getData()
            if apps:
                self.setMostRecentApplication(apps)
            apps = apps.split()
            self.makeMigrations(apps, migration, dryRun, empty, merge)

    def makeMigrations(
        self, apps, migration=None, dryRun=False, empty=False, merge=False
    ):
        """
        Public method to generate migrations.

        @param apps list of application names to generate migrations for
        @type list of str
        @param migration name of the migration to generate
        @type str
        @param dryRun flag indicating a dry run
        @type bool
        @param empty flag indicating the creation of an empty migration
        @type bool
        @param merge flag indicating to fix migration conflicts
        @type bool
        """
        title = self.tr("Make Migrations")

        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemigrations")
        if migration:
            args.append("--name")
            args.append(migration.replace(" ", "_"))
        if dryRun:
            args.append("--dry-run")
        if empty:
            args.append("--empty")
        if merge:
            args.append("--merge")
        if apps:
            args += apps

        dia = DjangoDialog(title, showInput=True)
        res = dia.startProcess(args, path)
        if res:
            dia.exec()

    def __squashMigrations(self):
        """
        Private slot to squash migrations.
        """
        migrations = self.__getMigrations()
        if not migrations:
            EricMessageBox.information(
                None,
                self.tr("Squash Migrations"),
                self.tr("""No migrations available."""),
            )
            return

        from .DjangoSquashMigrationSelectionDialog import (
            DjangoSquashMigrationSelectionDialog,
        )

        dlg = DjangoSquashMigrationSelectionDialog(migrations, self, self.__iconSuffix)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            app, start, end, noOptimize, name = dlg.getData()

            title = self.tr("Squash Migrations")

            try:
                path = self.__sitePath()
            except DjangoNoSiteSelectedError:
                return

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("squashmigrations")
            args.append("--noinput")
            if noOptimize:
                args.append("--no-optimize")
            if name:
                args.append("--squashed-name={0}".format(name))
            args.append(app)
            if start:
                args.append(start)
            args.append(end)

            dia = DjangoDialog(title)
            res = dia.startProcess(args, path)
            if res:
                dia.exec()

    ##################################################################
    ## slots below implement some tool functions
    ##################################################################

    def __diffSettings(self):
        """
        Private slot to show the changes made to the settings.py file.
        """
        title = self.tr("Diff Settings")

        from .DjangoDiffsettingsDataDialog import DjangoDiffsettingsDataDialog

        dlg = DjangoDiffsettingsDataDialog(self, self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            showAll, defaultModule, outputFormat = dlg.getData()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("diffsettings")
            if showAll:
                args.append("--all")
            if defaultModule:
                args.append("--default={0}".format(defaultModule))
            if outputFormat:
                args.append("--output={0}".format(outputFormat))

            try:
                path = self.__sitePath()
            except DjangoNoSiteSelectedError:
                return

            dia = DjangoDialog(title, fixed=True, linewrap=False)
            res = dia.startProcess(args, path, False)
            if res:
                dia.exec()

    def __runPythonShell(self):
        """
        Private slot to start a Python console for a Django project.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("shell")
            args.append(
                "--interface={0}".format(
                    self.__plugin.getPreferences("Python3ConsoleType")
                )
            )
            with contextlib.suppress(DjangoNoSiteSelectedError):
                wd = self.__sitePath()
                self.__adjustWorkingDirectory(args, wd)
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    EricMessageBox.critical(
                        None,
                        self.tr("Process Generation Error"),
                        self.tr("The Django process could not be started."),
                    )

    def __sendTestEmail(self):
        """
        Private slot to send a test email through Django.
        """
        title = self.tr("Send Test Email")

        from .DjangoSendTestEmailDataDialog import DjangoSendTestEmailDataDialog

        dlg = DjangoSendTestEmailDataDialog(self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            managers, admins, recipients = dlg.getData()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("sendtestemail")
            if managers:
                args.append("--managers")
            if admins:
                args.append("--admins")
            args.extend(recipients)

            try:
                path = self.__sitePath()
            except DjangoNoSiteSelectedError:
                return

            dia = DjangoDialog(
                title,
                linewrap=False,
                msgSuccess=self.tr("Test Email sent successfully."),
                msgError=self.tr("Test Email could not be sent."),
            )
            res = dia.startProcess(args, path, False)
            if res:
                dia.exec()

    ##################################################################
    ## slots below implement caching functions
    ##################################################################

    def __createCacheTables(self):
        """
        Private slot to create the tables for the SQL caching backend.
        """
        title = self.tr("Create Cache Tables")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("createcachetable")
        if self.__currentDatabase:
            args.append("--database={0}".format(self.__currentDatabase))

        dia = DjangoDialog(
            title, msgSuccess=self.tr("Cache tables created successfully.")
        )
        res = dia.startProcess(args, wd)
        if res:
            dia.exec()

    ##################################################################
    ## slots below implement testing functions
    ##################################################################

    def __dumpData(self):
        """
        Private slot to dump the database data to a fixture.
        """
        title = self.tr("Dump Data")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        from .DjangoDumpdataDataDialog import DjangoDumpdataDataDialog

        dlg = DjangoDumpdataDataDialog(self, self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            appls, excls, dumpFormat, indent = dlg.getData()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("dumpdata")
            args.append("--format={0}".format(dumpFormat))
            args.append("--indent={0}".format(indent))
            for excl in excls:
                args.append("--exclude={0}".format(excl))
            if self.__currentDatabase:
                args.append("--database={0}".format(self.__currentDatabase))
            args += appls

            if dumpFormat == "json":
                fileFilters = self.tr("JSON Files (*.json)")
            elif dumpFormat == "xml":
                fileFilters = self.tr("XML Files (*.xml)")
            elif dumpFormat == "yaml":
                fileFilters = self.tr("YAML Files (*.yaml)")

            dia = DjangoDialog(
                title, fixed=True, linewrap=False, saveFilters=fileFilters
            )
            res = dia.startProcess(args, wd, showCommand=False)
            if res:
                dia.exec()

    def __loadData(self):
        """
        Private slot to load data from fixture files.
        """
        title = self.tr("Load Data")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        from .DjangoLoaddataDataDialog import DjangoLoaddataDataDialog

        dlg = DjangoLoaddataDataDialog(self, self.__ui)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            fixtures, excludes, appLabel, ignore = dlg.getData()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("loaddata")
            for excl in excludes:
                args.append("--exclude={0}".format(excl))
            if ignore:
                args.append("--ignorenonexistent")
            if appLabel:
                args.append("--app={0}".format(appLabel))
            if self.__currentDatabase:
                args.append("--database={0}".format(self.__currentDatabase))
            args += fixtures

            dia = DjangoDialog(title)
            res = dia.startProcess(args, wd)
            if res:
                dia.exec()

    def __runTestSuite(self, deprecation=False):
        """
        Private slot to run the test suite for applications or the whole site.

        @param deprecation flag indicating to test for deprecation warnings
        @type bool
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommandNoClose")
        )[1]
        if consoleCmd:
            try:
                wd = self.__sitePath()
            except DjangoNoSiteSelectedError:
                return

            from .DjangoTestDataDialog import DjangoTestDataDialog

            dlg = DjangoTestDataDialog(
                self, self.__plugin.getPreferences("KeepTestDatabase"), self.__ui
            )
            if dlg.exec() == QDialog.DialogCode.Accepted:
                labels, pattern, tags, excludeTags, keep, reverse = dlg.getData()

                self.__plugin.setPreferences("KeepTestDatabase", keep)

                args = Utilities.parseOptionString(consoleCmd)
                args[0] = getExecutablePath(args[0])
                args.append(self.__getPythonExecutable())
                if deprecation:
                    args.append("-Wall")
                args.append("manage.py")
                args.append("test")
                if pattern:
                    args.append("--pattern=" + pattern)
                for tag in tags:
                    args.append("--tag=" + tag)
                for tag in excludeTags:
                    args.append("--exclude-tag=" + tag)
                if keep:
                    args.append("--keepdb")
                if reverse:
                    args.append("--reverse")
                args.extend(labels)

                self.__adjustWorkingDirectory(args, wd)
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    EricMessageBox.critical(
                        None,
                        self.tr("Process Generation Error"),
                        self.tr("The Django process could not be started."),
                    )

    def __runTestServer(self):
        """
        Private slot to run a development server with data from a set of
        fixtures.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand")
        )[1]
        if consoleCmd:
            from .DjangoRunTestServerDataDialog import DjangoRunTestServerDataDialog

            dlg = DjangoRunTestServerDataDialog(self, self.__ui)
            if dlg.exec() == QDialog.DialogCode.Accepted:
                fixtures = dlg.getData()

                args = Utilities.parseOptionString(consoleCmd)
                args[0] = getExecutablePath(args[0])
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("testserver")
                if self.__plugin.getPreferences("UseIPv6"):
                    args.append("--ipv6")
                addr = self.__plugin.getPreferences("ServerAddress")
                if addr:
                    args.append("--addrport={0}".format(addr))
                args += fixtures

                with contextlib.suppress(DjangoNoSiteSelectedError):
                    if isWindowsPlatform():
                        serverProcStarted, pid = QProcess.startDetached(
                            args[0], args[1:], self.__sitePath()
                        )
                    else:
                        if self.__testServerProc is not None:
                            self.__testServerProcFinished()

                        self.__testServerProc = QProcess()
                        self.__testServerProc.finished.connect(
                            self.__serverProcFinished
                        )
                        self.__testServerProc.setWorkingDirectory(self.__sitePath())
                        self.__testServerProc.start(args[0], args[1:])
                        serverProcStarted = self.__testServerProc.waitForStarted()
                    if not serverProcStarted:
                        EricMessageBox.critical(
                            None,
                            self.tr("Process Generation Error"),
                            self.tr("The Django test server could not be" " started."),
                        )

    def __testServerProcFinished(self):
        """
        Private slot connected to the finished signal of the test server.
        """
        if (
            self.__testServerProc is not None
            and self.__testServerProc.state() != QProcess.ProcessState.NotRunning
        ):
            self.__testServerProc.terminate()
            QTimer.singleShot(2000, self.__testServerProc.kill)
            self.__testServerProc.waitForFinished(3000)
        self.__testServerProc = None

    ##################################################################
    ## slots below implement authorization functions
    ##################################################################

    def __changePassword(self):
        """
        Private slot to change the password of a user.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommandNoClose")
        )[1]
        if consoleCmd:
            userName, ok = QInputDialog.getText(
                self.__ui,
                self.tr("Change Password"),
                self.tr("Enter the name of the user:"),
                QLineEdit.EchoMode.Normal,
            )
            if ok and userName != "":
                args = Utilities.parseOptionString(consoleCmd)
                args[0] = getExecutablePath(args[0])
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("changepassword")
                args.append(userName)
                with contextlib.suppress(DjangoNoSiteSelectedError):
                    wd = self.__sitePath()
                    self.__adjustWorkingDirectory(args, wd)
                    started, pid = QProcess.startDetached(args[0], args[1:], wd)
                    if not started:
                        EricMessageBox.critical(
                            None,
                            self.tr("Process Generation Error"),
                            self.tr("The Django process could not be" " started."),
                        )

    def __createSuperUser(self):
        """
        Private slot to create a super user account.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommandNoClose")
        )[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("createsuperuser")
            with contextlib.suppress(DjangoNoSiteSelectedError):
                wd = self.__sitePath()
                self.__adjustWorkingDirectory(args, wd)
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    EricMessageBox.critical(
                        None,
                        self.tr("Process Generation Error"),
                        self.tr("The Django process could not be started."),
                    )

    ##################################################################
    ## slots below implement session functions
    ##################################################################

    def __clearSessions(self):
        """
        Private slot to clear expired sessions.
        """
        title = self.tr("Clear Sessions")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("clearsessions")

        dia = DjangoDialog(
            title, msgSuccess=self.tr("Expired sessions cleared successfully.")
        )
        res = dia.startProcess(args, wd)
        if res:
            dia.exec()

    ##################################################################
    ## slots below implement translation functions
    ##################################################################

    def __getLocale(self, filename):
        """
        Private method to extract the locale out of a file name.

        @param filename name of the file used for extraction
        @type str
        @return extracted locale or None
        @rtype str
        """
        if self.__ericProject.getTranslationPattern():
            pattern = self.__ericProject.getTranslationPattern().replace(
                "%language%", "(.*?)"
            )
            match = re.search(pattern, filename)
            if match is not None:
                loc = match.group(1)
                return loc
            else:
                loc = None
        else:
            loc = None

        return loc

    def __normalizeList(self, filenames):
        """
        Private method to normalize a list of file names.

        @param filenames list of file names to normalize
        @type list of str
        @return normalized file names
        @rtype list of str
        """
        nfilenames = []
        for filename in filenames:
            if filename.endswith(".mo"):
                filename = filename.replace(".mo", ".po")
            if filename not in nfilenames:
                nfilenames.append(filename)

        return nfilenames

    def __siteFilteredList(self, filenames):
        """
        Private method to filter a list of file names by site.

        @param filenames list of file names to be filtered
        @type list of str
        @return file names belonging to the current site
        @rtype list of str
        """
        site = self.__site()
        nfilenames = []
        for filename in filenames:
            if site == "" or filename.startswith(site + os.sep):
                nfilenames.append(filename)

        return nfilenames

    def __projectLanguageAdded(self, code):
        """
        Private slot handling the addition of a new language.

        @param code language code of the new language
        @type str
        """
        title = self.tr("Initializing message catalog for '{0}'").format(code)

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("--locale={0}".format(code))

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalog initialized successfully.")
        )
        res = dia.startProcess(args, wd)
        if res:
            dia.exec()

            langFile = self.__ericProject.getTranslationPattern().replace(
                "%language%", code
            )
            self.__ericProject.appendFile(langFile)

    def updateSelectedCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of file names
        @type list of str
        """
        title = self.tr("Updating message catalogs")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        argsLists = []

        for filename in self.__normalizeList(self.__siteFilteredList(filenames)):
            locale = self.__getLocale(filename)
            if locale:
                args = []
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("makemessages")
                args.append("--no-obsolete")
                args.append("--locale={0}".format(locale))
                argsLists.append(args)

        if len(argsLists) == 0:
            EricMessageBox.warning(
                None, title, self.tr("No locales detected. Aborting...")
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startBatchProcesses(argsLists, wd)
        if res:
            dia.exec()

    def updateSelectedCatalogsWithObsolete(self, filenames):
        """
        Public method to update the message catalogs keeping obsolete messages.

        @param filenames list of filenames
        @type list of str
        """
        title = self.tr("Updating message catalogs (keeping obsolete" " messages)")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        argsLists = []

        for filename in self.__normalizeList(self.__siteFilteredList(filenames)):
            locale = self.__getLocale(filename)
            if locale:
                args = []
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("makemessages")
                args.append("--locale={0}".format(locale))
                argsLists.append(args)

        if len(argsLists) == 0:
            EricMessageBox.warning(
                None, title, self.tr("No locales detected. Aborting...")
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startBatchProcesses(argsLists, wd)
        if res:
            dia.exec()

    def updateCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of filenames (not used)
        @type list of str
        """
        title = self.tr("Updating message catalogs")

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("--all")
        args.append("--no-obsolete")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startProcess(args, wd)
        if res:
            dia.exec()

    def updateCatalogsWithObsolete(self, filenames):
        """
        Public method to update the message catalogs keeping obsolete messages.

        @param filenames list of filenames (not used)
        @type list of str
        """
        title = self.tr("Updating message catalogs (keeping obsolete" " messages)")

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("--all")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs updated successfully.")
        )
        res = dia.startProcess(args, wd)
        if res:
            dia.exec()

    def compileSelectedCatalogs(self, filenames):
        """
        Public method to update the message catalogs.

        @param filenames list of filenames
        @type list of str
        """
        title = self.tr("Compiling message catalogs")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        argsLists = []

        for filename in self.__normalizeList(self.__siteFilteredList(filenames)):
            locale = self.__getLocale(filename)
            if locale:
                args = []
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("compilemessages")
                args.append("--locale={0}".format(locale))
                if self.__plugin.getPreferences("FuzzyTranslations"):
                    args.append("--use-fuzzy")
                argsLists.append(args)

        if len(argsLists) == 0:
            EricMessageBox.warning(
                None, title, self.tr("No locales detected. Aborting...")
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs compiled successfully.")
        )
        res = dia.startBatchProcesses(argsLists, wd, mergedOutput=True)
        if res:
            dia.exec()

            for entry in os.walk(self.__sitePath()):
                for fileName in entry[2]:
                    fullName = os.path.join(entry[0], fileName)
                    if fullName.endswith(".mo"):
                        self.__ericProject.appendFile(fullName)

    def compileCatalogs(self, filenames):
        """
        Public method to compile the message catalogs.

        @param filenames list of filenames (not used)
        @type list of str
        """
        title = self.tr("Compiling message catalogs")

        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("compilemessages")
        if self.__plugin.getPreferences("FuzzyTranslations"):
            args.append("--use-fuzzy")

        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedError:
            EricMessageBox.warning(
                None,
                title,
                self.tr(
                    "No current site selected or no site created yet." " Aborting..."
                ),
            )
            return

        dia = DjangoDialog(
            title, msgSuccess=self.tr("\nMessage catalogs compiled successfully.")
        )
        res = dia.startProcess(args, wd, mergedOutput=True)
        if res:
            dia.exec()

            for entry in os.walk(self.__sitePath()):
                for fileName in entry[2]:
                    fullName = os.path.join(entry[0], fileName)
                    if fullName.endswith(".mo"):
                        self.__ericProject.appendFile(fullName)

    def openPOEditor(self, poFile):
        """
        Public method to edit the given file in an external .po editor.

        @param poFile name of the .po file
        @type str
        """
        editor = self.__plugin.getPreferences("TranslationsEditor")
        if poFile.endswith(".po") and editor:
            try:
                wd = self.__sitePath()
            except DjangoNoSiteSelectedError:
                wd = ""
            started, pid = QProcess.startDetached(editor, [poFile], wd)
            if not started:
                EricMessageBox.critical(
                    None,
                    self.tr("Process Generation Error"),
                    self.tr(
                        "The translations editor process ({0}) could" " not be started."
                    ).format(os.path.basename(editor)),
                )

    ##################################################################
    ## slots below implement check functions
    ##################################################################

    def __performCheck(self):
        """
        Private slot to inspect the project for common problems.
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedError:
            return

        from .DjangoCheckOptionsDialog import DjangoCheckOptionsDialog

        dlg = DjangoCheckOptionsDialog(
            self.__getPythonExecutable(),
            path,
            self.getRecentApplications(),
            self.__plugin.getPreferences("CheckDeployMode"),
        )
        if dlg.exec() == QDialog.DialogCode.Accepted:
            deploy, tags, appsStr, settingsFile = dlg.getData()
            self.__plugin.setPreferences("CheckDeployMode", deploy)
            if appsStr != "":
                self.setMostRecentApplication(appsStr)
            apps = appsStr.split()

            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("check")
            for tag in tags:
                args.append("--tag")
                args.append(tag)
            if deploy:
                args.append("--deploy")
                if settingsFile:
                    args.append("--settings={0}".format(settingsFile))
            args += apps

            dia = DjangoDialog(self.tr("Check Project"))
            res = dia.startProcess(args, path, mergedOutput=True)
            if res:
                dia.exec()

eric ide

mercurial