mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-27 00:33:48 -05:00
PostGIS provider has an option to lowercase field names. This options is available for user in QGIS algorithm ImportIntoPostGIS and not in DB Mananger. This commit fix it.
385 lines
15 KiB
Python
385 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
/***************************************************************************
|
|
Name : DB Manager
|
|
Description : Database manager plugin for QGIS
|
|
Date : Oct 13, 2011
|
|
copyright : (C) 2011 by Giuseppe Sucameli
|
|
email : brush.tyler@gmail.com
|
|
|
|
The content of this file is based on
|
|
- PG_Manager by Martin Dobias (GPLv2 license)
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
"""
|
|
from builtins import str
|
|
from builtins import range
|
|
|
|
from qgis.PyQt.QtCore import Qt, QSettings, QFileInfo
|
|
from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QMessageBox, QApplication
|
|
from qgis.PyQt.QtGui import QCursor
|
|
|
|
from qgis.core import QgsDataSourceUri, QgsVectorLayer, QgsRasterLayer, QgsMimeDataUtils, QgsMapLayer, QgsProviderRegistry, QgsCoordinateReferenceSystem, QgsVectorLayerImport
|
|
from qgis.gui import QgsMessageViewer
|
|
from qgis.utils import iface
|
|
|
|
from .ui.ui_DlgImportVector import Ui_DbManagerDlgImportVector as Ui_Dialog
|
|
|
|
|
|
class DlgImportVector(QDialog, Ui_Dialog):
|
|
HAS_INPUT_MODE, ASK_FOR_INPUT_MODE = list(range(2))
|
|
|
|
def __init__(self, inLayer, outDb, outUri, parent=None):
|
|
QDialog.__init__(self, parent)
|
|
self.inLayer = inLayer
|
|
self.db = outDb
|
|
self.outUri = outUri
|
|
self.setupUi(self)
|
|
|
|
self.default_pk = "id"
|
|
self.default_geom = "geom"
|
|
|
|
self.mode = self.ASK_FOR_INPUT_MODE if self.inLayer is None else self.HAS_INPUT_MODE
|
|
|
|
# used to delete the inlayer whether created inside this dialog
|
|
self.inLayerMustBeDestroyed = False
|
|
|
|
self.populateSchemas()
|
|
self.populateTables()
|
|
self.populateLayers()
|
|
self.populateEncodings()
|
|
|
|
# updates of UI
|
|
self.setupWorkingMode(self.mode)
|
|
self.cboSchema.currentIndexChanged.connect(self.populateTables)
|
|
|
|
def setupWorkingMode(self, mode):
|
|
""" hide the widget to select a layer/file if the input layer is already set """
|
|
self.wdgInput.setVisible(mode == self.ASK_FOR_INPUT_MODE)
|
|
self.resize(450, 350)
|
|
|
|
self.cboTable.setEditText(self.outUri.table())
|
|
|
|
if mode == self.ASK_FOR_INPUT_MODE:
|
|
self.btnChooseInputFile.clicked.connect(self.chooseInputFile)
|
|
# self.cboInputLayer.lineEdit().editingFinished.connect(self.updateInputLayer)
|
|
self.cboInputLayer.editTextChanged.connect(self.inputPathChanged)
|
|
# self.cboInputLayer.currentIndexChanged.connect(self.updateInputLayer)
|
|
self.btnUpdateInputLayer.clicked.connect(self.updateInputLayer)
|
|
|
|
self.editPrimaryKey.setText(self.default_pk)
|
|
self.editGeomColumn.setText(self.default_geom)
|
|
else:
|
|
# set default values
|
|
self.checkSupports()
|
|
self.updateInputLayer()
|
|
|
|
def checkSupports(self):
|
|
""" update options available for the current input layer """
|
|
allowSpatial = self.db.connector.hasSpatialSupport()
|
|
hasGeomType = self.inLayer and self.inLayer.hasGeometryType()
|
|
isShapefile = self.inLayer and self.inLayer.providerType() == "ogr" and self.inLayer.storageType() == "ESRI Shapefile"
|
|
|
|
self.chkGeomColumn.setEnabled(allowSpatial and hasGeomType)
|
|
if not self.chkGeomColumn.isEnabled():
|
|
self.chkGeomColumn.setChecked(False)
|
|
|
|
self.chkSourceSrid.setEnabled(allowSpatial and hasGeomType)
|
|
if not self.chkSourceSrid.isEnabled():
|
|
self.chkSourceSrid.setChecked(False)
|
|
self.chkTargetSrid.setEnabled(allowSpatial and hasGeomType)
|
|
if not self.chkTargetSrid.isEnabled():
|
|
self.chkTargetSrid.setChecked(False)
|
|
|
|
self.chkSinglePart.setEnabled(allowSpatial and hasGeomType and isShapefile)
|
|
if not self.chkSinglePart.isEnabled():
|
|
self.chkSinglePart.setChecked(False)
|
|
|
|
self.chkSpatialIndex.setEnabled(allowSpatial and hasGeomType)
|
|
if not self.chkSpatialIndex.isEnabled():
|
|
self.chkSpatialIndex.setChecked(False)
|
|
|
|
self.chkLowercaseFieldNames.setEnabled(self.db.hasLowercaseFieldNamesOption())
|
|
if not self.chkLowercaseFieldNames.isEnabled():
|
|
self.chkLowercaseFieldNames.setChecked(False)
|
|
|
|
def populateLayers(self):
|
|
self.cboInputLayer.clear()
|
|
for index, layer in enumerate(iface.legendInterface().layers()):
|
|
# TODO: add import raster support!
|
|
if layer.type() == QgsMapLayer.VectorLayer:
|
|
self.cboInputLayer.addItem(layer.name(), index)
|
|
|
|
def deleteInputLayer(self):
|
|
""" unset the input layer, then destroy it but only if it was created from this dialog """
|
|
if self.mode == self.ASK_FOR_INPUT_MODE and self.inLayer:
|
|
if self.inLayerMustBeDestroyed:
|
|
self.inLayer.deleteLater()
|
|
self.inLayer = None
|
|
self.inLayerMustBeDestroyed = False
|
|
return True
|
|
return False
|
|
|
|
def chooseInputFile(self):
|
|
vectorFormats = QgsProviderRegistry.instance().fileVectorFilters()
|
|
# get last used dir and format
|
|
settings = QSettings()
|
|
lastDir = settings.value("/db_manager/lastUsedDir", "")
|
|
lastVectorFormat = settings.value("/UI/lastVectorFileFilter", "")
|
|
# ask for a filename
|
|
filename, lastVectorFormat = QFileDialog.getOpenFileName(self, self.tr("Choose the file to import"),
|
|
lastDir, vectorFormats, lastVectorFormat)
|
|
if filename == "":
|
|
return
|
|
# store the last used dir and format
|
|
settings.setValue("/db_manager/lastUsedDir", QFileInfo(filename).filePath())
|
|
settings.setValue("/UI/lastVectorFileFilter", lastVectorFormat)
|
|
|
|
self.cboInputLayer.setEditText(filename)
|
|
|
|
def inputPathChanged(self, path):
|
|
if self.cboInputLayer.currentIndex() < 0:
|
|
return
|
|
self.cboInputLayer.blockSignals(True)
|
|
self.cboInputLayer.setCurrentIndex(-1)
|
|
self.cboInputLayer.setEditText(path)
|
|
self.cboInputLayer.blockSignals(False)
|
|
|
|
def reloadInputLayer(self):
|
|
""" create the input layer and update available options """
|
|
if self.mode != self.ASK_FOR_INPUT_MODE:
|
|
return True
|
|
|
|
self.deleteInputLayer()
|
|
|
|
index = self.cboInputLayer.currentIndex()
|
|
if index < 0:
|
|
filename = self.cboInputLayer.currentText()
|
|
if filename == "":
|
|
return False
|
|
|
|
layerName = QFileInfo(filename).completeBaseName()
|
|
layer = QgsVectorLayer(filename, layerName, "ogr")
|
|
if not layer.isValid() or layer.type() != QgsMapLayer.VectorLayer:
|
|
layer.deleteLater()
|
|
return False
|
|
|
|
self.inLayer = layer
|
|
self.inLayerMustBeDestroyed = True
|
|
|
|
else:
|
|
legendIndex = self.cboInputLayer.itemData(index)
|
|
self.inLayer = iface.legendInterface().layers()[legendIndex]
|
|
self.inLayerMustBeDestroyed = False
|
|
|
|
self.checkSupports()
|
|
return True
|
|
|
|
def updateInputLayer(self):
|
|
if not self.reloadInputLayer() or not self.inLayer:
|
|
return False
|
|
|
|
# update the output table name, pk and geom column
|
|
self.cboTable.setEditText(self.inLayer.name())
|
|
|
|
srcUri = QgsDataSourceUri(self.inLayer.source())
|
|
pk = srcUri.keyColumn() if srcUri.keyColumn() else self.default_pk
|
|
self.editPrimaryKey.setText(pk)
|
|
geom = srcUri.geometryColumn() if srcUri.geometryColumn() else self.default_geom
|
|
self.editGeomColumn.setText(geom)
|
|
|
|
srcCrs = self.inLayer.crs()
|
|
srid = srcCrs.postgisSrid() if srcCrs.isValid() else 4326
|
|
self.editSourceSrid.setText("%s" % srid)
|
|
self.editTargetSrid.setText("%s" % srid)
|
|
|
|
return True
|
|
|
|
def populateSchemas(self):
|
|
if not self.db:
|
|
return
|
|
|
|
self.cboSchema.clear()
|
|
schemas = self.db.schemas()
|
|
if schemas is None:
|
|
self.hideSchemas()
|
|
return
|
|
|
|
index = -1
|
|
for schema in schemas:
|
|
self.cboSchema.addItem(schema.name)
|
|
if schema.name == self.outUri.schema():
|
|
index = self.cboSchema.count() - 1
|
|
self.cboSchema.setCurrentIndex(index)
|
|
|
|
def hideSchemas(self):
|
|
self.cboSchema.setEnabled(False)
|
|
|
|
def populateTables(self):
|
|
if not self.db:
|
|
return
|
|
|
|
currentText = self.cboTable.currentText()
|
|
|
|
schemas = self.db.schemas()
|
|
if schemas is not None:
|
|
schema_name = self.cboSchema.currentText()
|
|
matching_schemas = [x for x in schemas if x.name == schema_name]
|
|
tables = matching_schemas[0].tables() if len(matching_schemas) > 0 else []
|
|
else:
|
|
tables = self.db.tables()
|
|
|
|
self.cboTable.clear()
|
|
for table in tables:
|
|
self.cboTable.addItem(table.name)
|
|
|
|
self.cboTable.setEditText(currentText)
|
|
|
|
def populateEncodings(self):
|
|
encodings = ['ISO-8859-1', 'ISO-8859-2', 'UTF-8', 'CP1250']
|
|
for enc in encodings:
|
|
self.cboEncoding.addItem(enc)
|
|
self.cboEncoding.setCurrentIndex(2)
|
|
|
|
def accept(self):
|
|
if self.mode == self.ASK_FOR_INPUT_MODE:
|
|
# create the input layer (if not already done) and
|
|
# update available options
|
|
self.reloadInputLayer()
|
|
|
|
# sanity checks
|
|
if self.inLayer is None:
|
|
QMessageBox.information(self, self.tr("Import to database"), self.tr("Input layer missing or not valid"))
|
|
return
|
|
|
|
if self.cboTable.currentText() == "":
|
|
QMessageBox.information(self, self.tr("Import to database"), self.tr("Output table name is required"))
|
|
return
|
|
|
|
if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
|
|
try:
|
|
sourceSrid = self.editSourceSrid.text()
|
|
except ValueError:
|
|
QMessageBox.information(self, self.tr("Import to database"),
|
|
self.tr("Invalid source srid: must be an integer"))
|
|
return
|
|
|
|
if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
|
|
try:
|
|
targetSrid = self.editTargetSrid.text()
|
|
except ValueError:
|
|
QMessageBox.information(self, self.tr("Import to database"),
|
|
self.tr("Invalid target srid: must be an integer"))
|
|
return
|
|
|
|
# override cursor
|
|
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
|
# store current input layer crs and encoding, so I can restore it
|
|
prevInCrs = self.inLayer.crs()
|
|
prevInEncoding = self.inLayer.dataProvider().encoding()
|
|
|
|
try:
|
|
schema = self.outUri.schema() if not self.cboSchema.isEnabled() else self.cboSchema.currentText()
|
|
table = self.cboTable.currentText()
|
|
|
|
# get pk and geom field names from the source layer or use the
|
|
# ones defined by the user
|
|
srcUri = QgsDataSourceUri(self.inLayer.source())
|
|
|
|
pk = srcUri.keyColumn() if not self.chkPrimaryKey.isChecked() else self.editPrimaryKey.text()
|
|
if not pk:
|
|
pk = self.default_pk
|
|
|
|
if self.inLayer.hasGeometryType() and self.chkGeomColumn.isEnabled():
|
|
geom = srcUri.geometryColumn() if not self.chkGeomColumn.isChecked() else self.editGeomColumn.text()
|
|
if not geom:
|
|
geom = self.default_geom
|
|
else:
|
|
geom = None
|
|
|
|
options = {}
|
|
if self.chkLowercaseFieldNames.isEnabled() and self.chkLowercaseFieldNames.isChecked():
|
|
pk = pk.lower()
|
|
if geom:
|
|
geom = geom.lower()
|
|
options['lowercaseFieldNames'] = True
|
|
|
|
# get output params, update output URI
|
|
self.outUri.setDataSource(schema, table, geom, "", pk)
|
|
uri = self.outUri.uri(False)
|
|
|
|
providerName = self.db.dbplugin().providerName()
|
|
if self.chkDropTable.isChecked():
|
|
options['overwrite'] = True
|
|
|
|
if self.chkSinglePart.isEnabled() and self.chkSinglePart.isChecked():
|
|
options['forceSinglePartGeometryType'] = True
|
|
|
|
outCrs = QgsCoordinateReferenceSystem()
|
|
if self.chkTargetSrid.isEnabled() and self.chkTargetSrid.isChecked():
|
|
targetSrid = int(self.editTargetSrid.text())
|
|
outCrs = QgsCoordinateReferenceSystem(targetSrid)
|
|
|
|
# update input layer crs and encoding
|
|
if self.chkSourceSrid.isEnabled() and self.chkSourceSrid.isChecked():
|
|
sourceSrid = int(self.editSourceSrid.text())
|
|
inCrs = QgsCoordinateReferenceSystem(sourceSrid)
|
|
self.inLayer.setCrs(inCrs)
|
|
|
|
if self.chkEncoding.isEnabled() and self.chkEncoding.isChecked():
|
|
enc = self.cboEncoding.currentText()
|
|
self.inLayer.setProviderEncoding(enc)
|
|
|
|
onlySelected = self.chkSelectedFeatures.isChecked()
|
|
|
|
# do the import!
|
|
ret, errMsg = QgsVectorLayerImport.importLayer(self.inLayer, uri, providerName, outCrs, onlySelected, False, options)
|
|
except Exception as e:
|
|
ret = -1
|
|
errMsg = str(e)
|
|
|
|
finally:
|
|
# restore input layer crs and encoding
|
|
self.inLayer.setCrs(prevInCrs)
|
|
self.inLayer.setProviderEncoding(prevInEncoding)
|
|
# restore cursor
|
|
QApplication.restoreOverrideCursor()
|
|
|
|
if ret != 0:
|
|
output = QgsMessageViewer()
|
|
output.setTitle(self.tr("Import to database"))
|
|
output.setMessageAsPlainText(self.tr("Error %d\n%s") % (ret, errMsg))
|
|
output.showMessage()
|
|
return
|
|
|
|
# create spatial index
|
|
if self.chkSpatialIndex.isEnabled() and self.chkSpatialIndex.isChecked():
|
|
self.db.connector.createSpatialIndex((schema, table), geom)
|
|
|
|
QMessageBox.information(self, self.tr("Import to database"), self.tr("Import was successful."))
|
|
return QDialog.accept(self)
|
|
|
|
def closeEvent(self, event):
|
|
# destroy the input layer instance but only if it was created
|
|
# from this dialog!
|
|
self.deleteInputLayer()
|
|
QDialog.closeEvent(self, event)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
|
|
a = QApplication(sys.argv)
|
|
dlg = DlgImportVector()
|
|
dlg.show()
|
|
sys.exit(a.exec_())
|