ProjectDjango/Project.py

Sat, 23 Mar 2013 19:41:38 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 23 Mar 2013 19:41:38 +0100
changeset 2
1e97424fda0c
parent 1
13a0cced0c6e
child 3
fbae2b012fac
permissions
-rw-r--r--

Continued porting the Django project plug-in from eric4.

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

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

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

import sys
import os
import re

from PyQt4.QtCore import QObject, QProcess, QTimer, QUrl, QFileInfo
from PyQt4.QtGui import QMenu, QInputDialog, QLineEdit, QDesktopServices, QDialog, \
    QFileDialog

from E5Gui.E5Application import e5App
from E5Gui import E5MessageBox, E5FileDialog
from E5Gui.E5Action import E5Action

from .DjangoDialog import DjangoDialog

import Preferences
import Utilities


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


class Project(QObject):
    """
    Class implementing the Django project support.
    """
    RecentApplicationsKey = "Django/RecentApplications"
    
    def __init__(self, plugin, parent=None):
        """
        Constructor
        
        @param plugin reference to the plugin object
        @param parent parent (QObject)
        """
        super().__init__(parent)
        
        self.__plugin = plugin
        self.__ui = parent
        self.__e5project = e5App().getObject("Project")
        self.__hooksInstalled = False
        
        self.__mainMenu = None
        
        self.__serverProc = None
        self.__testServerProc = None
        
        self.__recentApplications = []
        self.__loadRecentApplications()
    
    def initActions(self):
        """
        Public method to define the Pyramid actions.
        """
        # TODO: add support for manage.py changepassword
        # TODO: add support for manage.py createsuperuser
        # TODO: add support for manage.py clearsession
        self.actions = []
    
        self.selectSiteAct = E5Action(self.trUtf8('Current Project'),
                "",
                0, 0,
                self,'django_current_project')
        self.selectSiteAct.setStatusTip(self.trUtf8(
            'Selects the current project'))
        self.selectSiteAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Start Project'),
                self.trUtf8('Start &Project'), 
                0, 0,
                self,'django_start_project')
        self.startProjectAct.setStatusTip(self.trUtf8(
            'Starts a new Django project'))
        self.startProjectAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(
                self.trUtf8('Start Application (global)'),
                self.trUtf8('Start Application (&global)'), 
                0, 0,
                self,'django_start_global_application')
        self.startGlobalApplicationAct.setStatusTip(self.trUtf8(
            'Starts a new global Django application'))
        self.startGlobalApplicationAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(
                self.trUtf8('Start Application (local)'),
                self.trUtf8('Start Application (&local)'), 
                0, 0,
                self,'django_start_local_application')
        self.startLocalApplicationAct.setStatusTip(self.trUtf8(
            'Starts a new local Django application'))
        self.startLocalApplicationAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Run Server'),
                self.trUtf8('Run &Server'), 
                0, 0,
                self,'django_run_server')
        self.runServerAct.setStatusTip(self.trUtf8(
            'Starts the Django Web server'))
        self.runServerAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Run Web-Browser'),
                self.trUtf8('Run &Web-Browser'), 
                0, 0,
                self,'django_run_browser')
        self.runBrowserAct.setStatusTip(self.trUtf8(
            'Starts the default Web-Browser with the URL of the Django Web server'))
        self.runBrowserAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Create Cache Tables'),
                self.trUtf8('C&reate Cache Tables'), 
                0, 0,
                self,'django_create_cache_tables')
        self.createCacheTableAct.setStatusTip(self.trUtf8(
            'Creates the tables needed to use the SQL cache backend'))
        self.createCacheTableAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Help'),
                self.trUtf8('&Help'), 
                0, 0,
                self,'django_help')
        self.helpAct.setStatusTip(self.trUtf8(
            'Shows the Django help index'))
        self.helpAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('About Django'),
                self.trUtf8('About D&jango'), 
                0, 0,
                self,'django_about')
        self.aboutDjangoAct.setStatusTip(self.trUtf8(
            'Shows some information about Django'))
        self.aboutDjangoAct.setWhatsThis(self.trUtf8(
            """<b>About Django</b>"""
            """<p>Shows some information about Django.</p>"""
        ))
        self.aboutDjangoAct.triggered[()].connect(self.__djangoInfo)
        self.actions.append(self.aboutDjangoAct)
        
        self.__initDatabaseActions()
        self.__initDatabaseSqlActions()
        self.__initToolsActions()
        self.__initTestingActions()

    def __initDatabaseActions(self):
        """
        Private method to define the database related actions.
        """
        self.syncDatabaseAct = E5Action(self.trUtf8('Synchronize'),
                self.trUtf8('&Synchronize'), 
                0, 0,
                self,'django_database_syncdb')
        self.syncDatabaseAct.setStatusTip(self.trUtf8(
            'Synchronizes the database'))
        self.syncDatabaseAct.setWhatsThis(self.trUtf8(
            """<b>Synchronize</b>"""
            """<p>Synchronizes the database.</p>"""
        ))
        self.syncDatabaseAct.triggered[()].connect(self.__databaseSynchronize)
        self.actions.append(self.syncDatabaseAct)
        
        self.inspectDatabaseAct = E5Action(self.trUtf8('Introspect'),
                self.trUtf8('&Introspect'), 
                0, 0,
                self,'django_database_inspect')
        self.inspectDatabaseAct.setStatusTip(self.trUtf8(
            'Introspects the database tables and outputs a Django model module'))
        self.inspectDatabaseAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Flush'),
                self.trUtf8('&Flush'), 
                0, 0,
                self,'django_database_flush')
        self.flushDatabaseAct.setStatusTip(self.trUtf8(
            'Returns all database tables to the state just after their installation'))
        self.flushDatabaseAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Start Client Console'),
                self.trUtf8('Start &Client Console'), 
                0, 0,
                self,'django_database_client')
        self.databaseClientAct.setStatusTip(self.trUtf8(
            'Starts a console window for the database client'))
        self.databaseClientAct.setWhatsThis(self.trUtf8(
            """<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.databaseSqlCreateTablesAct = E5Action(self.trUtf8('Create Tables'),
                self.trUtf8('Create &Tables'), 
                0, 0,
                self,'django_database_sql_create_tables')
        self.databaseSqlCreateTablesAct.setStatusTip(self.trUtf8(
            'Prints the CREATE TABLE SQL statements for one or more applications'))
        self.databaseSqlCreateTablesAct.setWhatsThis(self.trUtf8(
            """<b>Create Tables</b>"""
            """<p>Prints the CREATE TABLE SQL statements for one or """
            """more applications.</p>"""
        ))
        self.databaseSqlCreateTablesAct.triggered[()].connect(
            self.__databaseSqlCreateTables)
        self.actions.append(self.databaseSqlCreateTablesAct)
        
        self.databaseSqlCreateIndexesAct = E5Action(self.trUtf8('Create Indexes'),
                self.trUtf8('Create &Indexes'), 
                0, 0,
                self,'django_database_sql_create_indexes')
        self.databaseSqlCreateIndexesAct.setStatusTip(self.trUtf8(
            'Prints the CREATE INDEX SQL statements for one or more applications'))
        self.databaseSqlCreateIndexesAct.setWhatsThis(self.trUtf8(
            """<b>Create Indexes</b>"""
            """<p>Prints the CREATE INDEX SQL statements for one or """
            """more applications.</p>"""
        ))
        self.databaseSqlCreateIndexesAct.triggered[()].connect(
            self.__databaseSqlCreateIndexes)
        self.actions.append(self.databaseSqlCreateIndexesAct)
        
        self.databaseSqlCreateEverythingAct = E5Action(self.trUtf8('Create Everything'),
                self.trUtf8('Create &Everything'), 
                0, 0,
                self,'django_database_sql_create_everything')
        self.databaseSqlCreateEverythingAct.setStatusTip(self.trUtf8(
            'Prints the CREATE ... SQL statements for one or more applications'))
        self.databaseSqlCreateEverythingAct.setWhatsThis(self.trUtf8(
            """<b>Create Everything</b>"""
            """<p>Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL """
            """statements for one or more applications.</p>"""
        ))
        self.databaseSqlCreateEverythingAct.triggered[()].connect(
            self.__databaseSqlCreateEverything)
        self.actions.append(self.databaseSqlCreateEverythingAct)
        
        self.databaseSqlCustomAct = E5Action(self.trUtf8('Custom Statements'),
                self.trUtf8('&Custom Statements'), 
                0, 0,
                self,'django_database_sql_custom')
        self.databaseSqlCustomAct.setStatusTip(self.trUtf8(
            'Prints the custom table modifying SQL statements for '
            'one or more applications'))
        self.databaseSqlCustomAct.setWhatsThis(self.trUtf8(
            """<b>Custom Statements</b>"""
            """<p>Prints the custom table modifying SQL statements """
            """for one or more applications.</p>"""
        ))
        self.databaseSqlCustomAct.triggered[()].connect(
            self.__databaseSqlCustom)
        self.actions.append(self.databaseSqlCustomAct)
        
        self.databaseSqlDropTablesAct = E5Action(self.trUtf8('Drop Tables'),
                self.trUtf8('&Drop Tables'), 
                0, 0,
                self,'django_database_sql_drop_tables')
        self.databaseSqlDropTablesAct.setStatusTip(self.trUtf8(
            'Prints the DROP TABLE SQL statements for '
            'one or more applications'))
        self.databaseSqlDropTablesAct.setWhatsThis(self.trUtf8(
            """<b>Drop Tables</b>"""
            """<p>Prints the DROP TABLE SQL statements """
            """for one or more applications.</p>"""
        ))
        self.databaseSqlDropTablesAct.triggered[()].connect(
            self.__databaseSqlDropTables)
        self.actions.append(self.databaseSqlDropTablesAct)
        
        self.databaseSqlFlushAct = E5Action(self.trUtf8('Flush Database'),
                self.trUtf8('&Flush Database'), 
                0, 0,
                self,'django_database_sql_flush_database')
        self.databaseSqlFlushAct.setStatusTip(self.trUtf8(
            'Prints a list of statements to return all database tables to the state '
            'just after their installation'))
        self.databaseSqlFlushAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Reset Sequences'),
                self.trUtf8('Reset &Sequences'), 
                0, 0,
                self,'django_database_sql_reset_sequences')
        self.databaseSqlResetSeqAct.setStatusTip(self.trUtf8(
            'Prints the SQL statements for resetting sequences for '
            'one or more applications'))
        self.databaseSqlResetSeqAct.setWhatsThis(self.trUtf8(
            """<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)
    
    def __initToolsActions(self):
        """
        Private method to define the tool actions.
        """
        self.diffSettingsAct = E5Action(self.trUtf8('Diff Settings'),
                self.trUtf8('&Diff Settings'), 
                0, 0,
                self,'django_tools_diffsettings')
        self.diffSettingsAct.setStatusTip(self.trUtf8(
            'Shows the modification made to the settings'))
        self.diffSettingsAct.setWhatsThis(self.trUtf8(
            """<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.cleanupAct = E5Action(self.trUtf8('Cleanup'),
                self.trUtf8('&Cleanup'), 
                0, 0,
                self,'django_tools_cleanup')
        self.cleanupAct.setStatusTip(self.trUtf8(
            'Cleans out old data from the database'))
        self.cleanupAct.setWhatsThis(self.trUtf8(
            """<b>Cleanup</b>"""
            """<p>Cleans out old data from the database.</p>"""
        ))
        self.cleanupAct.triggered[()].connect(self.__cleanup)
        self.actions.append(self.cleanupAct)
    
        self.validateAct = E5Action(self.trUtf8('Validate'),
                self.trUtf8('&Validate'), 
                0, 0,
                self,'django_tools_validate')
        self.validateAct.setStatusTip(self.trUtf8(
            'Validates all installed models'))
        self.validateAct.setWhatsThis(self.trUtf8(
            """<b>Validate</b>"""
            """<p>Validates all installed models.</p>"""
        ))
        self.validateAct.triggered[()].connect(self.__validate)
        self.actions.append(self.validateAct)
    
        self.runPythonShellAct = E5Action(self.trUtf8('Start Python Console'),
                self.trUtf8('Start &Python Console'), 
                0, 0,
                self,'django_tools_pythonconsole')
        self.runPythonShellAct.setStatusTip(self.trUtf8(
            'Starts a Python interactive interpreter'))
        self.runPythonShellAct.setWhatsThis(self.trUtf8(
            """<b>Start Python Console</b>"""
            """<p>Starts a Python interactive interpreter.</p>"""
        ))
        self.runPythonShellAct.triggered[()].connect(self.__runPythonShell)
        self.actions.append(self.runPythonShellAct)
    
    def __initTestingActions(self):
        """
        Private method to define the testing actions.
        """
        self.dumpDataAct = E5Action(self.trUtf8('Dump Data'),
                self.trUtf8('&Dump Data'), 
                0, 0,
                self,'django_tools_dumpdata')
        self.dumpDataAct.setStatusTip(self.trUtf8(
            'Dump the database data to a fixture'))
        self.dumpDataAct.setWhatsThis(self.trUtf8(
            """<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 = E5Action(self.trUtf8('Load Data'),
                self.trUtf8('&Load Data'), 
                0, 0,
                self,'django_tools_loaddata')
        self.loadDataAct.setStatusTip(self.trUtf8(
            'Load data from fixture files'))
        self.loadDataAct.setWhatsThis(self.trUtf8(
            """<b>Load Data</b>"""
            """<p>Load data from fixture files.</p>"""
        ))
        self.loadDataAct.triggered[()].connect(self.__loadData)
        self.actions.append(self.loadDataAct)
        
        self.runTestAct = E5Action(self.trUtf8('Run Testsuite'),
                self.trUtf8('Run &Testsuite'), 
                0, 0,
                self,'django_tools_run_test')
        self.runTestAct.setStatusTip(self.trUtf8(
            'Run the test suite for applications or the whole site'))
        self.runTestAct.setWhatsThis(self.trUtf8(
            """<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.runTestServerAct = E5Action(self.trUtf8('Run Testserver'),
                self.trUtf8('Run Test&server'), 
                0, 0,
                self,'django_tools_run_test_server')
        self.runTestServerAct.setStatusTip(self.trUtf8(
            'Run a development server with data from a set of fixtures'))
        self.runTestServerAct.setWhatsThis(self.trUtf8(
            """<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 initMenu(self):
        """
        Public slot to initialize the Django menu.
        
        @return the menu generated (QMenu)
        """
        menu = QMenu(self.trUtf8('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.addMenu(self.__initDatabaseMenu())
        menu.addSeparator()
        menu.addMenu(self.__initToolsMenu())
        menu.addSeparator()
        menu.addAction(self.createCacheTableAct)
        menu.addSeparator()
        menu.addMenu(self.__initTestingMenu())
        menu.addSeparator()
        menu.addAction(self.aboutDjangoAct)
        menu.addSeparator()
        menu.addAction(self.helpAct)
        
        self.__mainMenu = menu
        
        return menu

    def __initDatabaseMenu(self):
        """
        Private slot to initialize the database menu.
        
        @return the menu generated (QMenu)
        """
        menu = QMenu(self.trUtf8("&Database"), self.__ui)
        menu.setTearOffEnabled(True)
        
        menu.addAction(self.syncDatabaseAct)
        menu.addSeparator()
        menu.addAction(self.inspectDatabaseAct)
        menu.addSeparator()
        menu.addAction(self.flushDatabaseAct)
        menu.addSeparator()
        menu.addAction(self.databaseClientAct)
        menu.addSeparator()
        menu.addMenu(self.__initDatabaseSqlMenu())
        
        return menu
    
    def __initDatabaseSqlMenu(self):
        """
        Private slot to initialize the database SQL submenu.
        
        @return the menu generated (QMenu)
        """
        menu = QMenu(self.trUtf8("Show &SQL"), self.__ui)
        menu.setTearOffEnabled(True)
        
        menu.addAction(self.databaseSqlCreateTablesAct)
        menu.addAction(self.databaseSqlCreateIndexesAct)
        menu.addAction(self.databaseSqlCreateEverythingAct)
        menu.addSeparator()
        menu.addAction(self.databaseSqlCustomAct)
        menu.addSeparator()
        menu.addAction(self.databaseSqlDropTablesAct)
        menu.addSeparator()
        menu.addAction(self.databaseSqlFlushAct)
        menu.addAction(self.databaseSqlResetSeqAct)
        
        return menu
    
    def __initToolsMenu(self):
        """
        Private slot to initialize the tools menu.
        
        @return the menu generated (QMenu)
        """
        menu = QMenu(self.trUtf8("&Tools"), self.__ui)
        menu.setTearOffEnabled(True)
        
        menu.addAction(self.diffSettingsAct)
        menu.addAction(self.cleanupAct)
        menu.addAction(self.validateAct)
        menu.addSeparator()
        menu.addAction(self.runPythonShellAct)
        
        return menu
    
    def __initTestingMenu(self):
        """
        Private slot to initialize the testing menu.
        
        @return the menu generated (QMenu)
        """
        menu = QMenu(self.trUtf8("T&esting"), self.__ui)
        menu.setTearOffEnabled(True)
        
        menu.addAction(self.dumpDataAct)
        menu.addAction(self.loadDataAct)
        menu.addSeparator()
        menu.addAction(self.runTestAct)
        menu.addAction(self.runTestServerAct)
        
        return menu

    ##################################################################
    ## methods below implement the various hook related functions
    ##################################################################
    
    def projectOpenedHooks(self):
        """
        Public method to add our hook methods.
        """
        if self.__e5project.getProjectType() == "Django":
            self.__formsBrowser = \
                e5App().getObject("ProjectBrowser").getProjectBrowser("forms")
            self.__formsBrowser.addHookMethodAndMenuEntry("newForm", 
                self.newForm, self.trUtf8("New template..."))
            
            self.__e5project.projectLanguageAddedByCode.connect(
                self.__projectLanguageAdded)
            self.__translationsBrowser = \
                e5App().getObject("ProjectBrowser").getProjectBrowser("translations")
            self.__translationsBrowser.addHookMethodAndMenuEntry("generateAll", 
                self.updateCatalogs, self.trUtf8("Update all catalogs"))
            self.__translationsBrowser.addHookMethodAndMenuEntry("generateSelected", 
                self.updateSelectedCatalogs, self.trUtf8("Update selected catalogs"))
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateAllWithObsolete", self.updateCatalogsWithObsolete,
                self.trUtf8("Update all catalogs (with obsolete)"))
            self.__translationsBrowser.addHookMethodAndMenuEntry(
                "generateSelectedWithObsolete", self.updateSelectedCatalogsWithObsolete,
                self.trUtf8("Update selected catalogs (with obsolete)"))
            self.__translationsBrowser.addHookMethodAndMenuEntry("releaseAll", 
                self.compileCatalogs, self.trUtf8("Compile all catalogs"))
            self.__translationsBrowser.addHookMethodAndMenuEntry("releaseSelected", 
                self.compileSelectedCatalogs, 
                self.trUtf8("Compile selected catalogs"))
            
            self.__hooksInstalled = True
    
    def projectClosedHooks(self):
        """
        Public method to remove our hook methods.
        """
        if self.__hooksInstalled:
            self.__formsBrowser.removeHookMethod("newForm")
            self.__formsBrowser = None
            
            self.__e5project.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 = 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 (string)
        """
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self.__ui,
            self.trUtf8("New Form"),
            path,
            filter,
            None,
            QFileDialog.Options(QFileDialog.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 = E5MessageBox.yesNo(self.__ui,
                self.trUtf8("New Form"),
                self.trUtf8("The file already exists! Overwrite it?"),
                icon=E5MessageBox.Warning)
            
            if not res:
                # user selected to not overwrite
                return
        
        try:
            f = open(fname, "w")
            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')
            f.close()
        except (IOError, OSError) as e:
            E5MessageBox.critical(self.__ui,
                self.trUtf8("New Form"),
                self.trUtf8("<p>The new form file <b>{0}</b> could not be created.<br>"
                    "Problem: {1}</p>").format(fname, str(e)))
            return
        
        self.__e5project.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 __djangoInfo(self):
        """
        Private slot to show some info about Django.
        """
        from django import VERSION
        version = '.'.join([str(i) for i in VERSION[:-1]])
        if VERSION[-1]:
            version += '-' + VERSION[-1]
        
        E5MessageBox.about(self.__ui,
            self.trUtf8("About Django"),
            self.trUtf8(
                "<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=\"http://www.djangoproject.com\">"
                "http://www.djangoproject.com</a></td></tr>"
                "</table></p>"
            ).format(version)
        )
    
    def getDjangoVersion(self):
        """
        Public method to get the Django version.
        
        @return Django version as a tuple
        """
        from django import VERSION
        return VERSION
    
    def __getApplications(self):
        """
        Private method to ask the user for a list of application names.
        
        @return list of application names (list of strings)
        """
        applStr, ok = QInputDialog.getItem(
            self.__ui,
            self.trUtf8("Select Applications"),
            self.trUtf8("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 (string)
        """
        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.recent[:maxRecentApps]
        self.__saveRecentApplications()
    
    def getProjectPath(self):
        """
        Public method to get the path of the eric5 project.
        
        @return path of the eric5 project (string)
        """
        return self.__e5project.getProjectPath()
    
    def __getPythonExecutable(self):
        """
        Private method to determine the name of the Python executable.
        
        @return Python executable (string)
        """
        # TODO: make this differentiate between Python2 and Python3
        return sys.executable.replace("pythonw", "python")
    
    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 (string)
        @return tuple of two entries giving an indication, if the console
            is spawning (boolean) and the (possibly) cleaned console command
            (string)
        """
        if consoleCmd and consoleCmd[0] == '@':
            return (True, consoleCmd[1:])
        else:
            return (False, consoleCmd)
    
    ##################################################################
    ## slots below implement creation functions
    ##################################################################
    
    def startProjectOrApplication(self):
        """
        Public slot to start a new Django project or application.
        """
        if self.__e5project.getProjectType() == "Django":
            projectStr = self.trUtf8("Project")
            applStr = self.trUtf8("Application")
            selections = ["", projectStr, applStr]
            selection,  ok = QInputDialog.getItem(
                self.__ui,
                self.trUtf8("Start Django"),
                self.trUtf8("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.__e5project.getProjectPath())
                    self.__createProject(projectName, path)
                elif selection == applStr:
                    path, applName = os.path.split(self.__e5project.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 (string)
        @param path the directory where the project should be created
            (string)
        @return flag indicating a successful creation (boolean)
        """
        title = self.trUtf8("Start Django Project")
        
        args = []
        if Utilities.isWindowsPlatform():
            args.append(self.__getPythonExecutable())
            args.append(os.path.join(sys.exec_prefix, "Scripts", "django-admin.py"))
        else:
            if Utilities.isinpath("django-admin.py"):
                args.append("django-admin.py")
            elif Utilities.isinpath("django-admin"):
                args.append("django-admin")
            else:
                # fall back
                args.append("django-admin.py")
        args.append("startproject")
        args.append(projectName)
        
        dia = DjangoDialog(title, 
            msgSuccess = self.trUtf8("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.__e5project.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.trUtf8("Start Django Project"),
            self.trUtf8("Enter the name of the new Django project."),
            QLineEdit.Normal)
        if ok and projectName != "":
            res = self.__createProject(projectName, self.__e5project.getProjectPath())
            if res:
                # search for new files and add them to the project
                sitePath = os.path.join(self.__e5project.getProjectPath(), 
                                        projectName)
                for entry in os.walk(sitePath):
                    for fileName in entry[2]:
                        fullName = os.path.join(entry[0], fileName)
                        self.__e5project.appendFile(fullName)
    
    def __createApplication(self, applName, path, isGlobal = True):
        """
        Private slot to create a new Django application.
        
        @param applName name of the new application (string)
        @param path the directory where the application should be created
            (string)
        @param isGlobal flag indicating a standalone Django application (boolean)
        @return flag indicating a successful creation (boolean)
        """
        title = self.trUtf8("Start Django Application")
        
        args = []
        if isGlobal:
            if Utilities.isWindowsPlatform():
                args.append(self.__getPythonExecutable())
                args.append(os.path.join(sys.exec_prefix, "Scripts", "django-admin.py"))
            else:
                if Utilities.isinpath("django-admin.py"):
                    args.append("django-admin.py")
                elif Utilities.isinpath("django-admin"):
                    args.append("django-admin")
                else:
                    # fall back
                    args.append("django-admin.py")
        else:
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            try:
                path = self.__sitePath()
            except DjangoNoSiteSelectedException:
                return False
        args.append("startapp")
        args.append(applName)
        
        dia = DjangoDialog(title, 
            msgSuccess = self.trUtf8("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.trUtf8("Start Global Django Application"),
            self.trUtf8("Enter the name of the new global Django application."),
            QLineEdit.Normal)
        if ok and applName != "":
            res = self.__createApplication(applName, self.__e5project.getProjectPath())
            if res:
                # search for new files and add them to the project
                appPath = os.path.join(self.__e5project.getProjectPath(), applName)
                for entry in os.walk(appPath):
                    for fileName in entry[2]:
                        fullName = os.path.join(entry[0], fileName)
                        self.__e5project.appendFile(fullName)
    
    def __startLocalApplication(self):
        """
        Private slot to start a new local Django application.
        """
        applName, ok = QInputDialog.getText(
            self.__ui,
            self.trUtf8("Start Local Django Application"),
            self.trUtf8("Enter the name of the new local Django application."),
            QLineEdit.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.__e5project.appendFile(fullName)
                except DjangoNoSiteSelectedException:
                    return
    
    ##################################################################
    ## methods below implement site related functions
    ##################################################################
    
    def __findSites(self):
        """
        Private method to determine the relative path to all manage.py scripts.
        
        @return list of sites (list of strings)
        """
        sites = []
        for file in sorted(self.__e5project.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.
        
        @return selected site (string)
        """
        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.trUtf8("Select Project"),
                self.trUtf8("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.
        
        @exception DjangoNoSiteSelectedException raised, if no site is selected
        @return path of the site (string)
        """
        if self.__currentSite is None:
            self.__selectSite()
        
        if self.__currentSite is None:
            raise DjangoNoSiteSelectedException
        else:
            return os.path.join(self.__e5project.getProjectPath(), 
                                self.__currentSite)
    
    def __setCurrentSite(self, site):
        """
        Private slot to set the current site.
        
        @param site name of the site (string)
        """
        self.__currentSite = site
        if self.__currentSite is None:
            curSite = self.trUtf8("None")
        elif self.__currentSite == "":
            curSite = self.trUtf8("Project")
        else:
            curSite = self.__currentSite
        self.selectSiteAct.setText(
            self.trUtf8('&Current Django project ({0})').format(curSite))
        
        if self.__currentSite is None:
            self.__e5project.pdata["TRANSLATIONPATTERN"] = []
        else:
            self.__e5project.pdata["TRANSLATIONPATTERN"] = [
                os.path.join(site, "locale", "%language%", "LC_MESSAGES", "django.po")
            ]
    
    def __site(self):
        """
        Private method to get the name of the current site.
        
        @exception DjangoNoSiteSelectedException raised, if no site is selected
        @return name of the site (string)
        """
        if self.__currentSite is None:
            self.__selectSite()
        
        if self.__currentSite is None:
            raise DjangoNoSiteSelectedException
        else:
            return self.__currentSite

    ##################################################################
    ## slots below implement run functions
    ##################################################################
    
    def __runServer(self):
        """
        Private slot to start the Django Web server.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand"))[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("runserver")
            addr = self.__plugin.getPreferences("ServerAddress")
            if addr:
                args.append(addr)
            
            try:
                if Utilities.isWindowsPlatform():
                    serverProcStarted, pid = \
                        QProcess.startDetached(args[0], args[1:], self.__sitePath())
                else:
                    if self.__serverProc is not None:
                        self.__serverProcFinished()
                    
                    self.__serverProc = QProcess()
                    self.__serverProc.finished.connect(self.__serverProcFinished)
                    self.__serverProc.setWorkingDirectory(self.__sitePath())
                    self.__serverProc.start(args[0], args[1:])
                    serverProcStarted = self.__serverProc.waitForStarted()
                if not serverProcStarted:
                    E5MessageBox.critical(None,
                        self.trUtf8('Process Generation Error'),
                        self.trUtf8('The Django server could not be started.'))
            except DjangoNoSiteSelectedException:
                pass
    
    def __serverProcFinished(self):
        """
        Private slot connected to the finished signal.
        """
        if self.__serverProc is not None and \
           self.__serverProc.state() != QProcess.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")
        if addr:
            if ':' in addr:
                port = addr.split(':')[1]
            else:
                port = addr
        else:
            port = "8000"
        url = QUrl("http://127.0.0.1:{0}".format(port))
        res = QDesktopServices.openUrl(url)
        if not res:
            E5MessageBox.critical(None,
                self.trUtf8('Run Web-Browser'),
                self.trUtf8('Could not start the web-browser for the url "{0}".')\
                    .format(url.toString()))

    ##################################################################
    ## slots below implement database related functions
    ##################################################################
    
    def __databaseSynchronize(self):
        """
        Private slot to synchronize the database.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommandNoClose"))[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("syncdb")
            try:
                wd = self.__sitePath()
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    E5MessageBox.critical(None,
                        self.trUtf8('Process Generation Error'),
                        self.trUtf8('The Django process could not be started.'))
            except DjangoNoSiteSelectedException:
                pass
    
    def __databaseInspect(self):
        """
        Private slot to introspect the database and output a Django model module.
        """
        title = self.trUtf8("Introspect Database")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("inspectdb")
        
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        dia = DjangoDialog(title, fixed = True, linewrap = False)
        res = dia.startProcess(args, path, False)
        if res:
            dia.exec_()
    
    def __databaseFlush(self):
        """
        Private slot to return all database tables to the state just after their 
        installation.
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        title = self.trUtf8("Flush Database")
        
        res = E5MessageBox.yesNo(self.__ui,
            title,
            self.trUtf8("""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")
            
            dia = DjangoDialog(title, 
                msgSuccess = self.trUtf8("Database tables flushed successfully."))
            res = dia.startProcess(args, path)
            if res:
                dia.exec_()
    
    def __runDatabaseClient(self):
        """
        Private slot to start a database client for a Django project.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand"))[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("dbshell")
            try:
                wd = self.__sitePath()
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    E5MessageBox.critical(None,
                        self.trUtf8('Process Generation Error'),
                        self.trUtf8('The Django process could not be started.'))
            except DjangoNoSiteSelectedException:
                pass
    
    #######################################################################
    ## 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 (string)
        @param command Django sql... command (string)
        @param requestApps flag indicating to request a list of applications
            to work on (boolean)
        """
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        if requestApps:
            apps = self.__getApplications()
            if not apps:
                return
        else:
            apps = []
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append(command)
        args += apps
        
        fileFilter = self.trUtf8("SQL Files (*.sql)")
        
        dia = DjangoDialog(title, fixed = True, linewrap = False, 
                           saveFilters = fileFilter)
        res = dia.startProcess(args, path, False)
        if res:
            dia.exec_()
    
    def __databaseSqlCreateTables(self):
        """
        Private slot to print the CREATE TABLE SQL statements for one 
        or more applications.
        """
        self.__sqlCommand(self.trUtf8("Create Tables"), "sql")
    
    def __databaseSqlCreateIndexes(self):
        """
        Private slot to print the CREATE INDEX SQL statements for one 
        or more applications.
        """
        self.__sqlCommand(self.trUtf8("Create Indexes"), "sqlindexes")
    
    def __databaseSqlCreateEverything(self):
        """
        Private slot to print the CREATE TABLE, custom SQL and 
        CREATE INDEX SQL statements for one or more applications.
        """
        self.__sqlCommand(self.trUtf8("Create Everything"), "sqlall")
    
    def __databaseSqlCustom(self):
        """
        Private slot to print the custom table modifying SQL statements
        for one or more applications.
        """
        self.__sqlCommand(self.trUtf8("Custom Statements"), "sqlcustom")
    
    def __databaseSqlDropTables(self):
        """
        Private slot to print the DROP TABLE SQL statements for one or
        more applications.
        """
        self.__sqlCommand(self.trUtf8("Drop Tables"), "sqlclear")
    
    def __databaseSqlFlushDatabase(self):
        """
        Private slot to print a list of statements to return all database
        tables to their initial state.
        """
        self.__sqlCommand(self.trUtf8("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.trUtf8("Reset Sequences"), "sqlsequencereset")
    
    ##################################################################
    ## slots below implement some tool functions
    ##################################################################
    
    def __diffSettings(self):
        """
        Private slot to show the changes made to the settings.py file.
        """
        title = self.trUtf8("Diff Settings")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("diffsettings")
        
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        dia = DjangoDialog(title, fixed = True, linewrap = False)
        res = dia.startProcess(args, path, False)
        if res:
            dia.exec_()
    
    def __cleanup(self):
        """
        Private slot to clean out old data from the database.
        """
        title = self.trUtf8("Cleanup")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("cleanup")
        
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = self.trUtf8("Database cleaned up successfully."))
        res = dia.startProcess(args, path)
        if res:
            dia.exec_()
    
    def __validate(self):
        """
        Private slot to validate all installed models.
        """
        title = self.trUtf8("Validate")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("validate")
        
        try:
            path = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        dia = DjangoDialog(title)
        res = dia.startProcess(args, path)
        if res:
            dia.exec_()
    
    def __runPythonShell(self):
        """
        Private slot to start a Python console for a Django project.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommand"))[1]
        if consoleCmd:
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("shell")
            if self.__plugin.getPreferences("UsePlainPython"):
                args.append("--plain")
            try:
                wd = self.__sitePath()
                started, pid = QProcess.startDetached(args[0], args[1:], wd)
                if not started:
                    E5MessageBox.critical(None,
                        self.trUtf8('Process Generation Error'),
                        self.trUtf8('The Django process could not be started.'))
            except DjangoNoSiteSelectedException:
                pass
    
    ##################################################################
    ## slots below implement caching functions
    ##################################################################
    
    def __createCacheTables(self):
        """
        Private slot to create the tables for the SQL caching backend.
        """
        title = self.trUtf8("Create Cache Tables")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        tblStr, ok = QInputDialog.getText(
            self.__ui,
            title,
            self.trUtf8("Enter the names of the cache tables separated by spaces."),
            QLineEdit.Normal)
        if ok and tblStr != "":
            tableNames = tblStr.split()
            
            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("createcachetable")
            args += tableNames
            
            dia = DjangoDialog(title, 
                msgSuccess = self.trUtf8("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.trUtf8("Dump Data")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        from .DjangoDumpdataDataDialog import DjangoDumpdataDataDialog
        dlg = DjangoDumpdataDataDialog(self, self.__ui)
        if dlg.exec_() == QDialog.Accepted:
            appls, excls, format, indent = dlg.getData()
            
            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("dumpdata")
            args.append("--format={0}".format(format))
            args.append("--indent={0}".format(indent))
            for excl in excls:
                args.append("--exclude={0}".format(excl))
            args += appls
            
            if format == "json":
                fileFilters = self.trUtf8("JSON Files (*.json)")
            elif format == "xml":
                fileFilters = self.trUtf8("XML Files (*.xml)")
            elif format == "yaml":
                fileFilters = self.trUtf8("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.trUtf8("Load Data")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            return
        
        from .DjangoLoaddataDataDialog import DjangoLoaddataDataDialog
        dlg = DjangoLoaddataDataDialog(self, self.__ui)
        if dlg.exec_() == QDialog.Accepted:
            fixtures = dlg.getData()
            
            args = []
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("loaddata")
            args += fixtures
            
            dia = DjangoDialog(title)
            res = dia.startProcess(args, wd)
            if res:
                dia.exec_()
    
    def __runTestSuite(self):
        """
        Private slot to run the test suite for applications or the whole site.
        """
        consoleCmd = self.__isSpawningConsole(
            self.__plugin.getPreferences("ConsoleCommandNoClose"))[1]
        if consoleCmd:
            try:
                wd = self.__sitePath()
            except DjangoNoSiteSelectedException:
                return
            
            args = Utilities.parseOptionString(consoleCmd)
            args[0] = Utilities.getExecutablePath(args[0])
            args.append(self.__getPythonExecutable())
            args.append("manage.py")
            args.append("test")
            args += self.__getApplications()
            
            started, pid = QProcess.startDetached(args[0], args[1:], wd)
            if not started:
                E5MessageBox.critical(None,
                    self.trUtf8('Process Generation Error'),
                    self.trUtf8('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 .DjangoLoaddataDataDialog import DjangoLoaddataDataDialog
            dlg = DjangoLoaddataDataDialog(self, self.__ui)
            if dlg.exec_() == QDialog.Accepted:
                fixtures = dlg.getData()
                
                args = Utilities.parseOptionString(consoleCmd)
                args[0] = Utilities.getExecutablePath(args[0])
                args.append(self.__getPythonExecutable())
                args.append("manage.py")
                args.append("testserver")
                addr = self.__plugin.getPreferences("ServerAddress")
                if addr:
                    args.append("--addrport=%s" % addr)
                args += fixtures
                
                try:
                    if Utilities.isWindowsPlatform():
                        serverProcStarted, pid = \
                            QProcess.startDetached(args[0], args[1:], self.__sitePath())
                    else:
                        if self.__testServerProc is not None:
                            self.__testServerProcFinished()
                        
                        self.__testServerProc = QProcess()
                        self.__testServerProc.finished.connect(self.__serverProcFinished)
                        self.__testServerProc.setWorkingDirectory(self.__sitePath())
                        self.__testServerProc.start(args[0], args[1:])
                        serverProcStarted = self.__testServerProc.waitForStarted()
                    if not serverProcStarted:
                        E5MessageBox.critical(None,
                            self.trUtf8('Process Generation Error'),
                            self.trUtf8('The Django test server could not be started.'))
                except DjangoNoSiteSelectedException:
                    pass
    
    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.NotRunning:
            self.__testServerProc.terminate()
            QTimer.singleShot(2000, self.__testServerProc.kill)
            self.__testServerProc.waitForFinished(3000)
        self.__testServerProc = None
    
    ##################################################################
    ## slots below implement translation functions
    ##################################################################
    
    # TODO: the below stuff needs testing
    def __getLocale(self, filename):
        """
        Private method to extract the locale out of a file name.
        
        @param filename name of the file used for extraction (string)
        @return extracted locale (string) or None
        """
        if self.__e5project.pdata["TRANSLATIONPATTERN"]:
            pattern = self.__e5project.pdata["TRANSLATIONPATTERN"][0]\
                .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 (list of strings)
        @return normalized file names (list of strings)
        """
        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 (list of strings)
        @return file names belonging to the current site (list of strings)
        """
        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 (string)
        """
        title = self.trUtf8("Initializing message catalog for '{0}'").format(code)
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("--domain=django")
        args.append("--domain=djangojs")
        args.append("-l")
        args.append(code)
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No current site selected or no site created yet.'
                            ' Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\nMessage catalog initialized successfully."))
        res = dia.startProcess(args, wd)
        if res:
            dia.exec_()
            
            langFile = self.__e5project.pdata["TRANSLATIONPATTERN"][0]\
                .replace("%language%", code)
            self.__e5project.appendFile(langFile)
    
    def updateSelectedCatalogs(self, filenames):
        """
        Public method to update the message catalogs.
        
        @param filenames list of file names (list of strings)
        """
        title = self.trUtf8("Updating message catalogs")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('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("--domain=django")
                args.append("--domain=djangojs")
                args.append("-l")
                args.append(locale)
                argsLists.append(args)
        
        if len(argsLists) == 0:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No locales detected. Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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
        """
        title = self.trUtf8("Updating message catalogs (keeping obsolete messages)")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('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("--domain=django")
                args.append("--domain=djangojs")
                args.append("-l")
                args.append(locale)
                argsLists.append(args)
        
        if len(argsLists) == 0:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No locales detected. Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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)
        """
        title = self.trUtf8("Updating message catalogs")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("-a")
        args.append("--no-obsolete")
        args.append("--domain=django")
        args.append("--domain=djangojs")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No current site selected or no site created yet.'
                            ' Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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)
        """
        title = self.trUtf8("Updating message catalogs (keeping obsolete messages)")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("makemessages")
        args.append("-a")
        args.append("--domain=django")
        args.append("--domain=djangojs")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No current site selected or no site created yet.'
                            ' Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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
        """
        title = self.trUtf8("Compiling message catalogs")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('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("-l")
                args.append(locale)
                argsLists.append(args)
        
        if len(argsLists) == 0:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No locales detected. Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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.__e5project.appendFile(fullName)
    
    def compileCatalogs(self, filenames):
        """
        Public method to compile the message catalogs.
        
        @param filenames list of filenames (not used)
        """
        title = self.trUtf8("Compiling message catalogs")
        
        args = []
        args.append(self.__getPythonExecutable())
        args.append("manage.py")
        args.append("compilemessages")
        
        try:
            wd = self.__sitePath()
        except DjangoNoSiteSelectedException:
            E5MessageBox.warning(None,
                title,
                self.trUtf8('No current site selected or no site created yet.'
                            ' Aborting...'))
            return
        
        dia = DjangoDialog(title, 
            msgSuccess = \
                self.trUtf8("\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.__e5project.appendFile(fullName)

eric ide

mercurial