示例#1
0
    def initialize(self):
        '''
        Configure the mapper based on the user settings.
        '''
        #Load headers
        if self._dbModel != None:
            headers = self._dbModel.displayMapping().values()
            self._tableModel = BaseSTDMTableModel([], headers, self)
            self._tbFKEntity.setModel(self._tableModel)

            #First (ID) column will always be hidden
            self._tbFKEntity.hideColumn(0)

            self._tbFKEntity.horizontalHeader().setResizeMode(
                QHeaderView.Stretch)
示例#2
0
    def initialize(self):
        """
        Configure the mapper based on the user settings.
        """
        from stdm.data import numeric_varchar_columns

        #Load headers
        if not self._dbModel is None:
            headers = []

            display_cols = numeric_varchar_columns(self._ds_name)

            #Ensure only displayable values are included
            for c, dc in self._dbModel.displayMapping().iteritems():
                if c in display_cols:
                    headers.append(dc)

            self._tableModel = BaseSTDMTableModel([],headers,self)
            self._tbFKEntity.setModel(self._tableModel)
            
            #First (ID) column will always be hidden
            self._tbFKEntity.hideColumn(0)
            
            self._tbFKEntity.horizontalHeader().setResizeMode(QHeaderView.Interactive)
            self._tbFKEntity.verticalHeader().setVisible(True)

            '''
            If expression builder is enabled then disable edit button since
            mapper cannot work in both selection and editing mode.
            '''
            if self._use_expression_builder:
                self._filter_entity_btn.setVisible(True)
                self._edit_entity_btn.setVisible(False)
示例#3
0
 def initialize(self):
     '''
     Configure the mapper based on the user settings.
     '''
     #Load headers
     if self._dbModel != None:
         headers = self._dbModel.displayMapping().values()
         self._tableModel = BaseSTDMTableModel([],headers,self)
         self._tbFKEntity.setModel(self._tableModel)
         
         #First (ID) column will always be hidden
         self._tbFKEntity.hideColumn(0)
         
         self._tbFKEntity.horizontalHeader().setResizeMode(QHeaderView.Stretch)
示例#4
0
class ForeignKeyMapper(QWidget):
    '''
    Foreign key mapper widget.
    ''' 
    
    #Custom signals
    beforeEntityAdded = pyqtSignal("PyQt_PyObject")
    afterEntityAdded = pyqtSignal("PyQt_PyObject")
    entityRemoved = pyqtSignal("PyQt_PyObject")
    
    def __init__(self,parent=None):
        QWidget.__init__(self,parent)
        
        self._tbFKEntity = QTableView(self)
        self._tbFKEntity.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self._tbFKEntity.setAlternatingRowColors(True)
        self._tbFKEntity.setSelectionBehavior(QAbstractItemView.SelectRows)
        
        tbActions = QToolBar()
        tbActions.setIconSize(QSize(16,16))
        
        self._addEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/add.png"),
                                  QApplication.translate("ForeignKeyMapper","Add"),self)
        self.connect(self._addEntityAction,SIGNAL("triggered()"),self.onAddEntity)
        
        self._removeEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/remove.png"),
                                  QApplication.translate("ForeignKeyMapper","Remove"),self)
        self.connect(self._removeEntityAction,SIGNAL("triggered()"),self.onRemoveEntity)
        
        tbActions.addAction(self._addEntityAction)
        tbActions.addAction(self._removeEntityAction)
        
        layout = QVBoxLayout(self)
        layout.setSpacing(2)
        layout.setMargin(5)
        
        layout.addWidget(tbActions)
        layout.addWidget(self._tbFKEntity)
        
        #Instance variables
        self._dbModel = None
        self._omitPropertyNames = []
        self._entitySelector = None
        self._entitySelectorState = None
        self._supportsLists = True
        self._tableModel = None
        self._notifBar = None
        self._cellFormatters = {}
        self._deleteOnRemove = False
        self._uniqueValueColIndices = OrderedDict()
        
    def initialize(self):
        '''
        Configure the mapper based on the user settings.
        '''
        #Load headers
        if self._dbModel != None:
            headers = self._dbModel.displayMapping().values()
            self._tableModel = BaseSTDMTableModel([],headers,self)
            self._tbFKEntity.setModel(self._tableModel)
            
            #First (ID) column will always be hidden
            self._tbFKEntity.hideColumn(0)
            
            self._tbFKEntity.horizontalHeader().setResizeMode(QHeaderView.Stretch)
        
    def databaseModel(self):
        '''
        Returns the database model that represents the foreign key entity.
        '''
        return self._dbModel
    
    def setDatabaseModel(self,model):
        '''
        Set the database model that represents the foreign key entity.
        Model has to be a callable.
        '''
        self._dbModel = model
        
    def setEmitPropertyNames(self,propnames):
        '''
        Set the property names to be omitted from the display in the table list view.
        '''
        self._omitPropertyNames = propnames
        
    def omitPropertyNames(self):
        '''
        Returns the property names to be omitted from the display in the table list view.
        '''
        return self._omitPropertyNames
    
    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 cellFormatters(self):
        """
        Returns a dictionary of cell formatters used by the foreign key mapper.
        """
        return self._cellFormatters
    
    def entitySelector(self):
        '''
        Returns the dialog for selecting the entity objects.
        '''
        return self._entitySelector
    
    def setEntitySelector(self,selector,state=SELECT):
        '''
        Set the dialog for selecting entity objects.
        Selector must be a callable.
        '''
        self._entitySelector = selector
        self._entitySelectorState = state
        
    def supportList(self):
        '''
        Returns whether the mapper supports only one item or multiple entities i.e.
        one-to-one and one-to-many mapping. 
        Default is 'True'.
        '''
        return self._supportsLists
    
    def setSupportsList(self,supportsList):
        '''
        Sets whether the mapper supports only one item or multiple entities i.e.
        one-to-one (False) and one-to-many mapping (True).
        '''
        self._supportsLists = supportsList
        
    def setNotificationBar(self,notificationBar):
        '''
        Set the notification bar for displaying user messages.
        '''
        self._notifBar = notificationBar
        
    def viewModel(self):
        '''
        Return the view model used by the table view.
        '''
        return self._tableModel
    
    def deleteOnRemove(self):
        '''
        Returns the state whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        return self._deleteOnRemove
    
    def setDeleteonRemove(self,delete):
        '''
        Set whether whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        self._deleteOnRemove = delete
        
    def addUniqueColumnName(self,colName,replace = True):
        '''
        Set the name of the column whose values are to be unique.
        If 'replace' is True then the existing row will be replaced
        with one with the new value; else, the new row will not be added to the list.
        '''
        headers = self._dbModel.displayMapping().values()
        colIndex = getIndex(headers,colName)
        
        if colIndex != -1:
            self.addUniqueColumnIndex(colIndex, replace)
        
    def addUniqueColumnIndex(self,colIndex,replace = True):
        '''
        Set the index of the column whose values are to be unique. The column indices are
        zero-based.
        If 'replace' is True then the existing row will be replaced with the
        new value; else, the new row will not be added to the list.
        For multiple replace rules defined, then the first one added to the collection is the
        one that will be applied.
        '''
        self._uniqueValueColIndices[colIndex] = replace
    
    def _removeRow(self,rowNumber):
        '''
        Remove the row at the given index.
        '''
        self._tableModel.removeRows(rowNumber, 1)
        
    def onRemoveEntity(self):
        '''
        Slot raised on clicking to remove the selected entity.
        '''
        selectedRowIndices = self._tbFKEntity.selectionModel().selectedRows(0)
        
        if len(selectedRowIndices) == 0:
            msg = QApplication.translate("ForeignKeyMapper","Please select the record to be removed.")   
            self._notifBar.clear()
            self._notifBar.insertWarningNotification(msg)
            
        for selectedRowIndex in selectedRowIndices:
            #Delete record from database if flag has been set to True
            recId= selectedRowIndex.data()
            
            dbHandler = self._dbModel()
            delRec = dbHandler.queryObject().filter(self._dbModel.id == recId).first()
            
            if not delRec is None:
                self.entityRemoved.emit(delRec)
                
                if self._deleteOnRemove:
                    delRec.delete()
            
            self._removeRow(selectedRowIndex.row()) 
            
    def _recordIds(self):
        '''
        Returns the primary keys of the records in the table.
        '''
        rowCount = self._tableModel.rowCount()
        recordIds = []
        
        for r in range(rowCount):
            #Get ID value
            modelIndex = self._tableModel.index(r,0)
            modelId = modelIndex.data()
            recordIds.append(modelId)
                
        return recordIds
            
    def entities(self):
        '''
        Returns the model instance(s) depending on the configuration specified by the user.
        '''      
        recIds = self._recordIds()
                
        modelInstances = self._modelInstanceFromIds(recIds)
        
        if len(modelInstances) == 0:
            return None
        else:
            if self._supportsLists:
                return modelInstances
            else:
                return modelInstances[0]
            
    def setEntities(self,entities):
        '''
        Insert entities into the table.
        '''
        if isinstance(entities,list):
            for entity in entities:
                self._insertModelToView(entity)
                
        else:
            self._insertModelToView(entities)
            
    def searchModel(self,columnIndex,columnValue):
        '''
        Searches for 'columnValue' in the column whose index is specified by 'columnIndex' in all 
        rows contained in the model.
        '''
        if isinstance (columnValue,QVariant):
            columnValue = str(columnValue.toString())
            
        if not isinstance(columnValue,str):
            columnValue = str(columnValue)
            
        columnValue = columnValue.strip()
        
        proxy = QSortFilterProxyModel(self)
        proxy.setSourceModel(self._tableModel)
        proxy.setFilterKeyColumn(columnIndex)
        proxy.setFilterFixedString(columnValue)
        #Will return model index containing the primary key.
        matchingIndex = proxy.mapToSource(proxy.index(0,0))
        
        return matchingIndex
    
    def _modelInstanceFromIds(self,ids):
        '''
        Returns the model instance based the value of its primary key.
        '''
        dbHandler = self._dbModel()
        
        modelInstances = []
        
        for modelId in ids:
            modelObj = dbHandler.queryObject().filter(self._dbModel.id == modelId).first()
            if modelObj != None:
                modelInstances.append(modelObj)
            
        return modelInstances
        
    def _onRecordSelectedEntityBrowser(self,recid):
        '''
        Slot raised when the user has clicked the select button in the 'EntityBrowser' dialog
        to add the selected record to the table's list.
        Add the record to the foreign key table using the mappings.
        '''
        #Check if the record exists using the primary key so as to ensure only one instance is added to the table
        recIndex = getIndex(self._recordIds(),id)
        if recIndex != -1:
            return
        
        dbHandler = self._dbModel()
        modelObj = dbHandler.queryObject().filter(self._dbModel.id == recid).first()
        
        if modelObj != None:
            #Raise before entity added signal
            self.beforeEntityAdded.emit(modelObj)
            
            #Validate for unique value configurations
            for colIndex,replace in self._uniqueValueColIndices.items():
                attrName = self._dbModel.displayMapping().keys()[colIndex]
                attrValue = getattr(modelObj,attrName)
                
                #Check to see if there are cell formatters so that the correct value is searched for in the model
                if attrName in self._cellFormatters:
                    attrValue = self._cellFormatters[attrName](attrValue)
                    
                matchingIndex = self.searchModel(colIndex, attrValue)
                
                if matchingIndex.isValid():
                    if replace:
                        existingId = matchingIndex.data()
                        
                        #Delete old record from db
                        entityModels = self._modelInstanceFromIds([existingId])
                        if len(entityModels) > 0:
                            entityModels[0].delete()
                        self._removeRow(matchingIndex.row())
                        
                        break
                    
                    else:
                        #Break. Do not add item to the list.
                        return
            
            if not self._supportsLists and self._tableModel.rowCount() > 0:
                self._removeRow(0)
            self._insertModelToView(modelObj)
            
    def _insertModelToView(self,modelObj):    
        '''
        Insert the given database model instance into the view.
        '''
        insertPosition = self._tableModel.rowCount()
        self._tableModel.insertRows(insertPosition, 1)
        
        for i,attr in enumerate(self._dbModel.displayMapping().keys()):
            propIndex = self._tableModel.index(insertPosition,i)
            attrVal = getattr(modelObj,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)
            self._tableModel.setData(propIndex, attrVal)
            
        #Raise signal once entity has been inserted
        self.afterEntityAdded.emit(modelObj)
    
    def onAddEntity(self):
        '''
        Slot raised on selecting to add related entities that will be mapped to the primary
        database model instance.
        '''
        if self._entitySelector != None:           
            entitySelector = self._entitySelector(self,self._entitySelectorState)
            #Cascade cell formatters
            entitySelector.setCellFormatters(self._cellFormatters)
            self.connect(entitySelector, SIGNAL("recordSelected(int)"),self._onRecordSelectedEntityBrowser)
            #self.connect(entitySelector, SIGNAL("destroyed(QObject *)"),self.onEntitySelectorDestroyed)
            
            retStatus = entitySelector.exec_()
            if retStatus == QDialog.Accepted:
                pass
                
        else:
            if self._notifBar != None:
                msg = QApplication.translate("ForeignKeyMapper","Null instance of entity selector.")   
                self._notifBar.clear()
                self._notifBar.insertErrorNotification(msg)           
示例#5
0
class ForeignKeyMapper(QWidget):
    """
    Widget for selecting database records through an entity browser or
    using an ExpressionBuilder for filtering records.
    """
    #Custom signals
    beforeEntityAdded = pyqtSignal("PyQt_PyObject")
    afterEntityAdded = pyqtSignal("PyQt_PyObject")
    entityRemoved = pyqtSignal("PyQt_PyObject")
    
    def __init__(self, parent=None):
        QWidget.__init__(self,parent)
        
        self._tbFKEntity = QTableView(self)
        self._tbFKEntity.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self._tbFKEntity.setAlternatingRowColors(True)
        self._tbFKEntity.setSelectionBehavior(QAbstractItemView.SelectRows)

        self._add_entity_btn = QToolButton(self)
        self._add_entity_btn.setToolTip(QApplication.translate("ForeignKeyMapper","Add"))
        self._add_entity_btn.setIcon(QIcon(":/plugins/stdm/images/icons/add.png"))
        self._add_entity_btn.clicked.connect(self.onAddEntity)

        self._edit_entity_btn = QToolButton(self)
        self._edit_entity_btn.setVisible(False)
        self._edit_entity_btn.setToolTip(QApplication.translate("ForeignKeyMapper","Edit"))
        self._edit_entity_btn.setIcon(QIcon(":/plugins/stdm/images/icons/edit.png"))

        self._filter_entity_btn = QToolButton(self)
        self._filter_entity_btn.setVisible(False)
        self._filter_entity_btn.setToolTip(QApplication.translate("ForeignKeyMapper","Select by expression"))
        self._filter_entity_btn.setIcon(QIcon(":/plugins/stdm/images/icons/filter.png"))
        self._filter_entity_btn.clicked.connect(self.onFilterEntity)

        self._delete_entity_btn = QToolButton(self)
        self._delete_entity_btn.setToolTip(QApplication.translate("ForeignKeyMapper","Remove"))
        self._delete_entity_btn.setIcon(QIcon(":/plugins/stdm/images/icons/remove.png"))
        self._delete_entity_btn.clicked.connect(self.onRemoveEntity)

        layout = QVBoxLayout(self)
        layout.setSpacing(4)
        layout.setMargin(5)

        grid_layout = QGridLayout(self)
        grid_layout.setHorizontalSpacing(5)
        grid_layout.addWidget(self._add_entity_btn, 0, 0, 1, 1)
        grid_layout.addWidget(self._filter_entity_btn, 0, 1, 1, 1)
        grid_layout.addWidget(self._edit_entity_btn, 0, 2, 1, 1)
        grid_layout.addWidget(self._delete_entity_btn, 0, 3, 1, 1)
        grid_layout.setColumnStretch(4, 5)

        layout.addLayout(grid_layout)
        layout.addWidget(self._tbFKEntity)
        
        #Instance variables
        self._dbModel = None
        self._ds_name = ""
        self._omitPropertyNames = []
        self._entitySelector = None
        self._entitySelectorState = None
        self._supportsLists = True
        self._tableModel = None
        self._notifBar = None
        self._cellFormatters = {}
        self._deleteOnRemove = False
        self._uniqueValueColIndices = OrderedDict()
        self.global_id = None
        self.display_column = None
        self._deferred_objects = {}
        self._use_expression_builder = False
        
    def initialize(self):
        """
        Configure the mapper based on the user settings.
        """
        from stdm.data import numeric_varchar_columns

        #Load headers
        if not self._dbModel is None:
            headers = []

            display_cols = numeric_varchar_columns(self._ds_name)

            #Ensure only displayable values are included
            for c, dc in self._dbModel.displayMapping().iteritems():
                if c in display_cols:
                    headers.append(dc)

            self._tableModel = BaseSTDMTableModel([],headers,self)
            self._tbFKEntity.setModel(self._tableModel)
            
            #First (ID) column will always be hidden
            self._tbFKEntity.hideColumn(0)
            
            self._tbFKEntity.horizontalHeader().setResizeMode(QHeaderView.Interactive)
            self._tbFKEntity.verticalHeader().setVisible(True)

            '''
            If expression builder is enabled then disable edit button since
            mapper cannot work in both selection and editing mode.
            '''
            if self._use_expression_builder:
                self._filter_entity_btn.setVisible(True)
                self._edit_entity_btn.setVisible(False)
        
    def databaseModel(self):
        '''
        Returns the database model that represents the foreign key entity.
        '''
        return self._dbModel
    
    def setDatabaseModel(self,model):
        '''
        Set the database model that represents the foreign key entity.
        Model has to be a callable.
        '''
        self._dbModel = model

    def data_source_name(self):
        """
        :return: Returns the name of the data source (as specified in the
        database).
        :rtype: str
        """
        return self._ds_name

    def set_data_source_name(self, ds_name):
        """
        Set the name of the data source (as specified in the database). This
        will be used to construct the vector layer when filtering records
        using the expression builder.
        We are using this option since we cannot extract the table/view name
        from the database model.
        :param ds_name: Name of the data source.
        :type ds_name: str
        """
        self._ds_name = ds_name
        
    def setEmitPropertyNames(self,propnames):
        '''
        Set the property names to be omitted from the display in the table list view.
        '''
        self._omitPropertyNames = propnames
        
    def omitPropertyNames(self):
        '''
        Returns the property names to be omitted from the display in the table list view.
        '''
        return self._omitPropertyNames
    
    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 cellFormatters(self):
        """
        Returns a dictionary of cell formatters used by the foreign key mapper.
        """
        return self._cellFormatters
    
    def entitySelector(self):
        '''
        Returns the dialog for selecting the entity objects.
        '''
        return self._entitySelector
    
    def setEntitySelector(self, selector, state=SELECT):
        '''
        Set the dialog for selecting entity objects.
        Selector must be a callable.
        '''
        if callable(selector):
            self._entitySelector = selector
        else:
            self._entitySelector = selector.__class__

        self._entitySelectorState = state
        
    def supportList(self):
        '''
        Returns whether the mapper supports only one item or multiple entities i.e.
        one-to-one and one-to-many mapping. 
        Default is 'True'.
        '''
        return self._supportsLists
    
    def setSupportsList(self,supportsList):
        '''
        Sets whether the mapper supports only one item or multiple entities i.e.
        one-to-one (False) and one-to-many mapping (True).
        '''
        self._supportsLists = supportsList
        
    def setNotificationBar(self,notificationBar):
        '''
        Set the notification bar for displaying user messages.
        '''
        self._notifBar = notificationBar
        
    def viewModel(self):
        '''
        Return the view model used by the table view.
        '''
        return self._tableModel

    def set_expression_builder(self, state):
        """
        Set the mapper to use QGIS expression builder as the entity selector.
        """
        self._use_expression_builder = state

    def expression_builder_enabled(self):
        """
        Returns whether the mapper has been configured to use the expression builder
        """
        return self._use_expression_builder
    
    def deleteOnRemove(self):
        '''
        Returns the state whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        return self._deleteOnRemove
    
    def setDeleteonRemove(self,delete):
        '''
        Set whether whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        self._deleteOnRemove = delete
        
    def addUniqueColumnName(self,colName,replace = True):
        '''
        Set the name of the column whose values are to be unique.
        If 'replace' is True then the existing row will be replaced
        with one with the new value; else, the new row will not be added to the list.
        '''
        headers = self._dbModel.displayMapping().values()
        colIndex = getIndex(headers,colName)
        
        if colIndex != -1:
            self.addUniqueColumnIndex(colIndex, replace)
        
    def addUniqueColumnIndex(self,colIndex,replace = True):
        '''
        Set the index of the column whose values are to be unique. The column indices are
        zero-based.
        If 'replace' is True then the existing row will be replaced with the
        new value; else, the new row will not be added to the list.
        For multiple replace rules defined, then the first one added to the collection is the
        one that will be applied.
        '''
        self._uniqueValueColIndices[colIndex] = replace

    def onFilterEntity(self):
        """
        Slot raised to load the expression builder dialog.
        """
        vl, msg = self.vector_layer()

        if vl is None:
            msg = msg + "\n" + QApplication.translate("ForeignKeyMapper",
                            "The expression builder cannot be used at this moment.")
            QMessageBox.critical(self, QApplication.translate("ForeignKeyMapper",
                                            "Expression Builder"), msg)

            return

        if callable(self._dbModel):
            context = self._dbModel.__name__

        else:
            context = self._dbModel.__class__.__name__

        filter_dlg = ForeignKeyMapperExpressionDialog(vl, self, context=context)
        filter_dlg.setWindowTitle(QApplication.translate("ForeignKeyMapper",
                                                    "Filter By Expression"))
        filter_dlg.recordSelected[int].connect(self._onRecordSelectedEntityBrowser)

        res = filter_dlg.exec_()
    
    def _removeRow(self,rowNumber):
        '''
        Remove the row at the given index.
        '''
        self._tableModel.removeRows(rowNumber, 1)
        
    def onRemoveEntity(self):
        '''
        Slot raised on clicking to remove the selected entity.
        '''
        selectedRowIndices = self._tbFKEntity.selectionModel().selectedRows(0)
        
        if len(selectedRowIndices) == 0:
            msg = QApplication.translate("ForeignKeyMapper","Please select the record to be removed.")   
            self._notifBar.clear()
            self._notifBar.insertWarningNotification(msg)
            
        for selectedRowIndex in selectedRowIndices:
            #Delete record from database if flag has been set to True
            recId= selectedRowIndex.data()
            
            dbHandler = self._dbModel()
            delRec = dbHandler.queryObject().filter(self._dbModel.id == recId).first()
            
            if not delRec is None:
                self.entityRemoved.emit(delRec)
                
                if self._deleteOnRemove:
                    delRec.delete()
            
            self._removeRow(selectedRowIndex.row()) 
            
    def _recordIds(self):
        '''
        Returns the primary keys of the records in the table.
        '''
        recordIds = []

        if self._tableModel:
            rowCount = self._tableModel.rowCount()
            
            for r in range(rowCount):
                #Get ID value
                modelIndex = self._tableModel.index(r,0)
                modelId = modelIndex.data()
                recordIds.append(modelId)
                    
        return recordIds
            
    def entities(self):
        '''
        Returns the model instance(s) depending on the configuration specified by the user.
        '''
        recIds = self._recordIds()
                
        modelInstances = self._modelInstanceFromIds(recIds)
        
        if len(modelInstances) == 0:
            if self._supportsLists:
                return []

            else:
                return None

        else:
            if self._supportsLists:
                return modelInstances

            else:
                return modelInstances[0]
            
    def setEntities(self, entities):
        '''
        Insert entities into the table.
        '''
        if isinstance(entities,list):
            for entity in entities:
                self._insertModelToView(entity)
                
        else:
            self._insertModelToView(entities)
            
    def searchModel(self, columnIndex, columnValue):
        '''
        Searches for 'columnValue' in the column whose index is specified by 'columnIndex' in all 
        rows contained in the model.
        '''
        if isinstance (columnValue, QVariant):
            columnValue = unicode(columnValue.toString())

        if not isinstance(columnValue, str) or \
                not isinstance(columnValue, unicode):
            columnValue = unicode(columnValue)

        columnValue = columnValue.strip()

        proxy = QSortFilterProxyModel(self)
        proxy.setSourceModel(self._tableModel)
        proxy.setFilterKeyColumn(columnIndex)
        proxy.setFilterFixedString(columnValue)
        #Will return model index containing the primary key.
        matchingIndex = proxy.mapToSource(proxy.index(0,0))

        return matchingIndex
    
    def _modelInstanceFromIds(self,ids):
        '''
        Returns the model instance based the value of its primary key.
        '''
        dbHandler = self._dbModel()
        
        modelInstances = []
        
        for modelId in ids:
            modelObj = dbHandler.queryObject().filter(self._dbModel.id == modelId).first()
            if not modelObj is None:
                modelInstances.append(modelObj)

        return modelInstances
        
    def _onRecordSelectedEntityBrowser(self, rec, row_number=-1):
        '''
        Slot raised when the user has clicked the select button in the 'EntityBrowser' dialog
        to add the selected record to the table's list.
        Add the record to the foreign key table using the mappings.
        '''
        #Check if the record exists using the primary key so as to ensure only one instance is added to the table
        try:
            if isinstance(rec, int):
                recIndex = getIndex(self._recordIds(), rec)

                if recIndex != -1:
                    return


                dbHandler = self._dbModel()
                modelObj = dbHandler.queryObject().filter(self._dbModel.id == rec).first()

            elif isinstance(rec, object):
                modelObj = rec

            else:
                return

        except:
            pass

        dbHandler = self._dbModel()
        modelObj = dbHandler.queryObject().filter(self._dbModel.id == rec).first()

        if not modelObj is None:
            #Raise before entity added signal
            self.beforeEntityAdded.emit(modelObj)
            
            #Validate for unique value configurations
            if not self._validate_unique_columns(modelObj, row_number):
                return

            if self._tableModel:
                if not self._supportsLists and self._tableModel.rowCount() > 0:
                    self._removeRow(0)

                insert_position = self._insertModelToView(modelObj, row_number)

                if isinstance(rec, object):
                    self._deferred_objects[insert_position] = modelObj

            else:
                try:
                    self.global_id = self.onfk_lookup(modelObj)

                except Exception as ex:
                    QMessageBox.information(self,
                                    QApplication.translate("ForeignKeyMapper",
                                                        "Foreign Key Reference"),
                                            unicode(ex.message))

                    return

    def set_model_display_column(self, name):
        """
        :return:
        """
        self.display_column = name

    def onfk_lookup(self,obj, index=None):
        """
        :param Model obj:
        :param :
        :return:
        """
        display_label = None
        base_id = getattr(obj, 'id')
        col_list = self._dbModel.displayMapping().keys()

        if self.display_column:
            display_label = getattr(obj, self.display_column)

        else:
            display_label = getattr(obj, col_list[1])

        fk_reference = FKBrowserProperty(base_id, display_label)

        return fk_reference

    def _validate_unique_columns(self,model,exclude_row = -1):
        """
        Loop through the attributes of the model to assert for existing row values that should be unique based on
        the configuration of unique columns.
        """
        for colIndex,replace in self._uniqueValueColIndices.items():
            attrName = self._dbModel.displayMapping().keys()[colIndex]
            attrValue = getattr(model,attrName)

            #Check to see if there are cell formatters so that the correct value is searched for in the model
            if attrName in self._cellFormatters:
                attrValue = self._cellFormatters[attrName](attrValue)

            matchingIndex = self.searchModel(colIndex, attrValue)

            if matchingIndex.isValid() and matchingIndex.row() != exclude_row:
                if replace:
                    existingId = matchingIndex.data()

                    #Delete old record from db
                    entityModels = self._modelInstanceFromIds([existingId])

                    if len(entityModels) > 0:
                        entityModels[0].delete()

                    self._removeRow(matchingIndex.row())

                    return True

                else:
                    #Break. Do not add item to the list.
                    return False

        return True

    def _insertModelToView(self, model_obj, row_number = -1):
        """
        Insert the given database model instance into the view at the given row number position.
        """
        if row_number == -1:
            row_number = self._tableModel.rowCount()
            self._tableModel.insertRows(row_number, 1)

        '''
        Reset model so that we get the correct table mapping. This is a hack,
        will get a better solution in the future.
        '''
        model = DeclareMapping.instance().tableMapping(self._dbModel.__name__.lower())

        for i,attr in enumerate(self._dbModel.displayMapping().keys()):
            propIndex = self._tableModel.index(row_number,i)
            attrVal = getattr(model_obj, 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)

            self._tableModel.setData(propIndex, attrVal)

        #Raise signal once entity has been inserted
        self.afterEntityAdded.emit(model_obj)

        return row_number

    def vector_layer(self):
        """
        Returns a QgsVectorLayer based on the configuration information
        specified in the mapper including the system-wide data connection
        properties.
        """
        from stdm.data import vector_layer

        if self._dbModel is None:
            msg = QApplication.translate("ForeignKeyMapper",
                                         "Primary database model object not defined.")
            return None, msg

        filter_layer = vector_layer(self._ds_name)
        if filter_layer is None:
            msg = QApplication.translate("ForeignKeyMapper",
                "Vector layer could not be constructed from the database table.")

            return None, msg

        if not filter_layer.isValid():
            trans_msg = QApplication.translate("ForeignKeyMapper",
                u"The vector layer for '{0}' table is invalid.")
            msg = trans_msg.format(self._ds_name)

            return None, msg

        return filter_layer, ""
    
    def onAddEntity(self):
        """
        Slot raised on selecting to add related entities that will be mapped to the primary
        database model instance.
        """
        if self._tableModel:
            if not self._entitySelector is None:
                entitySelector = self._entitySelector(self, self._dbModel,
                                                    self._entitySelectorState)

                #Cascade cell formatters
                entitySelector.setCellFormatters(self._cellFormatters)
                entitySelector.recordSelected[int].connect(self._onRecordSelectedEntityBrowser)

                retStatus = entitySelector.exec_()
                if retStatus == QDialog.Accepted:
                    pass

            else:
                if not self._notifBar is None:
                    msg = QApplication.translate("ForeignKeyMapper","Null instance of entity selector.")
                    self._notifBar.clear()
                    self._notifBar.insertErrorNotification(msg)
        else:
            entitySelector = self._entitySelector(self,
                                                  self._dbModel,
                                                  self._entitySelectorState)
            entitySelector.recordSelected[int].connect(self._onRecordSelectedEntityBrowser)

            retStatus = entitySelector.exec_()
            if retStatus == QDialog.Accepted:
                pass
示例#6
0
class EntityBrowser(QDialog,Ui_EntityBrowser,SupportsManageMixin):
    '''
    Database model entity browser.
    ''' 
    
    '''
    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)
        
        self._dbmodel = dataModel
        self._state = state
        self._tableModel = None
        self._dataInitialized = False
        self._notifBar = NotificationBar(self.vlNotification)
        self._cellFormatters = {}
        #self._dateFormatter = dateFormatter
        
        #Connect signals
        self.connect(self.buttonBox,SIGNAL("accepted ()"),self.onAccept)
        self.connect(self.tbEntity,SIGNAL("doubleClicked (const QModelIndex&)"),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
        
        if self._dbmodel != None:
            self._initializeData()
            
        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(str(self.title()), \
                                                  str(QApplication.translate("EntityBrowser", str(numRecords))),rowStr)
        self.setWindowTitle(windowTitle)
        
        return numRecords
    
    def _initializeData(self):
        '''
        Set table model and load data into it.
        '''
        if self._dbmodel != None:
            headers = self._dbmodel.displayMapping().values()
            modelAttrs = self._dbmodel.displayMapping().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.information(None, QApplication.translate("EntityBrowser", "Loading dialog"), str(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
            self.cboFilterColumn.addItems(headers)
            
            #Use sortfilter proxy model for the view
            self._proxyModel = QSortFilterProxyModel()
            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.Stretch)
            
            #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)
                
        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)
                #QMessageBox.information(self, 'model',"propertyindex;{0}\nattributeVal;{1}".format(str(propIndex), str(attrVal)))
                #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
示例#7
0
    def _initializeData(self):
        '''
        Set table model and load data into it.
        '''
        if self._dbmodel != None:
            headers = self._dbmodel.displayMapping().values()
            modelAttrs = self._dbmodel.displayMapping().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.information(None, QApplication.translate("EntityBrowser", "Loading dialog"), str(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
            self.cboFilterColumn.addItems(headers)
            
            #Use sortfilter proxy model for the view
            self._proxyModel = QSortFilterProxyModel()
            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.Stretch)
            
            #Connect signals
            self.connect(self.cboFilterColumn, SIGNAL("currentIndexChanged (int)"),self.onFilterColumnChanged)
            self.connect(self.txtFilterPattern, SIGNAL("textChanged(const QString&)"),self.onFilterRegExpChanged)
示例#8
0
class ForeignKeyMapper(QWidget):
    '''
    Foreign key mapper widget.
    '''

    #Custom signals
    beforeEntityAdded = pyqtSignal("PyQt_PyObject")
    afterEntityAdded = pyqtSignal("PyQt_PyObject")
    entityRemoved = pyqtSignal("PyQt_PyObject")

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self._tbFKEntity = QTableView(self)
        self._tbFKEntity.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self._tbFKEntity.setAlternatingRowColors(True)
        self._tbFKEntity.setSelectionBehavior(QAbstractItemView.SelectRows)

        tbActions = QToolBar()
        tbActions.setIconSize(QSize(16, 16))

        self._addEntityAction = QAction(
            QIcon(":/plugins/stdm/images/icons/add.png"),
            QApplication.translate("ForeignKeyMapper", "Add"), self)
        self.connect(self._addEntityAction, SIGNAL("triggered()"),
                     self.onAddEntity)

        self._removeEntityAction = QAction(
            QIcon(":/plugins/stdm/images/icons/remove.png"),
            QApplication.translate("ForeignKeyMapper", "Remove"), self)
        self.connect(self._removeEntityAction, SIGNAL("triggered()"),
                     self.onRemoveEntity)

        tbActions.addAction(self._addEntityAction)
        tbActions.addAction(self._removeEntityAction)

        layout = QVBoxLayout(self)
        layout.setSpacing(2)
        layout.setMargin(5)

        layout.addWidget(tbActions)
        layout.addWidget(self._tbFKEntity)

        #Instance variables
        self._dbModel = None
        self._omitPropertyNames = []
        self._entitySelector = None
        self._entitySelectorState = None
        self._supportsLists = True
        self._tableModel = None
        self._notifBar = None
        self._cellFormatters = {}
        self._deleteOnRemove = False
        self._uniqueValueColIndices = OrderedDict()

    def initialize(self):
        '''
        Configure the mapper based on the user settings.
        '''
        #Load headers
        if self._dbModel != None:
            headers = self._dbModel.displayMapping().values()
            self._tableModel = BaseSTDMTableModel([], headers, self)
            self._tbFKEntity.setModel(self._tableModel)

            #First (ID) column will always be hidden
            self._tbFKEntity.hideColumn(0)

            self._tbFKEntity.horizontalHeader().setResizeMode(
                QHeaderView.Stretch)

    def databaseModel(self):
        '''
        Returns the database model that represents the foreign key entity.
        '''
        return self._dbModel

    def setDatabaseModel(self, model):
        '''
        Set the database model that represents the foreign key entity.
        Model has to be a callable.
        '''
        self._dbModel = model

    def setEmitPropertyNames(self, propnames):
        '''
        Set the property names to be omitted from the display in the table list view.
        '''
        self._omitPropertyNames = propnames

    def omitPropertyNames(self):
        '''
        Returns the property names to be omitted from the display in the table list view.
        '''
        return self._omitPropertyNames

    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 cellFormatters(self):
        """
        Returns a dictionary of cell formatters used by the foreign key mapper.
        """
        return self._cellFormatters

    def entitySelector(self):
        '''
        Returns the dialog for selecting the entity objects.
        '''
        return self._entitySelector

    def setEntitySelector(self, selector, state=SELECT):
        '''
        Set the dialog for selecting entity objects.
        Selector must be a callable.
        '''
        self._entitySelector = selector
        self._entitySelectorState = state

    def supportList(self):
        '''
        Returns whether the mapper supports only one item or multiple entities i.e.
        one-to-one and one-to-many mapping. 
        Default is 'True'.
        '''
        return self._supportsLists

    def setSupportsList(self, supportsList):
        '''
        Sets whether the mapper supports only one item or multiple entities i.e.
        one-to-one (False) and one-to-many mapping (True).
        '''
        self._supportsLists = supportsList

    def setNotificationBar(self, notificationBar):
        '''
        Set the notification bar for displaying user messages.
        '''
        self._notifBar = notificationBar

    def viewModel(self):
        '''
        Return the view model used by the table view.
        '''
        return self._tableModel

    def deleteOnRemove(self):
        '''
        Returns the state whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        return self._deleteOnRemove

    def setDeleteonRemove(self, delete):
        '''
        Set whether whether a record should be deleted from the database when it 
        is removed from the list.
        '''
        self._deleteOnRemove = delete

    def addUniqueColumnName(self, colName, replace=True):
        '''
        Set the name of the column whose values are to be unique.
        If 'replace' is True then the existing row will be replaced
        with one with the new value; else, the new row will not be added to the list.
        '''
        headers = self._dbModel.displayMapping().values()
        colIndex = getIndex(headers, colName)

        if colIndex != -1:
            self.addUniqueColumnIndex(colIndex, replace)

    def addUniqueColumnIndex(self, colIndex, replace=True):
        '''
        Set the index of the column whose values are to be unique. The column indices are
        zero-based.
        If 'replace' is True then the existing row will be replaced with the
        new value; else, the new row will not be added to the list.
        For multiple replace rules defined, then the first one added to the collection is the
        one that will be applied.
        '''
        self._uniqueValueColIndices[colIndex] = replace

    def _removeRow(self, rowNumber):
        '''
        Remove the row at the given index.
        '''
        self._tableModel.removeRows(rowNumber, 1)

    def onRemoveEntity(self):
        '''
        Slot raised on clicking to remove the selected entity.
        '''
        selectedRowIndices = self._tbFKEntity.selectionModel().selectedRows(0)

        if len(selectedRowIndices) == 0:
            msg = QApplication.translate(
                "ForeignKeyMapper", "Please select the record to be removed.")
            self._notifBar.clear()
            self._notifBar.insertWarningNotification(msg)

        for selectedRowIndex in selectedRowIndices:
            #Delete record from database if flag has been set to True
            recId = selectedRowIndex.data()

            dbHandler = self._dbModel()
            delRec = dbHandler.queryObject().filter(
                self._dbModel.id == recId).first()

            if not delRec is None:
                self.entityRemoved.emit(delRec)

                if self._deleteOnRemove:
                    delRec.delete()

            self._removeRow(selectedRowIndex.row())

    def _recordIds(self):
        '''
        Returns the primary keys of the records in the table.
        '''
        rowCount = self._tableModel.rowCount()
        recordIds = []

        for r in range(rowCount):
            #Get ID value
            modelIndex = self._tableModel.index(r, 0)
            modelId = modelIndex.data()
            recordIds.append(modelId)

        return recordIds

    def entities(self):
        '''
        Returns the model instance(s) depending on the configuration specified by the user.
        '''
        recIds = self._recordIds()

        modelInstances = self._modelInstanceFromIds(recIds)

        if len(modelInstances) == 0:
            return None
        else:
            if self._supportsLists:
                return modelInstances
            else:
                return modelInstances[0]

    def setEntities(self, entities):
        '''
        Insert entities into the table.
        '''
        if isinstance(entities, list):
            for entity in entities:
                self._insertModelToView(entity)

        else:
            self._insertModelToView(entities)

    def searchModel(self, columnIndex, columnValue):
        '''
        Searches for 'columnValue' in the column whose index is specified by 'columnIndex' in all 
        rows contained in the model.
        '''
        if isinstance(columnValue, QVariant):
            columnValue = str(columnValue.toString())

        if not isinstance(columnValue, str):
            columnValue = str(columnValue)

        columnValue = columnValue.strip()

        proxy = QSortFilterProxyModel(self)
        proxy.setSourceModel(self._tableModel)
        proxy.setFilterKeyColumn(columnIndex)
        proxy.setFilterFixedString(columnValue)
        #Will return model index containing the primary key.
        matchingIndex = proxy.mapToSource(proxy.index(0, 0))

        return matchingIndex

    def _modelInstanceFromIds(self, ids):
        '''
        Returns the model instance based the value of its primary key.
        '''
        dbHandler = self._dbModel()

        modelInstances = []

        for modelId in ids:
            modelObj = dbHandler.queryObject().filter(
                self._dbModel.id == modelId).first()
            if modelObj != None:
                modelInstances.append(modelObj)

        return modelInstances

    def _onRecordSelectedEntityBrowser(self, recid):
        '''
        Slot raised when the user has clicked the select button in the 'EntityBrowser' dialog
        to add the selected record to the table's list.
        Add the record to the foreign key table using the mappings.
        '''
        #Check if the record exists using the primary key so as to ensure only one instance is added to the table
        recIndex = getIndex(self._recordIds(), id)
        if recIndex != -1:
            return

        dbHandler = self._dbModel()
        modelObj = dbHandler.queryObject().filter(
            self._dbModel.id == recid).first()

        if modelObj != None:
            #Raise before entity added signal
            self.beforeEntityAdded.emit(modelObj)

            #Validate for unique value configurations
            for colIndex, replace in self._uniqueValueColIndices.items():
                attrName = self._dbModel.displayMapping().keys()[colIndex]
                attrValue = getattr(modelObj, attrName)

                #Check to see if there are cell formatters so that the correct value is searched for in the model
                if attrName in self._cellFormatters:
                    attrValue = self._cellFormatters[attrName](attrValue)

                matchingIndex = self.searchModel(colIndex, attrValue)

                if matchingIndex.isValid():
                    if replace:
                        existingId = matchingIndex.data()

                        #Delete old record from db
                        entityModels = self._modelInstanceFromIds([existingId])
                        if len(entityModels) > 0:
                            entityModels[0].delete()
                        self._removeRow(matchingIndex.row())

                        break

                    else:
                        #Break. Do not add item to the list.
                        return

            if not self._supportsLists and self._tableModel.rowCount() > 0:
                self._removeRow(0)
            self._insertModelToView(modelObj)

    def _insertModelToView(self, modelObj):
        '''
        Insert the given database model instance into the view.
        '''
        insertPosition = self._tableModel.rowCount()
        self._tableModel.insertRows(insertPosition, 1)

        for i, attr in enumerate(self._dbModel.displayMapping().keys()):
            propIndex = self._tableModel.index(insertPosition, i)
            attrVal = getattr(modelObj, 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)
            self._tableModel.setData(propIndex, attrVal)

        #Raise signal once entity has been inserted
        self.afterEntityAdded.emit(modelObj)

    def onAddEntity(self):
        '''
        Slot raised on selecting to add related entities that will be mapped to the primary
        database model instance.
        '''
        if self._entitySelector != None:
            entitySelector = self._entitySelector(self,
                                                  self._entitySelectorState)
            #Cascade cell formatters
            entitySelector.setCellFormatters(self._cellFormatters)
            self.connect(entitySelector, SIGNAL("recordSelected(int)"),
                         self._onRecordSelectedEntityBrowser)
            #self.connect(entitySelector, SIGNAL("destroyed(QObject *)"),self.onEntitySelectorDestroyed)

            retStatus = entitySelector.exec_()
            if retStatus == QDialog.Accepted:
                pass

        else:
            if self._notifBar != None:
                msg = QApplication.translate(
                    "ForeignKeyMapper", "Null instance of entity selector.")
                self._notifBar.clear()
                self._notifBar.insertErrorNotification(msg)