diff --git a/CenRa_COPIE/__init__.py b/CenRa_COPIE/__init__.py new file mode 100644 index 00000000..72443b8f --- /dev/null +++ b/CenRa_COPIE/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Copie + A QGIS plugin + Permet la copie d'une table dans une base PostGis + ------------------- + begin : 2015-04-13 + copyright : (C) 2015 by Guillaume COSTES - CEN Rhône-Alpes + email : guillaume.costes@espaces-naturels.fr + git sha : $Format:%H$ + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + This script initializes the plugin, making it known to QGIS. +""" + + +# noinspection PyPep8Naming +def classFactory(iface): # pylint: disable=invalid-name + """Load Copie class from file Copie. + + :param iface: A QGIS interface instance. + :type iface: QgsInterface + """ + # + from .copie import Copie + return Copie(iface) diff --git a/CenRa_COPIE/cenra.png b/CenRa_COPIE/cenra.png new file mode 100644 index 00000000..d53a910f Binary files /dev/null and b/CenRa_COPIE/cenra.png differ diff --git a/CenRa_COPIE/copie.py b/CenRa_COPIE/copie.py new file mode 100644 index 00000000..d2d26c83 --- /dev/null +++ b/CenRa_COPIE/copie.py @@ -0,0 +1,360 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + Copie + A QGIS plugin + Permet la copie d'une table dans une base PostGis + ------------------- + begin : 2015-04-13 + git sha : $Format:%H$ + copyright : (C) 2015 by Guillaume COSTES - CEN Rhône-Alpes + email : guillaume.costes@espaces-naturels.fr + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 __future__ import absolute_import +#from PyQt4.QtCore import * +#from PyQt4.QtGui import * +from qgis.PyQt.QtCore import QSettings +from qgis.PyQt.QtWidgets import QAction, QMenu, QDialog, QMessageBox +from qgis.PyQt.QtGui import QIcon +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from qgis.core import QgsDataSourceUri +from builtins import str +from builtins import object +from qgis.core import * +from qgis.gui import * +# Initialize Qt resources from file resources.py +from . import resources_rc +# Import the code for the dialog +from .copie_dialog import CopieDialog +import os.path +from .PythonSQL import * +import psycopg2 +import psycopg2.extras +import base64 +#import socket +import os +#import sys + +class Copie(object): + """QGIS Plugin Implementation.""" + + def __init__(self, iface): + """Constructor. + + :param iface: An interface instance that will be passed to this class + which provides the hook by which you can manipulate the QGIS + application at run time. + :type iface: QgsInterface + """ + # Save reference to the QGIS interface + self.iface = iface + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + # initialize locale + locale = QSettings().value('locale/userLocale')[0:2] + locale_path = os.path.join( + self.plugin_dir, + 'i18n', + 'Copie_{}.qm'.format(locale)) + + if os.path.exists(locale_path): + self.translator = QTranslator() + self.translator.load(locale_path) + + if qVersion() > '4.3.3': + QCoreApplication.installTranslator(self.translator) + + # Create the dialog (after translation) and keep reference + self.dlg = CopieDialog() + + # Declare instance attributes + self.actions = [] + self.menu = self.tr(u'&Copie') + # TODO: We are going to let the user set this up in a future iteration + self.toolbar = self.iface.addToolBar(u'Copie') + self.toolbar.setObjectName(u'Copie') + + # noinspection PyMethodMayBeStatic + def tr(self, message): + """Get the translation for a string using Qt translation API. + + We implement this ourselves since we do not inherit QObject. + + :param message: String for translation. + :type message: str, QString + + :returns: Translated version of message. + :rtype: QString + """ + # noinspection PyTypeChecker,PyArgumentList,PyCallByClass + return QCoreApplication.translate('Copie', message) + + + def add_action( + self, + icon_path, + text, + callback, + enabled_flag=True, + add_to_menu=True, + add_to_toolbar=True, + status_tip=None, + whats_this=None, + parent=None): + """Add a toolbar icon to the toolbar. + + :param icon_path: Path to the icon for this action. Can be a resource + path (e.g. ':/plugins/foo/bar.png') or a normal file system path. + :type icon_path: str + + :param text: Text that should be shown in menu items for this action. + :type text: str + + :param callback: Function to be called when the action is triggered. + :type callback: function + + :param enabled_flag: A flag indicating if the action should be enabled + by default. Defaults to True. + :type enabled_flag: bool + + :param add_to_menu: Flag indicating whether the action should also + be added to the menu. Defaults to True. + :type add_to_menu: bool + + :param add_to_toolbar: Flag indicating whether the action should also + be added to the toolbar. Defaults to True. + :type add_to_toolbar: bool + + :param status_tip: Optional text to show in a popup when mouse pointer + hovers over the action. + :type status_tip: str + + :param parent: Parent widget for the new action. Defaults None. + :type parent: QWidget + + :param whats_this: Optional text to show in the status bar when the + mouse pointer hovers over the action. + + :returns: The action that was created. Note that the action is also + added to self.actions list. + :rtype: QAction + """ + + icon = QIcon(icon_path) + action = QAction(icon, text, parent) + action.triggered.connect(callback) + action.setEnabled(enabled_flag) + + if status_tip is not None: + action.setStatusTip(status_tip) + + if whats_this is not None: + action.setWhatsThis(whats_this) + + if add_to_toolbar: + self.toolbar.addAction(action) + + if add_to_menu: + self.iface.addPluginToMenu( + self.menu, + action) + + self.actions.append(action) + + return action + + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + + icon_path = ':/plugins/Copie/table_copie.png' + self.add_action( + icon_path, + text=self.tr(u'Copie'), + callback=self.run, + parent=self.iface.mainWindow()) + + + def unload(self): + """Removes the plugin menu item and icon from QGIS GUI.""" + for action in self.actions: + self.iface.removePluginMenu( + self.tr(u'&Copie'), + action) + self.iface.removeToolBarIcon(action) + # remove the toolbar + del self.toolbar + + + def run(self): + """Run method that performs all the real work""" + layer = self.iface.activeLayer() + + if layer == None : + #self.iface.messageBar().pushMessage(u"Vous devez sélectionner une table !", level=QgsMessageBar.WARNING, duration=5) + self.iface.messageBar().pushMessage("Ooops", u"Vous devez sélectionner une table !", level=Qgis.Warning, duration=5) + + else : + # Récupération des sources de la couche active + list_sources = layer.source().split(" ") + # dbname + source_db = [s for s in list_sources if "dbname" in s][0].split("'")[1] + # schema + source_schema = [s for s in list_sources if "table" in s][0].split('"')[1] + # tablename + source_tablename = [s for s in list_sources if "table" in s][0].split('"')[3] + + if source_db != sigdb: + #self.iface.messageBar().pushMessage(u"Un référentiel ne peut être copié, utilisez les filtres !", level=QgsMessageBar.CRITICAL, duration=10) + self.iface.messageBar().pushMessage("Ooops", u"Vous ne pouvez copier des couches que dans sigXX", level=Qgis.Critical, duration=5) + + else : + + first_conn = psycopg2.connect("host=" + host + " port=" + port + " dbname="+dbname+" user=first_cnx password=" + password) + first_cur = first_conn.cursor(cursor_factory = psycopg2.extras.DictCursor) + first_cur.execute("SELECT mdp_w, login_w FROM pg_catalog.pg_user t1, admin_sig.vm_users_sig t2 WHERE t2.oid = t1.usesysid AND (login_w = '" + os_user + "' OR login_w = '" + os_user + "')") + res_ident = first_cur.fetchone() + mdp = base64.b64decode(str(res_ident[0])).decode('utf-8') + user = res_ident[1] + con = psycopg2.connect("host=" + host + " port=" + port + " dbname="+dbname+" user=" + user + " password=" + mdp) + cur = con.cursor(cursor_factory = psycopg2.extras.DictCursor) + first_conn.close() + + # Creation de la liste des schemas de la base de donnees + SQL = """WITH list_schema AS ( + SELECT catalog_name, schema_name + FROM information_schema.schemata + WHERE schema_name <> 'information_schema' + AND schema_name !~ E'^pg_' + ORDER BY schema_name + ) + + SELECT string_agg(schema_name,',') + FROM list_schema + GROUP BY catalog_name""" + + cur.execute(SQL) + + list_brut = str(next(cur)) + + list = list_brut [2:-3] + listItems = list.split(",") + + con.close() + + self.dlg.schema.clear() + self.dlg.schema.addItems(listItems) + self.dlg.schema.setCurrentIndex(-1) # Pour ne pas commencer la liste au premier schema + + + self.dlg.table_source.setText(source_schema + "." + source_tablename) # Affiche le nom de la table source + # show the dialog + self.dlg.show() + # Run the dialog event loop + result = self.dlg.exec_() + # See if OK was pressed + if result: + #******************************debut script********************************* + + + first_conn = psycopg2.connect("host=" + host + " port=" + port + " dbname="+dbname+" user=first_cnx password=" + password) + first_cur = first_conn.cursor(cursor_factory = psycopg2.extras.DictCursor) + first_cur.execute("SELECT mdp_w, login_w FROM pg_catalog.pg_user t1, admin_sig.vm_users_sig t2 WHERE t2.oid = t1.usesysid AND (login_w = '" + os_user + "' OR login_w = '" + os_user + "')") + res_ident = first_cur.fetchone() + mdp = base64.b64decode(str(res_ident[0])).decode('utf-8') + user = res_ident[1] + con = psycopg2.connect("host=" + host + " port=" + port + " dbname="+dbname+" user=" + user + " password=" + mdp) + cur = con.cursor(cursor_factory = psycopg2.extras.DictCursor) + first_conn.close() + # Récupération de la couche active + layer = self.iface.activeLayer() + + # Récupération des sources de la couche active + list_sources = layer.source().split(" ") + # dbname + source_db = [s for s in list_sources if "dbname" in s][0].split("'")[1] + # schema + source_schema = [s for s in list_sources if "table" in s][0].split('"')[1] + # tablename + source_tablename = [s for s in list_sources if "table" in s][0].split('"')[3] + + if self.dlg.schema.currentIndex() == -1 : + QMessageBox.warning(None, "Oups :", "Veuillez choisir un dossier de destination.") + return + + schema = self.dlg.schema.currentText() + + if self.dlg.table_destination.text() == '' : + QMessageBox.warning(None, "Oups :", "Veuillez choisir un nom de destination.") + return + + if self.dlg.annee.text() == 'aaaa' or self.dlg.annee.text() == '': + tablename = schema + "_" + self.dlg.table_destination.text().lower() + else : + tablename = schema + "_" + self.dlg.table_destination.text().lower() + "_" + self.dlg.annee.text() + + tablename_qgis = tablename[1:] # Permet d'enlever le "_", ajouter a la premiere etape, dans qgis + + if self.dlg.table_vide.isChecked() == 1 : + SQL_table = "CREATE TABLE " + schema + "." + tablename + " AS SELECT * FROM " + source_schema + "." + source_tablename + " LIMIT 0;" + else : + SQL_table = "CREATE TABLE " + schema + "." + tablename + " AS SELECT * FROM " + source_schema + "." + source_tablename + + + SQL_pkey = "ALTER TABLE " + schema + "." + tablename + " ADD CONSTRAINT " + tablename + "_pkey" + " PRIMARY KEY (gid)" + SQL_sequence_01 = "CREATE SEQUENCE " + schema + "." + tablename + "_gid_seq" + " INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1;" + SQL_sequence_02 = "ALTER TABLE " + schema + "." + tablename + " ALTER COLUMN gid SET DEFAULT nextval(\'" + schema + "." + tablename + "_gid_seq\'::regclass);" + SQL_sequence_03 = "SELECT setval(\'" + schema + "." + tablename + "_gid_seq\'::regclass, (SELECT max(gid) AS max_gid FROM " + schema + "." + tablename + "));" + SQL_sequence_04 = "ALTER SEQUENCE " + schema + "." + tablename + "_gid_seq" + " OWNED BY " + schema + "." + tablename + ".gid;" + + SQL_trigger_area_m2 = "CREATE TRIGGER area_m2" + tablename + " BEFORE INSERT OR UPDATE ON " + schema + "." + tablename + " FOR EACH ROW EXECUTE PROCEDURE ref.area_m2();" + SQL_trigger_area_ha = "CREATE TRIGGER area_ha" + tablename + " BEFORE INSERT OR UPDATE ON " + schema + "." + tablename + " FOR EACH ROW EXECUTE PROCEDURE ref.area_ha();" + SQL_trigger_length_m = "CREATE TRIGGER length_m" + tablename + " BEFORE INSERT OR UPDATE ON " + schema + "." + tablename + " FOR EACH ROW EXECUTE PROCEDURE ref.length_m();" + SQL_trigger_length_km = "CREATE TRIGGER length_km" + tablename + " BEFORE INSERT OR UPDATE ON " + schema + "." + tablename + " FOR EACH ROW EXECUTE PROCEDURE ref.length_km();" + SQL_trigger_coordonnees = "CREATE TRIGGER coordonnees" + tablename + " BEFORE INSERT OR UPDATE ON " + schema + "." + tablename + " FOR EACH ROW EXECUTE PROCEDURE ref.coordonnees();" + + cur.execute(SQL_table) + cur.execute(SQL_pkey) + cur.execute(SQL_sequence_01) + cur.execute(SQL_sequence_02) + cur.execute(SQL_sequence_03) + cur.execute(SQL_sequence_04) + + if layer.wkbType() == QgsWkbTypes.PointGeometry : + cur.execute(SQL_trigger_coordonnees) + + if layer.wkbType() == QgsWkbTypes.LineGeometry : + cur.execute(SQL_trigger_length_m) + cur.execute(SQL_trigger_length_km) + + if layer.wkbType() == QgsWkbTypes.PolygonGeometry : + cur.execute(SQL_trigger_area_m2) + cur.execute(SQL_trigger_area_ha) + + con.commit() + + ### Affichage de la table + uri = QgsDataSourceUri() + # set host name, port, database name, username and password + uri.setConnection(host ,port ,dbname ,user ,mdp) + # set database schema, table name, geometry column and optionaly subset (WHERE clause) + uri.setDataSource(schema, tablename, geom) + + layer = self.iface.addVectorLayer(uri.uri(), tablename_qgis, "postgres") + + con.commit() + con.close() + + #self.iface.messageBar().pushMessage("Table \"" + source_schema + "." + source_tablename + u"\" copiée dans \"" + schema + "." + tablename + "\"." , level=QgsMessageBar.INFO, duration=10) + self.iface.messageBar().pushMessage("Bravo!", "Table \"" + source_schema + "." + source_tablename + u"\" copiée dans \"" + schema + "." + tablename + "\".", level=Qgis.Success, duration=5) + pass diff --git a/CenRa_COPIE/copie_dialog.py b/CenRa_COPIE/copie_dialog.py new file mode 100644 index 00000000..95c2d782 --- /dev/null +++ b/CenRa_COPIE/copie_dialog.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + CopieDialog + A QGIS plugin + Permet la copie d'une table dans une base PostGis + ------------------- + begin : 2015-04-13 + git sha : $Format:%H$ + copyright : (C) 2015 by Guillaume COSTES - CEN Rhône-Alpes + email : guillaume.costes@espaces-naturels.fr + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +""" + +import os + +from qgis.PyQt import QtCore, QtGui, QtGui, uic +from qgis.PyQt.QtWidgets import QAction, QMenu, QDialog + +FORM_CLASS, _ = uic.loadUiType(os.path.join( + os.path.dirname(__file__), 'copie_dialog_base.ui')) + + +class CopieDialog(QDialog, FORM_CLASS): + def __init__(self, parent=None): + """Constructor.""" + super(CopieDialog, self).__init__(parent) + # Set up the user interface from Designer. + # After setupUI you can access any designer object by doing + # self., and you can use autoconnect slots - see + # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html + # #widgets-and-dialogs-with-auto-connect + self.setupUi(self) diff --git a/CenRa_COPIE/copie_dialog_base.py b/CenRa_COPIE/copie_dialog_base.py new file mode 100644 index 00000000..1310a4e3 --- /dev/null +++ b/CenRa_COPIE/copie_dialog_base.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'copie_dialog_base.ui' +# +# Created: Tue Apr 14 11:48:04 2015 +# by: PyQt4 UI code generator 4.10.2 +# +# WARNING! All changes made in this file will be lost! + +from builtins import object +from qgis.PyQt import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_CopieDialogBase(object): + def setupUi(self, CopieDialogBase): + CopieDialogBase.setObjectName(_fromUtf8("CopieDialogBase")) + CopieDialogBase.resize(386, 290) + self.button_box = QtGui.QDialogButtonBox(CopieDialogBase) + self.button_box.setGeometry(QtCore.QRect(100, 250, 161, 32)) + self.button_box.setOrientation(QtCore.Qt.Horizontal) + self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.button_box.setObjectName(_fromUtf8("button_box")) + self.schema_label = QtGui.QLabel(CopieDialogBase) + self.schema_label.setGeometry(QtCore.QRect(20, 100, 121, 29)) + self.schema_label.setMaximumSize(QtCore.QSize(10000, 16777215)) + self.schema_label.setObjectName(_fromUtf8("schema_label")) + self.label_nom_table = QtGui.QLabel(CopieDialogBase) + self.label_nom_table.setGeometry(QtCore.QRect(20, 150, 131, 29)) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.label_nom_table.setFont(font) + self.label_nom_table.setObjectName(_fromUtf8("label_nom_table")) + self.schema = QtGui.QComboBox(CopieDialogBase) + self.schema.setGeometry(QtCore.QRect(20, 130, 351, 20)) + self.schema.setObjectName(_fromUtf8("schema")) + self.table_destination = QtGui.QLineEdit(CopieDialogBase) + self.table_destination.setGeometry(QtCore.QRect(20, 180, 291, 20)) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.table_destination.setFont(font) + self.table_destination.setObjectName(_fromUtf8("table_destination")) + self.annee = QtGui.QLineEdit(CopieDialogBase) + self.annee.setGeometry(QtCore.QRect(320, 180, 50, 20)) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.annee.setFont(font) + self.annee.setAlignment(QtCore.Qt.AlignCenter) + self.annee.setObjectName(_fromUtf8("annee")) + self.table_vide = QtGui.QCheckBox(CopieDialogBase) + self.table_vide.setGeometry(QtCore.QRect(60, 220, 271, 20)) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.table_vide.setFont(font) + self.table_vide.setObjectName(_fromUtf8("table_vide")) + self.table_source = QtGui.QLineEdit(CopieDialogBase) + self.table_source.setEnabled(False) + self.table_source.setGeometry(QtCore.QRect(20, 60, 351, 20)) + font = QtGui.QFont() + font.setBold(False) + font.setWeight(50) + self.table_source.setFont(font) + self.table_source.setObjectName(_fromUtf8("table_source")) + self.Titre = QtGui.QLabel(CopieDialogBase) + self.Titre.setGeometry(QtCore.QRect(0, 0, 381, 31)) + font = QtGui.QFont() + font.setPointSize(14) + font.setBold(True) + font.setWeight(75) + self.Titre.setFont(font) + self.Titre.setAlignment(QtCore.Qt.AlignCenter) + self.Titre.setObjectName(_fromUtf8("Titre")) + self.table_source_label = QtGui.QLabel(CopieDialogBase) + self.table_source_label.setGeometry(QtCore.QRect(20, 30, 75, 29)) + self.table_source_label.setMaximumSize(QtCore.QSize(75, 16777215)) + self.table_source_label.setObjectName(_fromUtf8("table_source_label")) + + self.retranslateUi(CopieDialogBase) + QtCore.QObject.connect(self.button_box, QtCore.SIGNAL(_fromUtf8("accepted()")), CopieDialogBase.accept) + QtCore.QObject.connect(self.button_box, QtCore.SIGNAL(_fromUtf8("rejected()")), CopieDialogBase.reject) + QtCore.QMetaObject.connectSlotsByName(CopieDialogBase) + + def retranslateUi(self, CopieDialogBase): + CopieDialogBase.setWindowTitle(_translate("CopieDialogBase", "Copie", None)) + self.schema_label.setText(_translate("CopieDialogBase", "Dossier de destination ", None)) + self.label_nom_table.setText(_translate("CopieDialogBase", "Nom de la nouvelle table", None)) + self.annee.setText(_translate("CopieDialogBase", "aaaa", None)) + self.table_vide.setText(_translate("CopieDialogBase", "Cocher cette case pour vider la table de destination", None)) + self.Titre.setText(_translate("CopieDialogBase", "Copie d\'une table", None)) + self.table_source_label.setText(_translate("CopieDialogBase", "Table source ", None)) +