class ImportData(QWizard, Ui_frmImport): def __init__(self,parent=None): QWizard.__init__(self,parent) self.setupUi(self) self.curr_profile = current_profile() #Connect signals self.btnBrowseSource.clicked.connect(self.setSourceFile) self.lstDestTables.itemClicked.connect(self.destSelectChanged) self.btnSrcUp.clicked.connect(self.srcItemUp) self.btnSrcDown.clicked.connect(self.srcItemDown) self.btnSrcAll.clicked.connect(self.checkSrcItems) self.btnSrcNone.clicked.connect(self.uncheckSrcItems) self.btnDestUp.clicked.connect(self.targetItemUp) self.btnDestDown.clicked.connect(self.targetItemDown) self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools) self.chk_virtual.toggled.connect(self._on_load_virtual_columns) #Data Reader self.dataReader = None #Init self.registerFields() #Geometry columns self.geomcols = [] #Initialize value translators from definitions self._init_translators() #self._set_target_fields_stylesheet() def _init_translators(self): translator_menu = QMenu(self) self._trans_widget_mgr = TranslatorWidgetManager(self) self._trans_signal_mapper = QSignalMapper(self) for trans_name, config in ValueTranslatorConfig.translators.iteritems(): trans_action = QAction( u'{}...'.format(trans_name), translator_menu ) self._trans_signal_mapper.setMapping(trans_action, trans_name) trans_action.triggered.connect(self._trans_signal_mapper.map) translator_menu.addAction(trans_action) if len(translator_menu.actions()) == 0: self.btn_add_translator.setEnabled(False) else: self.btn_add_translator.setMenu(translator_menu) self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) self.btn_edit_translator.clicked.connect(self._on_edit_translator) self.btn_delete_translator.clicked.connect(self._on_delete_translator) def _load_translator_dialog(self, config_key): """ Load translator dialog. """ dest_column = self._selected_destination_column() src_column = self._selected_source_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: trans_config = ValueTranslatorConfig.translators.get(config_key, None) #Safety precaution if trans_config is None: return try: trans_dlg = trans_config.create( self, self._source_columns(), self.targetTab, dest_column, src_column ) except RuntimeError as re: QMessageBox.critical( self, QApplication.translate( 'ImportData', 'Value Translator' ), unicode(re) ) return self._handle_translator_dlg(dest_column, trans_dlg) def _handle_translator_dlg(self, key, dlg): if dlg.exec_() == QDialog.Accepted: self._trans_widget_mgr.add_widget(key, dlg) self._enable_disable_trans_tools() def _on_edit_translator(self): """ Slot to load the translator widget specific for the selected column for editing. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) self._handle_translator_dlg(dest_column, trans_dlg) def _on_delete_translator(self): """ Slot for deleting the translator widget for the selected column. """ dest_column = self._selected_destination_column() self._delete_translator(dest_column) def _delete_translator(self, destination_column): if not destination_column: return res = self._trans_widget_mgr.remove_translator_widget(destination_column) self._enable_disable_trans_tools() def _enable_disable_trans_tools(self, index=-1): """ Enable/disable appropriate value translator tools based on the selected column. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: self.btn_add_translator.setEnabled(True) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(True) self.btn_delete_translator.setEnabled(True) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) def _selected_destination_column(self): dest_field_item = self.lstTargetFields.currentItem() if dest_field_item is None: return "" else: return dest_field_item.text() def _selected_source_column(self): src_field_item = self.lstSrcFields.currentItem() if src_field_item is None: return "" else: return src_field_item.text() def _set_target_fields_stylesheet(self): self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected" " { selection-background-color: darkblue }") def registerFields(self): #Register wizard fields pgSource = self.page(0) pgSource.registerField("srcFile*",self.txtDataSource) pgSource.registerField("typeText",self.rbTextType) pgSource.registerField("typeSpatial",self.rbSpType) #Destination table configuration destConf = self.page(1) destConf.registerField("optAppend",self.rbAppend) destConf.registerField("optOverwrite",self.rbOverwrite) destConf.registerField("tabIndex*",self.lstDestTables) destConf.registerField("geomCol",self.geomClm,"currentText",SIGNAL("currentIndexChanged(int)")) def initializePage(self,pageid): #Re-implementation of wizard page initialization if pageid == 1: #Reference to checked listwidget item representing table name self.destCheckedItem=None self.geomClm.clear() if self.field("typeText"): self.loadTables("textual") self.geomClm.setEnabled(False) elif self.field("typeSpatial"): self.loadTables("spatial") self.geomClm.setEnabled(True) if pageid == 2: self.lstSrcFields.clear() self.lstTargetFields.clear() self.assignCols() self._enable_disable_trans_tools() def _source_columns(self): return self.dataReader.getFields() def assignCols(self): #Load source and target columns respectively srcCols = self._source_columns() for c in srcCols: srcItem = QListWidgetItem(c,self.lstSrcFields) srcItem.setCheckState(Qt.Unchecked) srcItem.setIcon(QIcon(":/plugins/stdm/images/icons/column.png")) self.lstSrcFields.addItem(srcItem) #Destination Columns tabIndex = int(self.field("tabIndex")) self.targetTab = self.destCheckedItem.text() targetCols = table_column_names(self.targetTab, False, True) #Remove geometry columns in the target columns list for gc in self.geomcols: colIndex = getIndex(targetCols,gc) if colIndex != -1: targetCols.remove(gc) #Remove 'id' column if there id_idx = getIndex(targetCols, 'id') if id_idx != -1: targetCols.remove('id') self._add_target_table_columns(targetCols) def _add_target_table_columns(self, items, style=False): for item in items: list_item = QListWidgetItem(item) if style: color = QColor(0, 128, 255) list_item.setTextColor(color) self.lstTargetFields.addItem(list_item) def _on_load_virtual_columns(self, state): """ Load/unload relationships in the list of destination table columns. """ virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab) if state: if len(virtual_columns) == 0: msg = QApplication.translate("ImportData", "There are no virtual columns for the specified table.") QMessageBox.warning( self, QApplication.translate( 'ImportData', 'Import Data' ), msg ) self.chk_virtual.setChecked(False) return self._add_target_table_columns(virtual_columns, True) else: self._remove_destination_table_fields(virtual_columns) def _remove_destination_table_fields(self, fields): """Remove the specified columns from the destination view.""" for f in fields: list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString) if len(list_items) > 0: list_item = list_items[0] row = self.lstTargetFields.row(list_item) rem_item = self.lstTargetFields.takeItem(row) del rem_item #Delete translator if already defined for the given column self._delete_translator(f) def loadGeomCols(self, table): #Load geometry columns based on the selected table self.geomcols = table_column_names(table, True, True) self.geomClm.clear() self.geomClm.addItems(self.geomcols) def loadTables(self, type): #Load textual or spatial tables self.lstDestTables.clear() tables = None if type == "textual": tables = profile_user_tables(self.curr_profile, False, True) elif type == "spatial": tables = profile_spatial_tables(self.curr_profile) if tables is not None: for t in tables: tabItem = QListWidgetItem(t,self.lstDestTables) tabItem.setCheckState(Qt.Unchecked) tabItem.setIcon(QIcon(":/plugins/stdm/images/icons/table.png")) self.lstDestTables.addItem(tabItem) def validateCurrentPage(self): #Validate the current page before proceeding to the next one validPage=True if not QFile.exists(unicode(self.field("srcFile"))): self.ErrorInfoMessage("The specified source file does not exist.") validPage = False else: if self.dataReader: self.dataReader.reset() self.dataReader = OGRReader(unicode(self.field("srcFile"))) if not self.dataReader.isValid(): self.ErrorInfoMessage("The source file could not be opened." "\nPlease check is the given file type " "is supported") validPage = False if self.currentId()==1: if self.destCheckedItem == None: self.ErrorInfoMessage("Please select the destination table.") validPage = False if self.currentId()==2: validPage = self.execImport() return validPage def setSourceFile(self): #Set the file path to the source file imageFilters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)" sourceFile = QFileDialog.getOpenFileName(self,"Select Source File",vectorFileDir(),imageFilters) if sourceFile != "": self.txtDataSource.setText(sourceFile) def getSrcDestPairs(self): #Return the matched source and destination columns srcDest = {} for l in range(self.lstTargetFields.count()): if l < self.lstSrcFields.count(): srcItem = self.lstSrcFields.item(l) if srcItem.checkState() == Qt.Checked: destItem = self.lstTargetFields.item(l) srcDest[srcItem.text()] = destItem.text() return srcDest def execImport(self): #Initiate the import process success = False matchCols = self.getSrcDestPairs() #Specify geometry column geom_column=None if self.field("typeSpatial"): geom_column = self.field("geomCol") # Ensure that user has selected at least one column if it is a # non-spatial table if len(matchCols) == 0: self.ErrorInfoMessage("Please select at least one source column.") return success value_translator_manager = self._trans_widget_mgr.translator_manager() # try: if self.field("optOverwrite"): entity = self.curr_profile.entity_by_name(self.targetTab) dependencies = entity.dependencies() view_dep = dependencies['views'] entity_dep = [e.name for e in entity.children()] entities_dep_str = ', '.join(entity_dep) views_dep_str = ', '.join(view_dep) if len(entity_dep) > 0 or len(view_dep) > 0: del_msg = QApplication.translate( 'ImportData', "Overwriting existing records will permanently \n" "remove records from other tables linked to the \n" "records. The following tables will be affected." "\n{}\n{}" "\nClick Yes to proceed importing or No to cancel.". format(entities_dep_str, views_dep_str) ) del_result = QMessageBox.critical( self, QApplication.translate( "ImportData", "Overwrite Import Data Warning" ), del_msg, QMessageBox.Yes | QMessageBox.No ) if del_result == QMessageBox.Yes: self.dataReader.featToDb( self.targetTab, matchCols, False, self, geom_column, translator_manager=value_translator_manager ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) self.InfoMessage( "All features have been imported successfully!") else: success = False else: self.dataReader.featToDb( self.targetTab, matchCols, True, self, geom_column, translator_manager=value_translator_manager ) self.InfoMessage( "All features have been imported successfully!" ) #Update directory info in the registry setVectorFileDir(self.field("srcFile")) success = True # except: # self.ErrorInfoMessage(unicode(sys.exc_info()[1])) return success def _clear_dest_table_selections(self, exclude=None): #Clears checked items in destination table list view if exclude is None: exclude = [] for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked and not item.text() in exclude: item.setCheckState(Qt.Unchecked) def destSelectChanged(self, item): """ Handler when a list widget item is clicked, clears previous selections """ if not self.destCheckedItem is None: if item.checkState() == Qt.Checked: self.destCheckedItem.setCheckState(Qt.Unchecked) else: self.destCheckedItem = None if item.checkState() == Qt.Checked: self.destCheckedItem = item #Ensure other selected items have been cleared self._clear_dest_table_selections(exclude=[item.text()]) #Load geometry columns if selection is a spatial table if self.field("typeSpatial"): self.loadGeomCols(item.text()) def syncRowSelection(self, srcList, destList): """ Sync the selection of an srcList item to the corresponding one in the destination column list. """ if (srcList.currentRow() + 1) <= destList.count(): destList.setCurrentRow(srcList.currentRow()) def sourceRowChanged(self): #Slot when the source list's current row changes self.syncRowSelection(self.lstSrcFields,self.lstTargetFields) def destRowChanged(self): #Slot when the destination list's current row changes self.syncRowSelection(self.lstTargetFields, self.lstSrcFields) def itemUp(self, listWidget): #Moves the selected item in the list widget one level up curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex - 1, curItem) listWidget.setCurrentRow(curIndex - 1) def itemDown(self, listWidget): #Moves the selected item in the list widget one level down curIndex=listWidget.currentRow() curItem=listWidget.takeItem(curIndex) listWidget.insertItem(curIndex + 1,curItem) listWidget.setCurrentRow(curIndex + 1) def checkAllItems(self, listWidget, state): #Checks all items in the list widget for l in range(listWidget.count()): item=listWidget.item(l) if state: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def checkSrcItems(self): #Slot for checking all source table columns self.checkAllItems(self.lstSrcFields, True) def uncheckSrcItems(self): #Slot for unchecking all source table columns self.checkAllItems(self.lstSrcFields, False) def srcItemUp(self): #Slot for moving source list item up self.itemUp(self.lstSrcFields) def srcItemDown(self): #Slot for moving source list item down self.itemDown(self.lstSrcFields) def targetItemUp(self): #Slot for moving target item up self.itemUp(self.lstTargetFields) def targetItemDown(self): #Slot for moving target item down self.itemDown(self.lstTargetFields) def keyPressEvent(self,e): """ Override method for preventing the dialog from closing itself when the escape key is hit """ if e.key() == Qt.Key_Escape: pass def InfoMessage(self, message): #Information message box msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(message) msg.exec_() def ErrorInfoMessage(self, message): #Error Message Box msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(message) msg.exec_() # # def drag_drop_handler(self, event): # if event.source() == self: # rows = set([mi.row() for mi in self.selectedIndexes()]) # targetRow = self.indexAt(event.pos()).row() # rows.discard(targetRow) # rows = sorted(rows) # if not rows: # return # if targetRow == -1: # targetRow = self.rowCount() # for _ in range(len(rows)): # self.insertRow(targetRow) # rowMapping = dict() # Src row to target row. # for idx, row in enumerate(rows): # if row < targetRow: # rowMapping[row] = targetRow + idx # else: # rowMapping[row + len(rows)] = targetRow + idx # colCount = self.columnCount() # for srcRow, tgtRow in sorted(rowMapping.iteritems()): # for col in range(0, colCount): # self.setItem(tgtRow, col, self.takeItem(srcRow, col)) # for row in reversed(sorted(rowMapping.iterkeys())): # self.removeRow(row) # event.accept() # return def drag_move(self, e): e.accept()
class ImportData(QWizard, Ui_frmImport): def __init__(self,parent=None): QWizard.__init__(self,parent) self.setupUi(self) self.curr_profile = current_profile() #Connect signals self.btnBrowseSource.clicked.connect(self.setSourceFile) self.lstDestTables.itemClicked.connect(self.destSelectChanged) self.btnSrcUp.clicked.connect(self.srcItemUp) self.btnSrcDown.clicked.connect(self.srcItemDown) self.btnSrcAll.clicked.connect(self.checkSrcItems) self.btnSrcNone.clicked.connect(self.uncheckSrcItems) self.btnDestUp.clicked.connect(self.targetItemUp) self.btnDestDown.clicked.connect(self.targetItemDown) self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools) self.chk_virtual.toggled.connect(self._on_load_virtual_columns) #Data Reader self.dataReader = None #Init self.registerFields() #Geometry columns self.geomcols = [] #Initialize value translators from definitions self._init_translators() #self._set_target_fields_stylesheet() def _init_translators(self): translator_menu = QMenu(self) self._trans_widget_mgr = TranslatorWidgetManager(self) self._trans_signal_mapper = QSignalMapper(self) for trans_name, config in ValueTranslatorConfig.translators.iteritems(): trans_action = QAction( u'{}...'.format(trans_name), translator_menu ) self._trans_signal_mapper.setMapping(trans_action, trans_name) trans_action.triggered.connect(self._trans_signal_mapper.map) translator_menu.addAction(trans_action) if len(translator_menu.actions()) == 0: self.btn_add_translator.setEnabled(False) else: self.btn_add_translator.setMenu(translator_menu) self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) self.btn_edit_translator.clicked.connect(self._on_edit_translator) self.btn_delete_translator.clicked.connect(self._on_delete_translator) def _load_translator_dialog(self, config_key): """ Load translator dialog. """ dest_column = self._selected_destination_column() src_column = self._selected_source_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: trans_config = ValueTranslatorConfig.translators.get(config_key, None) #Safety precaution if trans_config is None: return try: trans_dlg = trans_config.create( self, self._source_columns(), self.targetTab, dest_column, src_column ) except RuntimeError as re: QMessageBox.critical( self, QApplication.translate( 'ImportData', 'Value Translator' ), unicode(re) ) return self._handle_translator_dlg(dest_column, trans_dlg) def _handle_translator_dlg(self, key, dlg): if dlg.exec_() == QDialog.Accepted: self._trans_widget_mgr.add_widget(key, dlg) self._enable_disable_trans_tools() def _on_edit_translator(self): """ Slot to load the translator widget specific for the selected column for editing. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) self._handle_translator_dlg(dest_column, trans_dlg) def _on_delete_translator(self): """ Slot for deleting the translator widget for the selected column. """ dest_column = self._selected_destination_column() self._delete_translator(dest_column) def _delete_translator(self, destination_column): if not destination_column: return res = self._trans_widget_mgr.remove_translator_widget(destination_column) self._enable_disable_trans_tools() def _enable_disable_trans_tools(self, index=-1): """ Enable/disable appropriate value translator tools based on the selected column. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: self.btn_add_translator.setEnabled(True) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(True) self.btn_delete_translator.setEnabled(True) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) def _selected_destination_column(self): dest_field_item = self.lstTargetFields.currentItem() if dest_field_item is None: return "" else: return dest_field_item.text() def _selected_source_column(self): src_field_item = self.lstSrcFields.currentItem() if src_field_item is None: return "" else: return src_field_item.text() def _set_target_fields_stylesheet(self): self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected" " { selection-background-color: darkblue }") def registerFields(self): #Register wizard fields pgSource = self.page(0) pgSource.registerField("srcFile*",self.txtDataSource) pgSource.registerField("typeText",self.rbTextType) pgSource.registerField("typeSpatial",self.rbSpType) #Destination table configuration destConf = self.page(1) destConf.registerField("optAppend",self.rbAppend) destConf.registerField("optOverwrite",self.rbOverwrite) destConf.registerField("tabIndex*",self.lstDestTables) destConf.registerField("geomCol",self.geomClm,"currentText",SIGNAL("currentIndexChanged(int)")) def initializePage(self,pageid): #Re-implementation of wizard page initialization if pageid == 1: #Reference to checked listwidget item representing table name self.destCheckedItem=None self.geomClm.clear() if self.field("typeText"): self.loadTables("textual") self.geomClm.setEnabled(False) elif self.field("typeSpatial"): self.loadTables("spatial") self.geomClm.setEnabled(True) if pageid == 2: self.lstSrcFields.clear() self.lstTargetFields.clear() self.assignCols() self._enable_disable_trans_tools() def _source_columns(self): return self.dataReader.getFields() def assignCols(self): #Load source and target columns respectively srcCols = self._source_columns() for c in srcCols: srcItem = QListWidgetItem(c,self.lstSrcFields) srcItem.setCheckState(Qt.Unchecked) srcItem.setIcon(QIcon(":/plugins/stdm/images/icons/column.png")) self.lstSrcFields.addItem(srcItem) #Destination Columns tabIndex = int(self.field("tabIndex")) self.targetTab = self.destCheckedItem.text() targetCols = table_column_names(self.targetTab, False, True) #Remove geometry columns in the target columns list for gc in self.geomcols: colIndex = getIndex(targetCols,gc) if colIndex != -1: targetCols.remove(gc) #Remove 'id' column if there id_idx = getIndex(targetCols, 'id') if id_idx != -1: targetCols.remove('id') self._add_target_table_columns(targetCols) def _add_target_table_columns(self, items, style=False): for item in items: list_item = QListWidgetItem(item) if style: color = QColor(0, 128, 255) list_item.setTextColor(color) self.lstTargetFields.addItem(list_item) def _on_load_virtual_columns(self, state): """ Load/unload relationships in the list of destination table columns. """ virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab) if state: if len(virtual_columns) == 0: msg = QApplication.translate("ImportData", "There are no virtual columns for the specified table.") QMessageBox.warning( self, QApplication.translate( 'ImportData', 'Import Data' ), msg ) self.chk_virtual.setChecked(False) return self._add_target_table_columns(virtual_columns, True) else: self._remove_destination_table_fields(virtual_columns) def _remove_destination_table_fields(self, fields): """Remove the specified columns from the destination view.""" for f in fields: list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString) if len(list_items) > 0: list_item = list_items[0] row = self.lstTargetFields.row(list_item) rem_item = self.lstTargetFields.takeItem(row) del rem_item #Delete translator if already defined for the given column self._delete_translator(f) def loadGeomCols(self, table): #Load geometry columns based on the selected table self.geomcols = table_column_names(table, True, True) self.geomClm.clear() self.geomClm.addItems(self.geomcols) def loadTables(self, type): #Load textual or spatial tables self.lstDestTables.clear() tables = None if type == "textual": tables = profile_user_tables(self.curr_profile, False, True) elif type == "spatial": tables = profile_spatial_tables(self.curr_profile) if tables is not None: for t in tables: tabItem = QListWidgetItem(t,self.lstDestTables) tabItem.setCheckState(Qt.Unchecked) tabItem.setIcon(QIcon(":/plugins/stdm/images/icons/table.png")) self.lstDestTables.addItem(tabItem) def validateCurrentPage(self): #Validate the current page before proceeding to the next one validPage=True if not QFile.exists(unicode(self.field("srcFile"))): self.ErrorInfoMessage("The specified source file does not exist.") validPage = False else: if self.dataReader: self.dataReader.reset() self.dataReader = OGRReader(unicode(self.field("srcFile"))) if not self.dataReader.isValid(): self.ErrorInfoMessage("The source file could not be opened." "\nPlease check is the given file type " "is supported") validPage = False if self.currentId()==1: if self.destCheckedItem == None: self.ErrorInfoMessage("Please select the destination table.") validPage = False if self.currentId()==2: validPage = self.execImport() return validPage def setSourceFile(self): #Set the file path to the source file imageFilters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)" sourceFile = QFileDialog.getOpenFileName(self,"Select Source File",vectorFileDir(),imageFilters) if sourceFile != "": self.txtDataSource.setText(sourceFile) def getSrcDestPairs(self): #Return the matched source and destination columns srcDest = {} for l in range(self.lstTargetFields.count()): if l < self.lstSrcFields.count(): srcItem = self.lstSrcFields.item(l) if srcItem.checkState() == Qt.Checked: destItem = self.lstTargetFields.item(l) srcDest[srcItem.text()] = destItem.text() return srcDest def execImport(self): #Initiate the import process success = False matchCols = self.getSrcDestPairs() #Specify geometry column geom_column=None if self.field("typeSpatial"): geom_column = self.field("geomCol") # Ensure that user has selected at least one column if it is a # non-spatial table if len(matchCols) == 0: self.ErrorInfoMessage("Please select at least one source column.") return success value_translator_manager = self._trans_widget_mgr.translator_manager() # try: if self.field("optOverwrite"): entity = self.curr_profile.entity_by_name(self.targetTab) dependencies = entity.dependencies() view_dep = dependencies['views'] entity_dep = [e.name for e in entity.children()] entities_dep_str = ', '.join(entity_dep) views_dep_str = ', '.join(view_dep) if len(entity_dep) > 0 or len(view_dep) > 0: del_msg = QApplication.translate( 'ImportData', "Overwriting existing records will permanently \n" "remove records from other tables linked to the \n" "records. The following tables will be affected." "\n{}\n{}" "\nClick Yes to proceed importing or No to cancel.". format(entities_dep_str, views_dep_str) ) del_result = QMessageBox.critical( self, QApplication.translate( "ImportData", "Overwrite Import Data Warning" ), del_msg, QMessageBox.Yes | QMessageBox.No ) if del_result == QMessageBox.Yes: self.dataReader.featToDb( self.targetTab, matchCols, False, self, geom_column, translator_manager=value_translator_manager ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) self.InfoMessage( "All features have been imported successfully!" ) success = True else: success = False else: self.dataReader.featToDb( self.targetTab, matchCols, True, self, geom_column, translator_manager=value_translator_manager ) self.InfoMessage( "All features have been imported successfully!" ) #Update directory info in the registry setVectorFileDir(self.field("srcFile")) success = True # except: # self.ErrorInfoMessage(unicode(sys.exc_info()[1])) return success def _clear_dest_table_selections(self, exclude=None): #Clears checked items in destination table list view if exclude is None: exclude = [] for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked and not item.text() in exclude: item.setCheckState(Qt.Unchecked) def destSelectChanged(self, item): """ Handler when a list widget item is clicked, clears previous selections """ if not self.destCheckedItem is None: if item.checkState() == Qt.Checked: self.destCheckedItem.setCheckState(Qt.Unchecked) else: self.destCheckedItem = None if item.checkState() == Qt.Checked: self.destCheckedItem = item #Ensure other selected items have been cleared self._clear_dest_table_selections(exclude=[item.text()]) #Load geometry columns if selection is a spatial table if self.field("typeSpatial"): self.loadGeomCols(item.text()) def syncRowSelection(self, srcList, destList): """ Sync the selection of an srcList item to the corresponding one in the destination column list. """ if (srcList.currentRow() + 1) <= destList.count(): destList.setCurrentRow(srcList.currentRow()) def sourceRowChanged(self): #Slot when the source list's current row changes self.syncRowSelection(self.lstSrcFields,self.lstTargetFields) def destRowChanged(self): #Slot when the destination list's current row changes self.syncRowSelection(self.lstTargetFields, self.lstSrcFields) def itemUp(self, listWidget): #Moves the selected item in the list widget one level up curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex - 1, curItem) listWidget.setCurrentRow(curIndex - 1) def itemDown(self, listWidget): #Moves the selected item in the list widget one level down curIndex=listWidget.currentRow() curItem=listWidget.takeItem(curIndex) listWidget.insertItem(curIndex + 1,curItem) listWidget.setCurrentRow(curIndex + 1) def checkAllItems(self, listWidget, state): #Checks all items in the list widget for l in range(listWidget.count()): item=listWidget.item(l) if state: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def checkSrcItems(self): #Slot for checking all source table columns self.checkAllItems(self.lstSrcFields, True) def uncheckSrcItems(self): #Slot for unchecking all source table columns self.checkAllItems(self.lstSrcFields, False) def srcItemUp(self): #Slot for moving source list item up self.itemUp(self.lstSrcFields) def srcItemDown(self): #Slot for moving source list item down self.itemDown(self.lstSrcFields) def targetItemUp(self): #Slot for moving target item up self.itemUp(self.lstTargetFields) def targetItemDown(self): #Slot for moving target item down self.itemDown(self.lstTargetFields) def keyPressEvent(self,e): """ Override method for preventing the dialog from closing itself when the escape key is hit """ if e.key() == Qt.Key_Escape: pass def InfoMessage(self, message): #Information message box msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(message) msg.exec_() def ErrorInfoMessage(self, message): #Error Message Box msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(message) msg.exec_()
class ImportData(WIDGET, BASE): ROLE_TABLE_NAME = Qt.UserRole + 1 def __init__(self, parent=None): QWizard.__init__(self, parent) self.setupUi(self) self.btnSrcUp.setIcon(GuiUtils.get_icon('up.png')) self.btnSrcDown.setIcon(GuiUtils.get_icon('down.png')) self.btn_add_translator.setIcon(GuiUtils.get_icon('add.png')) self.btn_edit_translator.setIcon(GuiUtils.get_icon('edit.png')) self.btn_delete_translator.setIcon(GuiUtils.get_icon('remove.png')) self.btnDestUp.setIcon(GuiUtils.get_icon('up.png')) self.btnDestDown.setIcon(GuiUtils.get_icon('down.png')) self.curr_profile = current_profile() # Connect signals self.btnBrowseSource.clicked.connect(self.setSourceFile) self.lstDestTables.itemClicked.connect(self.destSelectChanged) self.btnSrcUp.clicked.connect(self.srcItemUp) self.btnSrcDown.clicked.connect(self.srcItemDown) self.btnSrcAll.clicked.connect(self.checkSrcItems) self.btnSrcNone.clicked.connect(self.uncheckSrcItems) self.btnDestUp.clicked.connect(self.targetItemUp) self.btnDestDown.clicked.connect(self.targetItemDown) self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools) self.chk_virtual.toggled.connect(self._on_load_virtual_columns) self.button_save_configuration.clicked.connect(self._save_column_mapping) self.button_load_configuration.clicked.connect(self._load_column_mapping) self.targetTab = '' self.import_was_successful = False self.restored_config = {} # Data Reader self.dataReader = None # Init self.registerFields() # Geometry columns self.geom_cols = [] # Initialize value translators from definitions self._init_translators() # self._set_target_fields_stylesheet() if os.path.exists(BACKUP_IMPORT_CONFIG_PATH): self._restore_previous_configuration() def closeEvent(self, event): self._save_if_unfinished() super().closeEvent(event) def reject(self): self._save_if_unfinished() super().reject() def accept(self): super().accept() self._save_if_unfinished() def _save_if_unfinished(self): """ If an unfinished import is in progress, then automatically save the settings to a hidden file """ if self.import_was_successful: return if not self.field("srcFile"): # user hasn't even started the process by picking a file, so we've nothing of value to save... return current_config = self._get_column_config() with open(BACKUP_IMPORT_CONFIG_PATH, 'wt') as f: f.write(json.dumps(current_config, indent=4)) def _restore_previous_configuration(self): """ Loads the previously unfinished configuration """ with open(BACKUP_IMPORT_CONFIG_PATH, 'rt') as f: config = json.loads(''.join(f.readlines())) os.remove(BACKUP_IMPORT_CONFIG_PATH) if not config: return if QMessageBox.question(self, self.tr('Import Data'), self.tr( 'A previously incomplete or unsuccessful import was detected. Would you like to restore the previous configuration and retry?'), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) != QMessageBox.Yes: return self.restored_config = config self.txtDataSource.setText(self.restored_config.get('source_file')) self.rbTextType.setChecked(self.restored_config.get('is_text', True)) self.rbSpType.setChecked(self.restored_config.get('is_spatial', False)) def _init_translators(self): translator_menu = QMenu(self) self._trans_widget_mgr = TranslatorWidgetManager(self) self._trans_signal_mapper = QSignalMapper(self) for trans_name, config in ValueTranslatorConfig.translators.items(): trans_action = QAction('{}...'.format(trans_name), translator_menu ) self._trans_signal_mapper.setMapping(trans_action, trans_name) trans_action.triggered.connect(self._trans_signal_mapper.map) translator_menu.addAction(trans_action) if len(translator_menu.actions()) == 0: self.btn_add_translator.setEnabled(False) else: self.btn_add_translator.setMenu(translator_menu) self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) self.btn_edit_translator.clicked.connect(self._on_edit_translator) self.btn_delete_translator.clicked.connect(self._on_delete_translator) def _load_translator_dialog(self, config_key): """ Load translator dialog. """ dest_column = self._selected_destination_column() src_column = self._selected_source_column() if dest_column: # Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: trans_config = ValueTranslatorConfig.translators.get(config_key, None) # Safety precaution if trans_config is None: return try: trans_dlg = trans_config.create( self, self._source_columns(), self.targetTab, dest_column, src_column ) except RuntimeError as re: QMessageBox.critical( self, QApplication.translate( 'ImportData', 'Value Translator' ), str(re) ) return self._handle_translator_dlg(dest_column, trans_dlg) def _handle_translator_dlg(self, key, dlg): if dlg.exec_() == QDialog.Accepted: self._trans_widget_mgr.add_widget(key, dlg) self._enable_disable_trans_tools() def _on_edit_translator(self): """ Slot to load the translator widget specific for the selected column for editing. """ dest_column = self._selected_destination_column() if dest_column: # Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) self._handle_translator_dlg(dest_column, trans_dlg) def _on_delete_translator(self): """ Slot for deleting the translator widget for the selected column. """ dest_column = self._selected_destination_column() self._delete_translator(dest_column) def _delete_translator(self, destination_column): if not destination_column: return _ = self._trans_widget_mgr.remove_translator_widget(destination_column) self._enable_disable_trans_tools() def _enable_disable_trans_tools(self, index=-1): """ Enable/disable appropriate value translator tools based on the selected column. """ dest_column = self._selected_destination_column() if dest_column: # Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: self.btn_add_translator.setEnabled(True) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(True) self.btn_delete_translator.setEnabled(True) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) def _selected_destination_column(self): dest_field_item = self.lstTargetFields.currentItem() if dest_field_item is None: return "" else: return dest_field_item.text() def _selected_source_column(self): src_field_item = self.lstSrcFields.currentItem() if src_field_item is None: return "" else: return src_field_item.text() def _set_target_fields_stylesheet(self): self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected" " { selection-background-color: darkblue }") def registerFields(self): # Register wizard fields pgSource = self.page(0) pgSource.registerField("srcFile*", self.txtDataSource) pgSource.registerField("typeText", self.rbTextType) pgSource.registerField("typeSpatial", self.rbSpType) # Destination table configuration destConf = self.page(1) destConf.registerField("optAppend", self.rbAppend) destConf.registerField("optOverwrite", self.rbOverwrite) destConf.registerField("tabIndex*", self.lstDestTables) destConf.registerField("geomCol", self.geomClm, "currentText", QComboBox.currentIndexChanged[int]) def initializePage(self, page_id): # Re-implementation of wizard page initialization if page_id == 1: # Reference to checked list widget item representing table name self.geomClm.clear() if self.field("typeText"): self.load_tables_of_type("textual", self.restored_config.get('dest_table')) self.geomClm.setEnabled(False) elif self.field("typeSpatial"): self.load_tables_of_type("spatial", self.restored_config.get('dest_table')) if self.selected_destination_table(): self.loadGeomCols(self.selected_destination_table()) self.geomClm.setEnabled(True) if self.restored_config: if self.restored_config.get('overwrite', False): self.rbOverwrite.setChecked(True) else: self.rbAppend.setChecked(True) if page_id == 2: self.lstSrcFields.clear() self.lstTargetFields.clear() self.assignCols() if self.restored_config and self.selected_destination_table() == self.restored_config.get('dest_table'): self._restore_column_config(self.restored_config) self._enable_disable_trans_tools() def _source_columns(self): return self.dataReader.getFields() @staticmethod def format_name_for_matching(name: str) -> str: """ Returns a column name formatted for tolerant matching, e.g. we ignore case, _ characters, etc """ return name.strip().lower().replace(' ', '').replace('_', '').replace('-', '') @staticmethod def names_are_matching(name1: str, name2: str) -> bool: """ Returns True if the specified column name pairs should be considered a tolerant match """ return ImportData.format_name_for_matching(name1) == ImportData.format_name_for_matching(name2) def selected_destination_table(self) -> Optional[str]: """ Returns the selected (checked) destination table """ for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked: return item.data(ImportData.ROLE_TABLE_NAME) return None def assignCols(self): # Load source and target columns respectively source_columns = self._source_columns() # Destination Columns self.targetTab = self.selected_destination_table() target_columns = table_column_names(self.targetTab, False, True) # Remove geometry columns and 'id' column in the target columns list target_columns = [c for c in target_columns if c not in self.geom_cols and c != 'id'] # now synchronize the lists, as much as possible # this consists of moving columns with matching names in the source and target lists to the same # placement at the top of the lists, and filtering out lists of remaining unmatched columns matched_source_columns = [] unmatched_source_columns = source_columns[:] matched_target_columns = [] unmatched_target_columns = target_columns[:] for source in source_columns: for target in unmatched_target_columns: if ImportData.names_are_matching(source, target): matched_source_columns.append(source) unmatched_source_columns = [c for c in unmatched_source_columns if c != source] matched_target_columns.append(target) unmatched_target_columns = [c for c in unmatched_target_columns if c != target] break # any matching columns get added to the start of the lists, and unmatched get added # to the end of the list for c in matched_source_columns + unmatched_source_columns: src_item = QListWidgetItem(c, self.lstSrcFields) # automatically check any columns we could match src_item.setCheckState(Qt.Checked if c in matched_source_columns else Qt.Unchecked) src_item.setIcon(GuiUtils.get_icon("column.png")) self.lstSrcFields.addItem(src_item) self._add_target_table_columns(matched_target_columns + unmatched_target_columns) def _add_target_table_columns(self, items, style=False): for item in items: list_item = QListWidgetItem(item) if style: color = QColor(0, 128, 255) list_item.setTextColor(color) self.lstTargetFields.addItem(list_item) def _on_load_virtual_columns(self, state): """ Load/unload relationships in the list of destination table columns. """ virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab) if state: if len(virtual_columns) == 0: msg = QApplication.translate("ImportData", "There are no virtual columns for the specified table.") QMessageBox.warning( self, QApplication.translate( 'ImportData', 'Import Data' ), msg ) self.chk_virtual.setChecked(False) return self._add_target_table_columns(virtual_columns, True) else: self._remove_destination_table_fields(virtual_columns) def _remove_destination_table_fields(self, fields): """Remove the specified columns from the destination view.""" for f in fields: list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString) if len(list_items) > 0: list_item = list_items[0] row = self.lstTargetFields.row(list_item) rem_item = self.lstTargetFields.takeItem(row) del rem_item # Delete translator if already defined for the given column self._delete_translator(f) def loadGeomCols(self, table): # Load geometry columns based on the selected table self.geom_cols = table_column_names(table, True, True) self.geomClm.clear() self.geomClm.addItems(self.geom_cols) if self.restored_config.get('geom_column'): self.geomClm.setCurrentIndex(self.geomClm.findText(self.restored_config['geom_column'])) def load_tables_of_type(self, type: str, initial_selection: Optional[str] = None): """ Load textual or spatial tables If initial_selection is specified then that table will be initially checked """ self.lstDestTables.clear() tables = None if type == "textual": tables = profile_user_tables(self.curr_profile, False, True, include_read_only=False) elif type == "spatial": tables = profile_spatial_tables(self.curr_profile, include_read_only=False) if tables is not None: for table_name, table_label in tables.items(): table_item = QListWidgetItem(table_label, self.lstDestTables) table_item.setData(ImportData.ROLE_TABLE_NAME, table_name) if initial_selection: table_item.setCheckState( Qt.Checked if ImportData.names_are_matching(table_name, initial_selection) else Qt.Unchecked) else: table_item.setCheckState(Qt.Unchecked) table_item.setIcon(GuiUtils.get_icon("table.png")) self.lstDestTables.addItem(table_item) def validateCurrentPage(self): # Validate the current page before proceeding to the next one validPage = True if not QFile.exists(str(self.field("srcFile"))): self.show_error_message("The specified source file does not exist.") validPage = False else: if self.dataReader: self.dataReader.reset() self.dataReader = OGRReader(str(self.field("srcFile"))) if not self.dataReader.isValid(): self.show_error_message("The source file could not be opened." "\nPlease check is the given file type " "is supported") validPage = False if self.currentId() == 1: if self.selected_destination_table() is None: self.show_error_message("Please select the destination table.") validPage = False if self.currentId() == 2: self.import_was_successful = self.execImport() validPage = self.import_was_successful return validPage def setSourceFile(self): # Set the file path to the source file filters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)" sourceFile, _ = QFileDialog.getOpenFileName(self, "Select Source File", vectorFileDir(), filters) if sourceFile: self.txtDataSource.setText(sourceFile) def get_source_dest_pairs(self) -> dict: """ Builds a dictionary of source field to destination field name """ mapping = {} for target_row in range(self.lstTargetFields.count()): if target_row < self.lstSrcFields.count(): source_item = self.lstSrcFields.item(target_row) if source_item.checkState() == Qt.Checked: dest_item = self.lstTargetFields.item(target_row) mapping[source_item.text()] = dest_item.text() return mapping def set_source_dest_pairs(self, mapping: dict): """ Sets the source to destination pairs for fields to match Any existing mapping will be cleared """ self.uncheckSrcItems() index = 0 for target_source, target_dest in mapping.items(): # move source row up for source_row in range(self.lstSrcFields.count()): if ImportData.names_are_matching(target_source, self.lstSrcFields.item(source_row).text()): source_item = self.lstSrcFields.takeItem(source_row) self.lstSrcFields.insertItem(index, source_item) source_item.setCheckState(Qt.Checked) # move target row up for dest_row in range(self.lstTargetFields.count()): if ImportData.names_are_matching(target_dest, self.lstTargetFields.item(dest_row).text()): dest_item = self.lstTargetFields.takeItem(dest_row) self.lstTargetFields.insertItem(index, dest_item) index += 1 def execImport(self): # Initiate the import process success = False matchCols = self.get_source_dest_pairs() # Specify geometry column geom_column = None if self.field("typeSpatial"): geom_column = self.field("geomCol") # Ensure that user has selected at least one column if it is a # non-spatial table if len(matchCols) == 0: self.show_error_message("Please select at least one source column.") return success value_translator_manager = self._trans_widget_mgr.translator_manager() try: if self.field("optOverwrite"): entity = self.curr_profile.entity_by_name(self.targetTab) dependencies = entity.dependencies() view_dep = dependencies['views'] entity_dep = [e.name for e in entity.children()] entities_dep_str = ', '.join(entity_dep) views_dep_str = ', '.join(view_dep) if len(entity_dep) > 0 or len(view_dep) > 0: del_msg = QApplication.translate( 'ImportData', "Overwriting existing records will permanently \n" "remove records from other tables linked to the \n" "records. The following tables will be affected." "\n{}\n{}" "\nClick Yes to proceed importing or No to cancel.". format(entities_dep_str, views_dep_str) ) del_result = QMessageBox.critical( self, QApplication.translate( "ImportData", "Overwrite Import Data Warning" ), del_msg, QMessageBox.Yes | QMessageBox.No ) if del_result == QMessageBox.Yes: self.dataReader.featToDb( self.targetTab, matchCols, False, self, geom_column, translator_manager=value_translator_manager ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) self.show_info_message( "All features have been imported successfully!" ) success = True else: success = False else: self.dataReader.featToDb( self.targetTab, matchCols, True, self, geom_column, translator_manager=value_translator_manager ) self.show_info_message( "All features have been imported successfully!" ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) success = True except ImportFeatureException as e: self.show_error_message(str(e)) return success def _clear_dest_table_selections(self, exclude=None): # Clears checked items in destination table list view if exclude is None: exclude = [] for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked and not item.data(ImportData.ROLE_TABLE_NAME) in exclude: item.setCheckState(Qt.Unchecked) def destSelectChanged(self, item): """ Handler when a list widget item is clicked, clears previous selections """ if item.checkState() == Qt.Checked: selected_table = item.data(ImportData.ROLE_TABLE_NAME) # Ensure other selected items have been cleared self._clear_dest_table_selections(exclude=[selected_table]) # Load geometry columns if selection is a spatial table if self.field("typeSpatial"): self.loadGeomCols(selected_table) def syncRowSelection(self, srcList, destList): """ Sync the selection of an srcList item to the corresponding one in the destination column list. """ if (srcList.currentRow() + 1) <= destList.count(): destList.setCurrentRow(srcList.currentRow()) def sourceRowChanged(self): # Slot when the source list's current row changes self.syncRowSelection(self.lstSrcFields, self.lstTargetFields) def destRowChanged(self): # Slot when the destination list's current row changes self.syncRowSelection(self.lstTargetFields, self.lstSrcFields) def itemUp(self, listWidget): # Moves the selected item in the list widget one level up curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex - 1, curItem) listWidget.setCurrentRow(curIndex - 1) def itemDown(self, listWidget): # Moves the selected item in the list widget one level down curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex + 1, curItem) listWidget.setCurrentRow(curIndex + 1) def checkAllItems(self, listWidget, state): # Checks all items in the list widget for l in range(listWidget.count()): item = listWidget.item(l) if state: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def checkSrcItems(self): # Slot for checking all source table columns self.checkAllItems(self.lstSrcFields, True) def uncheckSrcItems(self): # Slot for unchecking all source table columns self.checkAllItems(self.lstSrcFields, False) def srcItemUp(self): # Slot for moving source list item up self.itemUp(self.lstSrcFields) def srcItemDown(self): # Slot for moving source list item down self.itemDown(self.lstSrcFields) def targetItemUp(self): # Slot for moving target item up self.itemUp(self.lstTargetFields) def targetItemDown(self): # Slot for moving target item down self.itemDown(self.lstTargetFields) def keyPressEvent(self, e): """ Override method for preventing the dialog from closing itself when the escape key is hit """ if e.key() == Qt.Key_Escape: pass def show_info_message(self, message): # Information message box msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(message) msg.exec_() def show_error_message(self, message): # Error Message Box msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(message) msg.exec_() def _save_column_mapping(self): """ Exports the current column mapping to a JSON definition file """ config = RegistryConfig() prev_folder = config.read(["LastImportConfigFolder"]).get("LastImportConfigFolder") if not prev_folder: prev_folder = QDir.homePath() dest_path, _ = QFileDialog.getSaveFileName(self, self.tr("Save Configuration"), prev_folder, "{0} (*.json)".format(self.tr('Configuration files'))) if not dest_path: return dest_path = QgsFileUtils.ensureFileNameHasExtension(dest_path, ['.json']) config.write({"LastImportConfigFolder": QFileInfo(dest_path).path()}) with open(dest_path, 'wt') as f: f.write(json.dumps(self._get_column_config(), indent=4)) def _get_column_config(self) -> dict: """ Returns a dictionary encapsulating the column mapping configuration """ return { 'column_mapping': self.get_source_dest_pairs(), 'source_file': str(self.field("srcFile")), 'is_text': bool(self.field("typeText")), 'is_spatial': bool(self.field("typeSpatial")), 'geom_column': self.field("geomCol") or None, 'overwrite': bool(self.field("optOverwrite")), 'dest_table': self.targetTab or '' } def _load_column_mapping(self): """ Imports the current column mapping from a JSON definition file """ config = RegistryConfig() prev_folder = config.read(["LastImportConfigFolder"]).get("LastImportConfigFolder") if not prev_folder: prev_folder = QDir.homePath() source_path, _ = QFileDialog.getOpenFileName(self, self.tr("Load Configuration"), prev_folder, "{0} (*.json)".format(self.tr('Configuration files'))) if not source_path: return config.write({"LastImportConfigFolder": QFileInfo(source_path).path()}) with open(source_path, 'rt') as f: imported_config = json.loads(''.join(f.readlines())) self._restore_column_config(imported_config) def _restore_column_config(self, config: dict): """ Restores a previously saved column configuration """ column_mapping = config.get('column_mapping', {}) # test validity -- ensure that all the referenced source and destination columns # from the saved file are available for saved_source, saved_dest in column_mapping.items(): for source_row in range(self.lstSrcFields.count()): if ImportData.names_are_matching(saved_source, self.lstSrcFields.item(source_row).text()): break else: self.show_error_message(self.tr('Source column {} not found in dataset'.format(saved_source))) return for destination_row in range(self.lstTargetFields.count()): if ImportData.names_are_matching(saved_dest, self.lstTargetFields.item(destination_row).text()): break else: self.show_error_message(self.tr('Destination column {} not found in dataset'.format(saved_dest))) return self.set_source_dest_pairs(column_mapping)