Ejemplo n.º 1
0
class EntityBrowser(QDialog, Ui_EntityBrowser, SupportsManageMixin):
    """
    Dialog browsing entities in a table view.
    """

    """
    Custom signal that is raised when the dialog is in SELECT state. It contains
    the record id of the selected row.
    """
    recordSelected = pyqtSignal(int)

    def __init__(self, parent=None, dataModel=None, state=MANAGE):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        SupportsManageMixin.__init__(self, state)

        # Refresh data source so that display mapping reference  is correct
        self._data_source_name = dataModel.__name__.lower()
        self._dbmodel = DeclareMapping.instance().tableMapping(self._data_source_name)
        self._state = state
        self._tableModel = None
        self._dataInitialized = False
        self._notifBar = NotificationBar(self.vlNotification)
        self._cellFormatters = {}

        # Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        self.tbEntity.doubleClicked[QModelIndex].connect(self.onDoubleClickView)

    def setDatabaseModel(self, databaseModel):
        """
        Set the database model that represents the entity for browsing its corresponding records.
        """
        self._dbmodel = databaseModel

    def dateFormatter(self):
        """
        Function for formatting date values
        """
        return self._dateFormatter

    def setDateFormatter(self, formatter):
        """
        Sets the function for formatting date values. Overrides the default function. 
        """
        self._dateFormatter = formatter

    def state(self):
        """
        Returns the current state that the dialog has been configured in.
        """
        return self._state

    def setState(self, state):
        """
        Set the state of the dialog.
        """
        self._state = state

    def title(self):
        """
        Set the title of the entity browser dialog.
        Protected method to be overriden by subclasses.
        """
        return ""

    def setCellFormatters(self, formattermapping):
        """
        Dictionary of attribute mappings and corresponding functions for 
        formatting the attribute value to the display value.
        """
        self._cellFormatters = formattermapping

    def addCellFormatter(self, attributeName, formatterFunc):
        """
        Add a new cell formatter configuration to the collection
        """
        self._cellFormatters[attributeName] = formatterFunc

    def showEvent(self, showEvent):
        """
        Override event for loading the database records once the dialog is visible.
        This is for improved user experience i.e. to prevent the dialog from taking
        long to load.
        """
        self.setWindowTitle(self.title())

        if self._dataInitialized:
            return
        try:
            if not self._dbmodel is None:
                self._initializeData()

        except Exception as ex:
            pass

        self._dataInitialized = True

    def hideEvent(self, hideEvent):
        """
        Override event which just sets a flag to indicate that the data records have already been
        initialized.
        """
        pass

    def recomputeRecordCount(self):
        """
        Get the number of records in the specified table and updates the window title.
        """
        entity = self._dbmodel()

        # Get number of records
        numRecords = entity.queryObject().count()

        rowStr = "row" if numRecords == 1 else "rows"
        windowTitle = "{0} - {1} {2}".format(
            unicode(self.title()), unicode(QApplication.translate("EntityBrowser", str(numRecords))), rowStr
        )
        self.setWindowTitle(windowTitle)

        return numRecords

    def _initializeData(self):
        """
        Set table model and load data into it.
        """
        if not self._dbmodel is None:
            display_mapping = self._dbmodel.displayMapping()
            headers = display_mapping.values()
            modelAttrs = display_mapping.keys()

            """
            Load entity data. There might be a better way in future in order to ensure that
            there is a balance between user data discovery experience and performance.
            """
            numRecords = self.recomputeRecordCount()

            # Load progress dialog
            progressLabel = QApplication.translate("EntityBrowser", "Fetching Records...")
            progressDialog = QProgressDialog(progressLabel, "", 0, numRecords, self)

            entity = self._dbmodel()
            entityRecords = entity.queryObject().filter().all()
            # entityRecordsList = [[getattr(er,attr)for attr in modelAttrs]for er in entityRecords]

            # Add records to nested list for enumeration in table model
            entityRecordsList = []
            for i, er in enumerate(entityRecords):
                entityRowInfo = []
                progressDialog.setValue(i)
                try:
                    for attr in modelAttrs:
                        attrVal = getattr(er, attr)

                        # Check if there are display formatters and apply if one exists for the given attribute
                        if attr in self._cellFormatters:
                            attrVal = self._cellFormatters[attr](attrVal)

                        if not attr in self._cellFormatters and isinstance(attrVal, date):
                            attrVal = dateFormatter(attrVal)

                        entityRowInfo.append(attrVal)

                except Exception as ex:
                    QMessageBox.critical(
                        self, QApplication.translate("EntityBrowser", "Loading Entity"), unicode(ex.message)
                    )
                    return

                entityRecordsList.append(entityRowInfo)

            # Set maximum value of the progress dialog
            progressDialog.setValue(numRecords)

            self._tableModel = BaseSTDMTableModel(entityRecordsList, headers, self)

            # Add filter columns
            class_name = entity.__class__.__name__.replace(" ", "_").lower()
            if table_searchable_cols(class_name):
                self.cboFilterColumn.addItems(table_searchable_cols(class_name))

            # Use sortfilter proxy model for the view
            self._proxyModel = VerticalHeaderSortFilterProxyModel()
            self._proxyModel.setDynamicSortFilter(True)
            self._proxyModel.setSourceModel(self._tableModel)
            self._proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
            self._proxyModel.setFilterKeyColumn(1)

            self.tbEntity.setModel(self._proxyModel)
            self.tbEntity.setSortingEnabled(True)
            self.tbEntity.sortByColumn(1, Qt.AscendingOrder)

            # First (ID) column will always be hidden
            self.tbEntity.hideColumn(0)
            self.cboFilterColumn.removeItem(0)

            self.tbEntity.horizontalHeader().setResizeMode(QHeaderView.Interactive)

            # Connect signals
            self.connect(self.cboFilterColumn, SIGNAL("currentIndexChanged (int)"), self.onFilterColumnChanged)
            self.connect(self.txtFilterPattern, SIGNAL("textChanged(const QString&)"), self.onFilterRegExpChanged)

    def onFilterColumnChanged(self, index):
        """
        Set the filter column for the proxy model.
        """
        self._proxyModel.setFilterKeyColumn((index + 1))

    def onFilterRegExpChanged(self, text):
        """
        Slot raised whenever the filter text changes.
        """
        regExp = QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString)
        self._proxyModel.setFilterRegExp(regExp)

    def onDoubleClickView(self, modelindex):
        """
        Slot raised upon double clicking the table view.
        To be implemented by subclasses.
        """
        pass

    def _selectedIds(self):
        """
        Get the IDs of the selected row in the table view.
        """
        self._notifBar.clear()

        selectedIds = []
        selRowIndices = self.tbEntity.selectionModel().selectedRows(0)

        if len(selRowIndices) == 0:
            msg = QApplication.translate("EntityBrowser", "Please select a record from the table.")
            self._notifBar.insertWarningNotification(msg)
            return selectedIds

        for proxyRowIndex in selRowIndices:
            # Get the index of the source or else the row items will have unpredictable behavior
            rowIndex = self._proxyModel.mapToSource(proxyRowIndex)
            entityId = rowIndex.data(Qt.DisplayRole)
            selectedIds.append(entityId)
            # QMessageBox.information(None, "Slected", str(selectedIds))

        return selectedIds

    def onAccept(self):
        """
        Slot raised when user clicks to accept the dialog. The resulting action will be dependent 
        on the state that the browser is currently configured in.
        """
        selIDs = self._selectedIds()
        if len(selIDs) == 0:
            return

        if self._mode == SELECT:
            # Get the first selected id
            selId = selIDs[0]
            self.recordSelected.emit(selId)
            self._notifBar.insertInfoNotification(QApplication.translate("EntityBrowser", "Record has been selected"))

    def addModelToView(self, modelObj):
        """
        Convenience method for adding model info into the view.
        """
        try:
            insertPosition = self._tableModel.rowCount()
            self._tableModel.insertRows(insertPosition, 1)

            for i, attr in enumerate(self._dbmodel.displayMapping().keys()):
                propIndex = self._tableModel.index(insertPosition, i)
                if hasattr(modelObj, attr):
                    attrVal = getattr(modelObj, attr)

                # Check if there re display formatters and apply if one exists for the given attribute
                if attr in self._cellFormatters:
                    attrVal = self._cellFormatters[attr](attrVal)
                if not attr in self._cellFormatters and isinstance(attrVal, date):
                    attrVal = dateFormatter(attrVal)

                self._tableModel.setData(propIndex, attrVal)
        except Exception as ex:
            QMessageBox.information(self, QApplication.translate("EntityBrowser", "Updating row"), str(ex.message))
            return

    def _modelFromID(self, recordid):
        """
        Convenience method that returns the model object based on its ID.
        """
        dbHandler = self._dbmodel()
        modelObj = dbHandler.queryObject().filter(self._dbmodel.id == recordid).first()

        return modelObj if modelObj != None else None
Ejemplo n.º 2
0
    def _initializeData(self):
        """
        Set table model and load data into it.
        """
        if not self._dbmodel is None:
            display_mapping = self._dbmodel.displayMapping()
            headers = display_mapping.values()
            modelAttrs = display_mapping.keys()

            """
            Load entity data. There might be a better way in future in order to ensure that
            there is a balance between user data discovery experience and performance.
            """
            numRecords = self.recomputeRecordCount()

            # Load progress dialog
            progressLabel = QApplication.translate("EntityBrowser", "Fetching Records...")
            progressDialog = QProgressDialog(progressLabel, "", 0, numRecords, self)

            entity = self._dbmodel()
            entityRecords = entity.queryObject().filter().all()
            # entityRecordsList = [[getattr(er,attr)for attr in modelAttrs]for er in entityRecords]

            # Add records to nested list for enumeration in table model
            entityRecordsList = []
            for i, er in enumerate(entityRecords):
                entityRowInfo = []
                progressDialog.setValue(i)
                try:
                    for attr in modelAttrs:
                        attrVal = getattr(er, attr)

                        # Check if there are display formatters and apply if one exists for the given attribute
                        if attr in self._cellFormatters:
                            attrVal = self._cellFormatters[attr](attrVal)

                        if not attr in self._cellFormatters and isinstance(attrVal, date):
                            attrVal = dateFormatter(attrVal)

                        entityRowInfo.append(attrVal)

                except Exception as ex:
                    QMessageBox.critical(
                        self, QApplication.translate("EntityBrowser", "Loading Entity"), unicode(ex.message)
                    )
                    return

                entityRecordsList.append(entityRowInfo)

            # Set maximum value of the progress dialog
            progressDialog.setValue(numRecords)

            self._tableModel = BaseSTDMTableModel(entityRecordsList, headers, self)

            # Add filter columns
            class_name = entity.__class__.__name__.replace(" ", "_").lower()
            if table_searchable_cols(class_name):
                self.cboFilterColumn.addItems(table_searchable_cols(class_name))

            # Use sortfilter proxy model for the view
            self._proxyModel = VerticalHeaderSortFilterProxyModel()
            self._proxyModel.setDynamicSortFilter(True)
            self._proxyModel.setSourceModel(self._tableModel)
            self._proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
            self._proxyModel.setFilterKeyColumn(1)

            self.tbEntity.setModel(self._proxyModel)
            self.tbEntity.setSortingEnabled(True)
            self.tbEntity.sortByColumn(1, Qt.AscendingOrder)

            # First (ID) column will always be hidden
            self.tbEntity.hideColumn(0)
            self.cboFilterColumn.removeItem(0)

            self.tbEntity.horizontalHeader().setResizeMode(QHeaderView.Interactive)

            # Connect signals
            self.connect(self.cboFilterColumn, SIGNAL("currentIndexChanged (int)"), self.onFilterColumnChanged)
            self.connect(self.txtFilterPattern, SIGNAL("textChanged(const QString&)"), self.onFilterRegExpChanged)