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
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)