ProjectDjango/DjangoMigrationsListDialog.py

Sat, 17 Dec 2016 19:07:02 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 17 Dec 2016 19:07:02 +0100
changeset 72
0f95786f7868
child 73
8a4ccc4f1359
permissions
-rw-r--r--

Did some adjustments to Djange >= 1.9.0, added support for the eric web browser and started adding support for the various migration commands.

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

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

"""
Module implementing a dialog show a list of all available migrations.
"""

from __future__ import unicode_literals
try:
    str = unicode    # __IGNORE_WARNING__
except NameError:
    pass

from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
    QHeaderView, QTreeWidgetItem

from E5Gui import E5MessageBox

from .Ui_DjangoMigrationsListDialog import Ui_DjangoMigrationsListDialog

import Preferences


class DjangoMigrationsListDialog(QDialog, Ui_DjangoMigrationsListDialog):
    """
    Class implementing a dialog show a list of all available migrations.
    """
    MigrationsListMode = "L"
    MigrationsPlanMode = "P"
    
    def __init__(self, mode, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        """
        super(DjangoMigrationsListDialog, self).__init__(parent)
        self.setupUi(self)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
        
        self.ioEncoding = Preferences.getSystem("IOEncoding")
        
        self.proc = None
        
        self.__mode = mode
        if self.__mode == DjangoMigrationsListDialog.MigrationsListMode:
            self.setWindowTitle(self.tr("Available Migrations"))
            self.migrationsList.setHeaderLabels([
                self.tr("Name"),
            ])
        else:
            self.setWindowTitle(self.tr("Migrations Plan"))
            self.migrationsList.setHeaderLabels([
                self.tr("Migration"),
                self.tr("Dependencies"),
            ])
    
    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked
        @type QAbstractButton
        """
        if button == self.buttonBox.button(QDialogButtonBox.Close):
            self.close()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.__finish()
    
    def __finish(self):
        """
        Private slot called when the process finished or the user pressed the
        button.
        """
        if self.proc is not None and \
           self.proc.state() != QProcess.NotRunning:
            self.proc.terminate()
            QTimer.singleShot(2000, self.proc.kill)
            self.proc.waitForFinished(3000)
        
        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        
        self.proc = None
        
        self.__resizeColumns()
    
    def __procFinished(self, exitCode, exitStatus):
        """
        Private slot connected to the finished signal.
        
        @param exitCode exit code of the process (integer)
        @param exitStatus exit status of the process (QProcess.ExitStatus)
        """
        self.__finish()
    
    def __resizeColumns(self):
        """
        Private method to resize the list columns.
        """
        self.migrationsList.header().resizeSections(
            QHeaderView.ResizeToContents)
        if self.__mode == DjangoMigrationsListDialog.MigrationsListMode:
            self.migrationsList.header().setStretchLastSection(True)
    
    def start(self, pythonExecutable, sitePath):
        """
        Public slot used to start the process.
        
        @param pythonExecutable Python executable to be used
        @type str
        @param sitePath path of the site
        @type str
        @return flag indicating a successful start of the process (boolean)
        """
        self.errorGroup.hide()
        
        self.proc = QProcess()
        self.proc.finished.connect(self.__procFinished)
        self.proc.readyReadStandardOutput.connect(self.__readStdout)
        self.proc.readyReadStandardError.connect(self.__readStderr)
        
        self.__lastTopItem = None
        
        if sitePath:
            self.proc.setWorkingDirectory(sitePath)
        
        args = []
        args.append("manage.py")
        args.append("showmigrations")
        if self.__mode == DjangoMigrationsListDialog.MigrationsListMode:
            args.append("--list")
        else:
            args.append("--plan")
            args.append("--verbosity")
            args.append("2")
        
        self.proc.start(pythonExecutable, args)
        procStarted = self.proc.waitForStarted()
        if not procStarted:
            self.buttonBox.setFocus()
            E5MessageBox.critical(
                self,
                self.tr('Process Generation Error'),
                self.tr(
                    'The process {0} could not be started. '
                    'Ensure, that it is in the search path.'
                ).format(pythonExecutable))
        return procStarted
    
    def __readStdout(self):
        """
        Private slot to handle the readyReadStdout signal.
        
        It reads the output of the process, formats it and inserts it into
        the contents pane.
        """
        while self.proc.canReadLine():
            s = str(self.proc.readLine(), self.ioEncoding, 'replace').rstrip()
            if self.__mode == DjangoMigrationsListDialog.MigrationsListMode:
                self.__createListItem(s)
            else:
                self.__createPlanItem(s)
    
    def __createListItem(self, line):
        """
        Private method to create an item for list mode.
        
        @param line line of text
        @type str
        """
        if not line.startswith(" "):
            # application name
            self.__lastTopItem = QTreeWidgetItem(
                self.migrationsList, [line.strip()])
            self.__lastTopItem.setExpanded(True)
        else:
            # migration name
            line = line.strip()
            applied = line[:3]
            name = line[3:].strip()
            if self.__lastTopItem:
                itm = QTreeWidgetItem(self.__lastTopItem, [name])
            else:
                itm = QTreeWidgetItem(self.migrationsList, [name])
            if applied[1] != " ":
                itm.setCheckState(0, Qt.Checked)
    
    def __createPlanItem(self, line):
        """
        Private method to create an item for plan mode.
        
        @param line line of text
        @type str
        """
        line = line.strip()
        applied = line[:3]
        parts = line[3:].strip().split(None, 2)
        if len(parts) == 3:
            dependencies = "\n".join([
                d.strip() for d in parts[2].strip()[1:-1].split(",")
            ])
            itm = QTreeWidgetItem(self.migrationsList, [
                parts[0].strip(),
                dependencies, 
            ])
        else:
            itm = QTreeWidgetItem(self.migrationsList, [
                parts[0].strip(),
                "",
            ])
        if applied[1] != " ":
            itm.setCheckState(0, Qt.Checked)
    
    def __readStderr(self):
        """
        Private slot to handle the readyReadStderr signal.
        
        It reads the error output of the process and inserts it into the
        error pane.
        """
        if self.proc is not None:
            self.errorGroup.show()
            s = str(self.proc.readAllStandardError(), self.ioEncoding,
                    'replace')
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()

eric ide

mercurial