Sat, 17 Dec 2016 19:07:02 +0100
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()