Merge pull request #4119 from arnaud-morvan/processing_postgis_wrappers

[processing] Add PostGIS widget wrappers
This commit is contained in:
volaya 2017-02-16 11:12:30 +01:00 committed by GitHub
commit cdb35d6288
8 changed files with 346 additions and 134 deletions

View File

@ -27,8 +27,6 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QSettings
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterString
from processing.core.parameters import ParameterCrs
@ -87,17 +85,15 @@ class Ogr2OgrToPostGisList(GdalAlgorithm):
GdalAlgorithm.__init__(self)
self.processing = False
def dbConnectionNames(self):
settings = QSettings()
settings.beginGroup('/PostgreSQL/connections/')
return settings.childGroups()
def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Import Vector into PostGIS database (available connections)')
self.group, self.i18n_group = self.trAlgorithm('[OGR] Miscellaneous')
self.DB_CONNECTIONS = self.dbConnectionNames()
self.addParameter(ParameterSelection(self.DATABASE,
self.tr('Database (connection name)'), self.DB_CONNECTIONS))
self.addParameter(ParameterString(
self.DATABASE,
self.tr('Database (connection name)'),
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.ConnectionWidgetWrapper'}}))
self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer')))
self.addParameter(ParameterString(self.SHAPE_ENCODING,
@ -110,11 +106,24 @@ class Ogr2OgrToPostGisList(GdalAlgorithm):
self.tr('Reproject to this CRS on output '), '', optional=True))
self.addParameter(ParameterCrs(self.S_SRS,
self.tr('Override source CRS'), '', optional=True))
self.addParameter(ParameterString(self.SCHEMA,
self.tr('Schema name'), 'public', optional=True))
self.addParameter(ParameterString(self.TABLE,
self.tr('Table name, leave blank to use input name'),
'', optional=True))
self.addParameter(ParameterString(
self.SCHEMA,
self.tr('Schema name'),
'public',
optional=True,
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.SchemaWidgetWrapper',
'connection_param': self.DATABASE}}))
self.addParameter(ParameterString(
self.TABLE,
self.tr('Table name, leave blank to use input name'),
'',
optional=True,
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.TableWidgetWrapper',
'schema_param': self.SCHEMA}}))
self.addParameter(ParameterString(self.PK,
self.tr('Primary key (new field)'), 'id', optional=True))
self.addParameter(ParameterTableField(self.PRIMARY_KEY,
@ -168,7 +177,7 @@ class Ogr2OgrToPostGisList(GdalAlgorithm):
self.processing = False
def getConsoleCommands(self):
connection = self.DB_CONNECTIONS[self.getParameterValue(self.DATABASE)]
connection = self.getParameterValue(self.DATABASE)
uri = uri_from_name(connection)
if self.processing:
# to get credentials input when needed

View File

@ -33,7 +33,6 @@ from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecution
from processing.core.parameters import ParameterBoolean
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterString
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterTableField
from processing.tools import dataobjects, postgis
@ -58,14 +57,30 @@ class ImportIntoPostGIS(GeoAlgorithm):
self.group, self.i18n_group = self.trAlgorithm('Database')
self.addParameter(ParameterVector(self.INPUT,
self.tr('Layer to import')))
self.DB_CONNECTIONS = self.dbConnectionNames()
self.addParameter(ParameterSelection(self.DATABASE,
self.tr('Database (connection name)'), self.DB_CONNECTIONS))
self.addParameter(ParameterString(self.SCHEMA,
self.tr('Schema (schema name)'), 'public'))
self.addParameter(ParameterString(self.TABLENAME,
self.tr('Table to import to (leave blank to use layer name)'), optional=True))
self.addParameter(ParameterString(
self.DATABASE,
self.tr('Database (connection name)'),
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.ConnectionWidgetWrapper'}}))
self.addParameter(ParameterString(
self.SCHEMA,
self.tr('Schema (schema name)'),
'public',
optional=True,
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.SchemaWidgetWrapper',
'connection_param': self.DATABASE}}))
self.addParameter(ParameterString(
self.TABLENAME,
self.tr('Table to import to (leave blank to use layer name)'),
'',
optional=True,
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.TableWidgetWrapper',
'schema_param': self.SCHEMA}}))
self.addParameter(ParameterTableField(self.PRIMARY_KEY,
self.tr('Primary key field'), self.INPUT, optional=True))
self.addParameter(ParameterString(self.GEOMETRY_COLUMN,
@ -85,7 +100,7 @@ class ImportIntoPostGIS(GeoAlgorithm):
self.tr('Create single-part geometries instead of multi-part'), False))
def processAlgorithm(self, feedback):
connection = self.DB_CONNECTIONS[self.getParameterValue(self.DATABASE)]
connection = self.getParameterValue(self.DATABASE)
db = postgis.GeoDB.from_name(connection)
schema = self.getParameterValue(self.SCHEMA)

View File

@ -26,8 +26,6 @@ __copyright__ = '(C) 2012, Victor Olaya, Carterix Geomatics'
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QSettings
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterString
@ -42,7 +40,12 @@ class PostGISExecuteSQL(GeoAlgorithm):
def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('PostGIS execute SQL')
self.group, self.i18n_group = self.trAlgorithm('Database')
self.addParameter(ParameterString(self.DATABASE, self.tr('Database')))
self.addParameter(ParameterString(
self.DATABASE,
self.tr('Database'),
metadata={
'widget_wrapper': {
'class': 'processing.gui.wrappers_postgis.ConnectionWidgetWrapper'}}))
self.addParameter(ParameterString(self.SQL, self.tr('SQL query'), '', True))
def processAlgorithm(self, feedback):

View File

@ -178,6 +178,11 @@ class Parameter(object):
def wrapper(self, dialog, row=0, col=0):
wrapper = self.metadata.get('widget_wrapper', None)
params = {}
# wrapper metadata should be a dict with class key
if isinstance(wrapper, dict):
params = deepcopy(wrapper)
wrapper = params.pop('class')
# wrapper metadata should be a class path
if isinstance(wrapper, str):
tokens = wrapper.split('.')
@ -185,7 +190,7 @@ class Parameter(object):
wrapper = getattr(mod, tokens[-1])
# or directly a class object
if isclass(wrapper):
wrapper = wrapper(self, dialog, row, col)
wrapper = wrapper(self, dialog, row, col, **params)
# or a wrapper instance
return wrapper

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
NumberInputPanel.py
---------------------
Date : August 2016
Copyright : (C) 2016 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* 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
__author__ = 'Victor Olaya'
__date__ = 'August 2016'
__copyright__ = '(C) 2016, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.PyQt import uic
from qgis.PyQt.QtCore import pyqtSignal
from qgis.PyQt.QtWidgets import QDialog
from qgis.core import (QgsDataSourceUri,
QgsCredentials,
QgsExpression,
QgsRasterLayer)
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
from qgis.utils import iface
pluginPath = os.path.split(os.path.dirname(__file__))[0]
WIDGET, BASE = uic.loadUiType(
os.path.join(pluginPath, 'ui', 'widgetBaseSelector.ui'))
class StringInputPanel(BASE, WIDGET):
hasChanged = pyqtSignal()
def __init__(self, param):
super(StringInputPanel, self).__init__(None)
self.setupUi(self)
self.param = param
self.text = param.default
self.btnSelect.clicked.connect(self.showExpressionsBuilder)
self.leText.textChanged.connect(lambda: self.hasChanged.emit())
def showExpressionsBuilder(self):
context = self.param.expressionContext()
dlg = QgsExpressionBuilderDialog(None, self.leText.text(), self, 'generic', context)
dlg.setWindowTitle(self.tr('Expression based input'))
if dlg.exec_() == QDialog.Accepted:
exp = QgsExpression(dlg.expressionText())
if not exp.hasParserError():
self.setValue(dlg.expressionText())
def getValue(self):
return self.leText.text()
def setValue(self, value):
self.leText.setText(str(value))

View File

@ -2,7 +2,7 @@
"""
***************************************************************************
wrappers.py
wrappers.py - Standard parameters widget wrappers
---------------------
Date : May 2016
Copyright : (C) 2016 by Arnaud Morvan, Victor Olaya
@ -17,8 +17,6 @@
* *
***************************************************************************
"""
from builtins import str
from builtins import range
__author__ = 'Arnaud Morvan'
@ -34,16 +32,34 @@ import locale
import os
from functools import cmp_to_key
from qgis.core import QgsCoordinateReferenceSystem, QgsApplication, QgsWkbTypes, QgsMapLayerProxyModel
from qgis.PyQt.QtWidgets import QCheckBox, QComboBox, QLineEdit, QPlainTextEdit, QWidget, QHBoxLayout, QToolButton, QFileDialog
from qgis.gui import (QgsFieldExpressionWidget,
QgsExpressionLineEdit,
QgsProjectionSelectionWidget,
QgsGenericProjectionSelector,
QgsFieldComboBox,
QgsFieldProxyModel,
QgsMapLayerComboBox
)
from qgis.core import (
QgsApplication,
QgsCoordinateReferenceSystem,
QgsExpression,
QgsMapLayerProxyModel,
QgsWkbTypes,
)
from qgis.PyQt.QtWidgets import (
QCheckBox,
QComboBox,
QDialog,
QFileDialog,
QHBoxLayout,
QLineEdit,
QPlainTextEdit,
QToolButton,
QWidget,
)
from qgis.gui import (
QgsExpressionLineEdit,
QgsExpressionBuilderDialog,
QgsFieldComboBox,
QgsFieldExpressionWidget,
QgsFieldProxyModel,
QgsGenericProjectionSelector,
QgsMapLayerComboBox,
QgsProjectionSelectionWidget,
)
from qgis.PyQt.QtCore import pyqtSignal, QObject, QVariant, QSettings
from processing.gui.NumberInputPanel import NumberInputPanel, ModellerNumberInputPanel
@ -72,7 +88,6 @@ from processing.gui.MultipleInputPanel import MultipleInputPanel
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
from processing.gui.FixedTablePanel import FixedTablePanel
from processing.gui.ExtentSelectionPanel import ExtentSelectionPanel
from processing.gui.StringInputPanel import StringInputPanel
DIALOG_STANDARD = 'standard'
@ -101,14 +116,14 @@ class WidgetWrapper(QObject):
widgetValueHasChanged = pyqtSignal(object)
def __init__(self, param, dialog, row=0, col=0):
def __init__(self, param, dialog, row=0, col=0, **kwargs):
QObject.__init__(self)
self.param = param
self.dialog = dialog
self.row = row
self.col = col
self.dialogType = dialogTypes.get(dialog.__class__.__name__, DIALOG_STANDARD)
self.widget = self.createWidget()
self.widget = self.createWidget(**kwargs)
if param.default is not None:
self.setValue(param.default)
@ -123,7 +138,7 @@ class WidgetWrapper(QObject):
return v
return combobox.currentData()
def createWidget(self):
def createWidget(self, **kwargs):
pass
def setValue(self, value):
@ -177,6 +192,36 @@ class WidgetWrapper(QObject):
return filename, selected_filter
class ExpressionWidgetWrapperMixin():
def wrapWithExpressionButton(self, basewidget):
expr_button = QToolButton()
expr_button.clicked.connect(self.showExpressionsBuilder)
expr_button.setText('...')
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(basewidget)
layout.addWidget(expr_button)
widget = QWidget()
widget.setLayout(layout)
return widget
def showExpressionsBuilder(self):
context = self.param.expressionContext()
value = self.value()
if not isinstance(value, str):
value = ''
dlg = QgsExpressionBuilderDialog(None, value, self.widget, 'generic', context)
dlg.setWindowTitle(self.tr('Expression based input'))
if dlg.exec_() == QDialog.Accepted:
exp = QgsExpression(dlg.expressionText())
if not exp.hasParserError():
self.setValue(dlg.expressionText())
class BasicWidgetWrapper(WidgetWrapper):
def createWidget(self):
@ -793,22 +838,19 @@ class VectorWidgetWrapper(WidgetWrapper):
return self.comboValue(validator, combobox=self.combo)
class StringWidgetWrapper(WidgetWrapper):
class StringWidgetWrapper(WidgetWrapper, ExpressionWidgetWrapperMixin):
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
if self.param.multiline:
widget = QPlainTextEdit()
if self.param.default:
widget.setPlainText(self.param.default)
else:
widget = StringInputPanel(self.param)
if self.param.default:
widget.setValue(self.param.default)
self._lineedit = QLineEdit()
return self.wrapWithExpressionButton(self._lineedit)
elif self.dialogType == DIALOG_BATCH:
widget = QLineEdit()
if self.param.default:
widget.setText(self.param.default)
else:
# strings, numbers, files and table fields are all allowed input types
strings = self.dialog.getAvailableValuesOfType([ParameterString, ParameterNumber, ParameterFile,
@ -816,20 +858,23 @@ class StringWidgetWrapper(WidgetWrapper):
options = [(self.dialog.resolveValueDescription(s), s) for s in strings]
if self.param.multiline:
widget = MultilineTextPanel(options)
widget.setText(self.param.default or "")
else:
widget = QComboBox()
widget.setEditable(True)
for desc, val in options:
widget.addItem(desc, val)
widget.setEditText(self.param.default or "")
return widget
def setValue(self, value):
if self.dialogType == DIALOG_STANDARD:
pass # TODO
if self.param.multiline:
self.widget.setPlainText(value)
else:
self._lineedit.setText(value)
elif self.dialogType == DIALOG_BATCH:
self.widget.setText(value)
else:
if self.param.multiline:
self.widget.setValue(value)
@ -841,10 +886,12 @@ class StringWidgetWrapper(WidgetWrapper):
if self.param.multiline:
text = self.widget.toPlainText()
else:
text = self.widget.getValue()
text = self._lineedit.text()
return text
elif self.dialogType == DIALOG_BATCH:
return self.widget.text()
else:
if self.param.multiline:
value = self.widget.getValue()

View File

@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
postgis.py - Postgis widget wrappers
---------------------
Date : December 2016
Copyright : (C) 2016 by Arnaud Morvan
Email : arnaud dot morvan at camptocamp dot com
***************************************************************************
* *
* 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 qgis.PyQt.QtCore import QSettings
from qgis.PyQt.QtWidgets import QComboBox
from processing.core.parameters import (
ParameterString,
ParameterNumber,
ParameterFile,
ParameterTableField,
ParameterExpression
)
from processing.core.outputs import OutputString
from processing.gui.wrappers import (
WidgetWrapper,
ExpressionWidgetWrapperMixin,
DIALOG_MODELER,
)
from processing.tools.postgis import GeoDB
class ConnectionWidgetWrapper(WidgetWrapper, ExpressionWidgetWrapperMixin):
"""
WidgetWrapper for ParameterString that create and manage a combobox widget
with existing postgis connections.
"""
def createWidget(self):
self._combo = QComboBox()
for group in self.items():
self._combo.addItem(*group)
self._combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return self.wrapWithExpressionButton(self._combo)
def items(self):
settings = QSettings()
settings.beginGroup('/PostgreSQL/connections/')
items = [(group, group) for group in settings.childGroups()]
if self.dialogType == DIALOG_MODELER:
strings = self.dialog.getAvailableValuesOfType(
[ParameterString, ParameterNumber, ParameterFile,
ParameterTableField, ParameterExpression], OutputString)
items = items + [(self.dialog.resolveValueDescription(s), s) for s in strings]
return items
def setValue(self, value):
self.setComboValue(value, self._combo)
def value(self):
return self.comboValue(combobox=self._combo)
class SchemaWidgetWrapper(WidgetWrapper, ExpressionWidgetWrapperMixin):
"""
WidgetWrapper for ParameterString that create and manage a combobox widget
with existing schemas from a parent connection parameter.
"""
def createWidget(self, connection_param=None):
self._connection_param = connection_param
self._connection = None
self._database = None
self._combo = QComboBox()
self._combo.setEditable(True)
self.refreshItems()
self._combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self._combo.lineEdit().editingFinished.connect(lambda: self.widgetValueHasChanged.emit(self))
return self.wrapWithExpressionButton(self._combo)
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.param.name == self._connection_param:
self.connection_wrapper = wrapper
self.setConnection(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.connectionChanged)
break
def connectionChanged(self, wrapper):
connection = wrapper.value()
if connection == self._connection:
return
self.setConnection(connection)
def setConnection(self, connection):
self._connection = connection
if isinstance(connection, str):
self._database = GeoDB.from_name(connection)
else:
self._database = None
self.refreshItems()
self.widgetValueHasChanged.emit(self)
def refreshItems(self):
value = self.comboValue(combobox=self._combo)
self._combo.clear()
if self._database is not None:
for schema in self._database.list_schemas():
self._combo.addItem(schema[1], schema[1])
if self.dialogType == DIALOG_MODELER:
strings = self.dialog.getAvailableValuesOfType(
[ParameterString, ParameterNumber, ParameterFile,
ParameterTableField, ParameterExpression], OutputString)
for text, data in [(self.dialog.resolveValueDescription(s), s) for s in strings]:
self._combo.addItem(text, data)
self.setComboValue(value, self._combo)
def setValue(self, value):
self.setComboValue(value, self._combo)
self.widgetValueHasChanged.emit(self)
def value(self):
return self.comboValue(combobox=self._combo)
def database(self):
return self._database
class TableWidgetWrapper(WidgetWrapper, ExpressionWidgetWrapperMixin):
"""
WidgetWrapper for ParameterString that create and manage a combobox widget
with existing tables from a parent schema parameter.
"""
def createWidget(self, schema_param=None):
self._schema_param = schema_param
self._database = None
self._schema = None
self._combo = QComboBox()
self._combo.setEditable(True)
self.refreshItems()
self._combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self._combo.lineEdit().editingFinished.connect(lambda: self.widgetValueHasChanged.emit(self))
return self.wrapWithExpressionButton(self._combo)
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.param.name == self._schema_param:
self.schema_wrapper = wrapper
self.setSchema(wrapper.database(), wrapper.value())
wrapper.widgetValueHasChanged.connect(self.schemaChanged)
break
def schemaChanged(self, wrapper):
database = wrapper.database()
schema = wrapper.value()
if database == self._database and schema == self._schema:
return
self.setSchema(database, schema)
def setSchema(self, database, schema):
self._database = database
self._schema = schema
self.refreshItems()
self.widgetValueHasChanged.emit(self)
def refreshItems(self):
value = self.comboValue(combobox=self._combo)
self._combo.clear()
if (self._database is not None and isinstance(self._schema, str)):
for table in self._database.list_geotables(self._schema):
self._combo.addItem(table[0], table[0])
if self.dialogType == DIALOG_MODELER:
strings = self.dialog.getAvailableValuesOfType(
[ParameterString, ParameterNumber, ParameterFile,
ParameterTableField, ParameterExpression], OutputString)
for text, data in [(self.dialog.resolveValueDescription(s), s) for s in strings]:
self._combo.addItem(text, data)
self.setComboValue(value, self._combo)
def setValue(self, value):
self.setComboValue(value, self._combo)
self.widgetValueHasChanged.emit(self)
def value(self):
return self.comboValue(combobox=self._combo)

View File

@ -146,7 +146,7 @@ class ModelerParametersDialog(QDialog):
self.wrappers[param.name] = wrapper
widget = wrapper.widget
if widget:
if widget is not None:
self.valueItems[param.name] = widget
if param.name in list(tooltips.keys()):
tooltip = tooltips[param.name]