Compare commits

...
12 Commits
Author SHA1 Message Date
tlaveille ebeefb78b9 qgisMaximumVersion = 4.99 2026-03-12 09:06:45 +01:00
tlaveille 256a0ac6d4 maj flux 3.20 2026-03-05 11:42:17 +01:00
tlaveille af10bce94c nom de la variable XML 2026-03-05 11:34:24 +01:00
tlaveille 8c55d902c0 lecture des description dans un tooltip 2026-03-05 10:09:58 +01:00
tlaveille 5a03084daa ouverture des couche flage 00 2026-02-26 15:14:49 +01:00
tlaveille a3b634d514 maj flux 3.19 2026-02-26 14:22:25 +01:00
tlaveille 728f72cffd visualisation des droit des vue et vue materialiser 2026-02-26 11:59:33 +01:00
tlaveille 272b47a174 maj flux 3.18 2026-02-25 11:26:50 +01:00
tlaveille 21a652fdb2 filtre par localisation bug fix 2026-02-25 11:13:35 +01:00
tlaveille 799a635edc mise à jour et ajoue de tool tip. 2026-02-13 15:10:07 +01:00
tlaveille ff2e0f22be commant 2026-02-12 11:41:59 +01:00
tlaveille 8c1ee42080 kill des connection postgresql 2026-02-12 11:05:14 +01:00
10 changed files with 278 additions and 79 deletions
+1
View File
@@ -5,6 +5,7 @@
[general] [general]
name=CenRa_AutoMap name=CenRa_AutoMap
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=CenRa_AutoMap description=CenRa_AutoMap
version=2.8 version=2.8
+1
View File
@@ -9,6 +9,7 @@
[general] [general]
name=CenRA_COPIE name=CenRA_COPIE
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=Permet la copie d'une table dans une base PostGis description=Permet la copie d'une table dans une base PostGis
version=3.1 version=3.1
+215 -67
View File
@@ -1,7 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Module Flux Editor pour le plugin CenRA_FLUX
Permet de gérer et charger des couches depuis les bases de données PostGIS (SIG et REF)
"""
from __future__ import absolute_import from __future__ import absolute_import
# Import the PyQt and QGIS libraries # Import des bibliothèques PyQt et QGIS
from builtins import str from builtins import str
from qgis.PyQt import QtCore, QtGui from qgis.PyQt import QtCore, QtGui
@@ -9,7 +13,17 @@ from qgis.PyQt.QtCore import QSettings
from qgis.PyQt import QtWidgets from qgis.PyQt import QtWidgets
from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtGui import QIcon
from qgis.core import QgsDataSourceUri, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject, QgsSettings, QgsApplication, QgsVectorLayer, QgsRasterLayer, QgsWkbTypes from qgis.core import (
QgsDataSourceUri,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
QgsProject,
QgsSettings,
QgsApplication,
QgsVectorLayer,
QgsRasterLayer,
QgsWkbTypes
)
from qgis.PyQt.QtWidgets import ( from qgis.PyQt.QtWidgets import (
QDialog, QDialog,
QPushButton, QPushButton,
@@ -31,9 +45,11 @@ from qgis.utils import iface
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
# Variable globale pour le mode debug
global DeBUG global DeBUG
DeBUG = 0 DeBUG = 0
# Création des icônes pour les couches raster et vecteur
itemIconRaster = QTableWidgetItem() itemIconRaster = QTableWidgetItem()
icon = QIcon() icon = QIcon()
icon.addPixmap(QtGui.QPixmap(resources_path('icons', 'mIconRaster.svg')), QIcon.Mode(0), QIcon.State(1)) icon.addPixmap(QtGui.QPixmap(resources_path('icons', 'mIconRaster.svg')), QIcon.Mode(0), QIcon.State(1))
@@ -44,28 +60,36 @@ icon = QIcon()
icon.addPixmap(QtGui.QPixmap(resources_path('icons', 'mIconVecteur.svg')), QIcon.Mode(0), QIcon.State(1)) icon.addPixmap(QtGui.QPixmap(resources_path('icons', 'mIconVecteur.svg')), QIcon.Mode(0), QIcon.State(1))
itemIconVecteur.setIcon(icon) itemIconVecteur.setIcon(icon)
# Récupération des informations de connexion à la base de données
try: try:
account = login_base('account') account = login_base('account')
user = account[0] user = account[0] # Nom d'utilisateur
mdp = account[1] mdp = account[1] # Mot de passe
host = account[2] host = account[2] # Hôte de la base de données
port = account[3] port = account[3] # Port de connexion
dbname = account[4] dbname = account[4] # Nom de la base de données
sigdb = account[5] sigdb = account[5] # Base de données SIG
refdb = account[6] refdb = account[6] # Base de données REF
except NameError: except NameError:
print('Fails to login DB for account') print('Fails to login DB for account')
# Chargement de l'interface utilisateur depuis le fichier .ui
EDITOR_CLASS = load_ui('CenRa_Flux_base.ui') EDITOR_CLASS = load_ui('CenRa_Flux_base.ui')
targetCrs = QgsCoordinateReferenceSystem('EPSG:4326') # Configuration des systèmes de coordonnées de référence (CRS)
layerCrs = QgsCoordinateReferenceSystem('EPSG:2154') targetCrs = QgsCoordinateReferenceSystem('EPSG:4326') # WGS84 (coordonnées géographiques)
layerCrs = QgsCoordinateReferenceSystem('EPSG:2154') # Lambert 93 (projection française)
TranformCRS = QgsCoordinateTransform(layerCrs, targetCrs, QgsProject.instance()) TranformCRS = QgsCoordinateTransform(layerCrs, targetCrs, QgsProject.instance())
class Flux_Editor(QDialog, EDITOR_CLASS): class Flux_Editor(QDialog, EDITOR_CLASS):
"""
Classe principale de l'éditeur de flux
Gère l'interface de sélection et de chargement des couches depuis les bases de données
"""
def __init__(self, parent=None): def __init__(self, parent=None):
"""Initialisation de l'interface et des composants"""
_ = parent _ = parent
super().__init__() super().__init__()
self.setupUi(self) self.setupUi(self)
@@ -74,29 +98,36 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.setWindowIcon(QtGui.QIcon(resources_path('icons', 'icon.png'))) self.setWindowIcon(QtGui.QIcon(resources_path('icons', 'icon.png')))
self.first_start = None self.first_start = None
self.iface = iface self.iface = iface
# Configuration des éléments visuels de l'interface
self.label_3.setPixmap(QtGui.QPixmap(resources_path('ui', 'logo.png'))) self.label_3.setPixmap(QtGui.QPixmap(resources_path('ui', 'logo.png')))
self.commandLinkButton.setIcon(QtGui.QIcon(resources_path('ui', 'arrow-bottom.png'))) self.commandLinkButton.setIcon(QtGui.QIcon(resources_path('ui', 'arrow-bottom.png')))
self.commandLinkButton_2.setIcon(QtGui.QIcon(resources_path('ui', 'arrow-up.png'))) self.commandLinkButton_2.setIcon(QtGui.QIcon(resources_path('ui', 'arrow-up.png')))
# Configuration du tableau des couches disponibles
self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger(0)) self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger(0))
self.toolButton.setIcon(QtGui.QIcon(resources_path('ui', 'find.png'))) self.toolButton.setIcon(QtGui.QIcon(resources_path('ui', 'find.png')))
self.toolButton_2.setIcon(QtGui.QIcon(resources_path('ui', 'star.png'))) self.toolButton_2.setIcon(QtGui.QIcon(resources_path('ui', 'star.png')))
# Ajout des options de base de données (SIG ou REF)
self.comboBox_2.addItem("SIG") self.comboBox_2.addItem("SIG")
self.comboBox_2.addItem('REF') self.comboBox_2.addItem('REF')
self.commandLinkButton.clicked.connect(self.selection_flux) # Connexion des signaux aux slots (événements de l'interface)
self.tableWidget.itemDoubleClicked.connect(self.selection_flux) self.commandLinkButton.clicked.connect(self.selection_flux) # Bouton de sélection de flux
self.pushButton_2.clicked.connect(self.limite_flux) self.tableWidget.itemDoubleClicked.connect(self.selection_flux) # Double-clic sur une couche
self.commandLinkButton_2.clicked.connect(self.suppression_flux) self.pushButton_2.clicked.connect(self.limite_flux) # Bouton de chargement
self.tableWidget_2.itemDoubleClicked.connect(self.suppression_flux) self.commandLinkButton_2.clicked.connect(self.suppression_flux) # Bouton de suppression
self.comboBox_2.currentIndexChanged.connect(self.bd_source) self.tableWidget_2.itemDoubleClicked.connect(self.suppression_flux) # Double-clic pour supprimer
self.comboBox_2.currentIndexChanged.connect(self.bd_source) # Changement de base de données
self.checkBox.hide() self.checkBox.hide()
self.toolButton.clicked.connect(self.getCanevas) self.toolButton.clicked.connect(self.getCanevas) # Filtrer par emprise du canevas
self.toolButton_2.clicked.connect(self.filtre_favorit) self.toolButton.setToolTip('Filtrer uniquement les couche Contour/Habitat/Travaux \nqui ce trouve dans le canvas de la carte.')
self.toolButton_2.clicked.connect(self.filtre_favorit) # Filtrer par favoris
self.toolButton_2.setToolTip('Afficher uniquement les favoris.')
layout = QVBoxLayout() layout = QVBoxLayout()
self.lineEdit.textChanged.connect(self.filtre_dynamique) self.lineEdit.textChanged.connect(self.filtre_dynamique) # Recherche dynamique
layout.addWidget(self.lineEdit) layout.addWidget(self.lineEdit)
self.viewer.hide() self.viewer.hide()
# Configuration du menu de debug (caché par défaut)
self.DeBUG.addItem('') self.DeBUG.addItem('')
self.DeBUG.addItem('Dev') self.DeBUG.addItem('Dev')
self.DeBUG.addItem('01') self.DeBUG.addItem('01')
@@ -105,13 +136,14 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.comboBox.currentIndexChanged.connect(self.initialisation_flux) self.comboBox.currentIndexChanged.connect(self.initialisation_flux)
self.DeBUG.currentIndexChanged.connect(self.SwitchDEBUG) self.DeBUG.currentIndexChanged.connect(self.SwitchDEBUG)
self.DeBUG.hide() self.DeBUG.hide() # Menu debug caché par défaut
def raise_(self): def raise_(self):
"""Run method that performs all the real work""" """Run method that performs all the real work"""
self.bd_source() self.bd_source()
def SwitchDEBUG(self): def SwitchDEBUG(self):
"""Change les paramètres de connexion selon le mode debug sélectionné"""
try: try:
global user, mdp, host, port, dbname, sigdb, refdb global user, mdp, host, port, dbname, sigdb, refdb
account = login_base(self.DeBUG.currentText()) account = login_base(self.DeBUG.currentText())
@@ -131,10 +163,12 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.DeBUG.show() self.DeBUG.show()
def mousePressEvent(self, event): def mousePressEvent(self, event):
"""Détecte les clics sur le logo pour activer le mode debug (3 clics consécutifs)"""
global DeBUG global DeBUG
# Zone du logo (coordonnées x: 330-560, y: 5-75)
if 330 <= event.pos().x() <= 560 and 5 <= event.pos().y() <= 75: if 330 <= event.pos().x() <= 560 and 5 <= event.pos().y() <= 75:
DeBUG = DeBUG + 1 DeBUG = DeBUG + 1
if DeBUG == 3: if DeBUG == 3: # Activation après 3 clics
DeBUG = 0 DeBUG = 0
self.ModeDeBUG() self.ModeDeBUG()
else: else:
@@ -142,20 +176,23 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.DeBUG.hide() self.DeBUG.hide()
def bd_source(self): def bd_source(self):
"""Sélectionne la base de données source (SIG ou REF)"""
self.activateWindow() self.activateWindow()
bd_origine = self.comboBox_2.currentText() bd_origine = self.comboBox_2.currentText()
if bd_origine == 'REF': if bd_origine == 'REF':
self.run_ref() self.run_ref() # Connexion à la base REF
if bd_origine == 'SIG': if bd_origine == 'SIG':
self.run_sig() self.run_sig() # Connexion à la base SIG
def run_ref(self): def run_ref(self):
"""Run method that performs all the real work""" """Initialise la connexion à la base de données REF"""
# Vider le tableau des flux sélectionnés
while self.tableWidget_2.rowCount() > 0: while self.tableWidget_2.rowCount() > 0:
self.tableWidget_2.removeRow(self.tableWidget_2.rowCount() - 1) self.tableWidget_2.removeRow(self.tableWidget_2.rowCount() - 1)
global cur, con, dbtype global cur, con, dbtype
dbtype = refdb dbtype = refdb
# Connexion à la base de données REF
con = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + dbtype + " user=" + user + " password=" + mdp) con = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + dbtype + " user=" + user + " password=" + mdp)
cur = con.cursor(cursor_factory=psycopg2.extras.DictCursor) cur = con.cursor(cursor_factory=psycopg2.extras.DictCursor)
self.combobox_custom() self.combobox_custom()
@@ -172,11 +209,13 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
pass pass
def run_sig(self): def run_sig(self):
"""Run method that performs all the real work""" """Initialise la connexion à la base de données SIG"""
# Vider le tableau des flux sélectionnés
while self.tableWidget_2.rowCount() > 0: while self.tableWidget_2.rowCount() > 0:
self.tableWidget_2.removeRow(self.tableWidget_2.rowCount() - 1) self.tableWidget_2.removeRow(self.tableWidget_2.rowCount() - 1)
global cur, con, dbtype global cur, con, dbtype
dbtype = sigdb dbtype = sigdb
# Connexion à la base de données SIG
con = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + dbtype + " user=" + user + " password=" + mdp) con = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + dbtype + " user=" + user + " password=" + mdp)
cur = con.cursor(cursor_factory=psycopg2.extras.DictCursor) cur = con.cursor(cursor_factory=psycopg2.extras.DictCursor)
self.combobox_custom() self.combobox_custom()
@@ -189,29 +228,46 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.tableWidget_2.removeRow(self.tableWidget_2.currentRow()) self.tableWidget_2.removeRow(self.tableWidget_2.currentRow())
def AddOrDelToUserFav(self): def AddOrDelToUserFav(self):
"""Ajoute ou supprime une couche des favoris de l'utilisateur"""
selected_items = self.tableWidget.selectedItems() selected_items = self.tableWidget.selectedItems()
favorit_statut = selected_items[4].text() favorit_statut = selected_items[4].text() # 0 = non favori, 1 = favori
selected_items[4].tableWidget().removeCellWidget(selected_items[4].row(), 4) selected_items[4].tableWidget().removeCellWidget(selected_items[4].row(), 4)
schema_name = selected_items[2].text() schema_name = selected_items[2].text()
table_name = selected_items[3].text() table_name = selected_items[3].text()
# Ajout aux favoris
if favorit_statut == "0": if favorit_statut == "0":
FAV = "mStarIconDel.png" FAV = "mStarIconDel.png" # Icône étoile pleine
selected_items[4].setText("1") selected_items[4].setText("1")
# Requête SQL pour ajouter aux favoris
SQLAddFav = """INSERT INTO admin_sig.favtable (utilisateur, schema_name, table_name) VALUES ('""" + user + "','" + schema_name + "','" + table_name + """');""" SQLAddFav = """INSERT INTO admin_sig.favtable (utilisateur, schema_name, table_name) VALUES ('""" + user + "','" + schema_name + "','" + table_name + """');"""
if dbtype == sigdb: if dbtype == sigdb:
cur.execute(SQLAddFav) cur.execute(SQLAddFav)
con.commit() con.commit()
else:
conSIG = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + sigdb + " user=" + user + " password=" + mdp)
curSIG = conSIG.cursor(cursor_factory=psycopg2.extras.DictCursor)
curSIG.execute(SQLAddFav)
conSIG.commit()
conSIG.close()
# Suppression des favoris
else: else:
FAV = "mStarIconAdd.png" FAV = "mStarIconAdd.png" # Icône étoile vide
selected_items[4].setText("0") selected_items[4].setText("0")
# Requête SQL pour supprimer des favoris
SQLDelFav = """DELETE FROM admin_sig.favtable WHERE utilisateur LIKE '""" + user + """' AND schema_name LIKE '""" + schema_name + """' AND table_name LIKE '""" + table_name + """';""" SQLDelFav = """DELETE FROM admin_sig.favtable WHERE utilisateur LIKE '""" + user + """' AND schema_name LIKE '""" + schema_name + """' AND table_name LIKE '""" + table_name + """';"""
if dbtype == sigdb: if dbtype == sigdb:
cur.execute(SQLDelFav) cur.execute(SQLDelFav)
con.commit() con.commit()
else:
conSIG = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + sigdb + " user=" + user + " password=" + mdp)
curSIG = conSIG.cursor(cursor_factory=psycopg2.extras.DictCursor)
curSIG.execute(SQLDelFav)
conSIG.commit()
conSIG.close()
iconFav = QIcon() iconFav = QIcon()
iconFav.addPixmap(QtGui.QPixmap(resources_path('icons', FAV)), QIcon.Mode(0), QIcon.State(1)) iconFav.addPixmap(QtGui.QPixmap(resources_path('icons', FAV)), QIcon.Mode(0), QIcon.State(1))
@@ -220,30 +276,39 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.FavButton.clicked.connect(self.AddOrDelToUserFav) self.FavButton.clicked.connect(self.AddOrDelToUserFav)
def initialisation_flux(self): def initialisation_flux(self):
"""Initialise et remplit le tableau des couches disponibles"""
if self.toolButton_2.text() == "1":
self.filtre_favorit(None)
self.tableWidget.clear() self.tableWidget.clear()
if NoSignals == 0: if NoSignals == 0: # Éviter les boucles infinies lors des mises à jour
if dbtype == sigdb: if dbtype == sigdb:
if self.comboBox.currentText() == 'toutes les catégories': if self.comboBox.currentText() == 'toutes les catégories':
custom_list = schemaname_list custom_list = schemaname_list
elif self.comboBox.currentText() == 'travaux': elif self.comboBox.currentText() == 'travaux':
custom_list = """(SELECT schemaname,tablename from pg_catalog.pg_tables custom_list = schemaname_list[:-2] + """
where schemaname like '""" + str(self.comboBox.currentText()) + """%' order by schemaname,tablename) UNION (SELECT schemaname,matviewname AS tablename FROM pg_catalog.pg_matviews where schemaname like '""" + str(self.comboBox.currentText()) + """%' order by schemaname,tablename) order by schemaname,tablename;""" WHERE schemaname LIKE '""" + str(self.comboBox.currentText()) + """%';
"""
else: else:
custom_list = """(SELECT schemaname,tablename from pg_catalog.pg_tables custom_list = schemaname_list[:-2] + """
where schemaname like '\\_""" + str(self.comboBox.currentText()) + """%' order by schemaname,tablename) UNION (SELECT schemaname,matviewname AS tablename FROM pg_catalog.pg_matviews where schemaname like '\\_""" + str(self.comboBox.currentText()) + """%' order by schemaname,tablename) order by schemaname,tablename;""" WHERE schemaname LIKE '_""" + str(self.comboBox.currentText()) + """%';
"""
else: else:
if self.comboBox.currentText() == 'toutes les catégories': if self.comboBox.currentText() == 'toutes les catégories':
custom_list = schemaname_list_ref custom_list = schemaname_list_ref
else: else:
custom_list = """SELECT schemaname, tablename from pg_catalog.pg_tables WHERE schemaname LIKE '""" + str(self.comboBox.currentText()) + """' AND tablename NOT LIKE 'qgis_projects' order by schemaname, tablename;""" custom_list = schemaname_list_ref[:-2] + """
WHERE schemaname LIKE '""" + str(self.comboBox.currentText()) + """%' AND tablename NOT LIKE 'qgis_projects';
"""
cur.execute(custom_list) cur.execute(custom_list)
list_schema = cur.fetchall() list_schema = cur.fetchall()
# Vérification de la présence de couches raster dans la base
SQLcountRaster = """SELECT schemaname,viewname FROM pg_catalog.pg_views SQLcountRaster = """SELECT schemaname,viewname FROM pg_catalog.pg_views
WHERE schemaname LIKE 'public' AND viewname LIKE 'raster_columns';""" WHERE schemaname LIKE 'public' AND viewname LIKE 'raster_columns';"""
cur.execute(SQLcountRaster) cur.execute(SQLcountRaster)
RasterIF = len(cur.fetchall()) RasterIF = len(cur.fetchall())
# Récupération de la liste des couches raster si disponibles
if RasterIF == 1: if RasterIF == 1:
SQLloadRaster = """SELECT concat(r_table_schema,'.',r_table_name) from public.raster_columns; """ SQLloadRaster = """SELECT concat(r_table_schema,'.',r_table_name) from public.raster_columns; """
cur.execute(SQLloadRaster) cur.execute(SQLloadRaster)
@@ -254,6 +319,7 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
else: else:
RasterList = [] RasterList = []
# Récupération des projets QGIS stockés dans la base de données
SQLprojects = """SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE tablename LIKE 'qgis_projects'""" SQLprojects = """SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE tablename LIKE 'qgis_projects'"""
cur.execute(SQLprojects) cur.execute(SQLprojects)
list_projects = cur.fetchall() list_projects = cur.fetchall()
@@ -264,33 +330,65 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
cur.execute(SQLProjectsQgis) cur.execute(SQLProjectsQgis)
list_projects_qgis.append(cur.fetchall()) list_projects_qgis.append(cur.fetchall())
# Récupération des droits d'accès de l'utilisateur sur les tables
SQLGrands = """
WITH t as (SELECT
n.nspname AS table_schema,
c.relname AS table_name,
r.rolname AS grantee,
CASE
WHEN acl_text LIKE '%r%' THEN 'SELECT'
WHEN acl_text LIKE '%w%' THEN 'UPDATE'
WHEN acl_text LIKE '%a%' THEN 'INSERT'
WHEN acl_text LIKE '%d%' THEN 'DELETE'
WHEN acl_text LIKE '%x%' THEN 'REFERENCES'
ELSE 'OTHER'
END AS privilege_type
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN LATERAL unnest(c.relacl) AS acl_item(acl) ON TRUE
LEFT JOIN LATERAL (
SELECT acl::text AS acl_text,
split_part(acl::text, '=', 1) AS grantee_name
) priv ON TRUE
LEFT JOIN pg_roles r ON r.rolname = priv.grantee_name
WHERE c.relkind IN ('r', 'v','m'))
"""
if self.comboBox.currentText() == 'toutes les catégories': if self.comboBox.currentText() == 'toutes les catégories':
SQLGrands = """SELECT concat(table_schema,'.',table_name) FROM information_schema.role_table_grants WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT';""" SQLGrands = SQLGrands + """SELECT concat(table_schema,'.',table_name) FROM t WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT';"""
else: else:
if dbtype == sigdb: if dbtype == sigdb:
SQLGrands = """SELECT concat(table_schema,'.',table_name) FROM information_schema.role_table_grants WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT' AND table_schema LIKE '_""" + str(self.comboBox.currentText()) + """_%';""" SQLGrands = SQLGrands + """SELECT concat(table_schema,'.',table_name) FROM t WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT' AND table_schema LIKE '_""" + str(self.comboBox.currentText()) + """_%';"""
elif dbtype == refdb: elif dbtype == refdb:
SQLGrands = """SELECT concat(table_schema,'.',table_name) FROM information_schema.role_table_grants WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT' AND table_schema LIKE '""" + str(self.comboBox.currentText()) + """%';""" SQLGrands = SQLGrands + """SELECT concat(table_schema,'.',table_name) FROM t WHERE grantee in(SELECT rolname FROM pg_catalog.pg_roles WHERE oid in(SELECT roleid FROM pg_auth_members WHERE member = (SELECT usesysid FROM pg_catalog.pg_user WHERE usename = '""" + user + """'))) and privilege_type = 'SELECT' AND table_schema LIKE '""" + str(self.comboBox.currentText()) + """%';"""
cur.execute(SQLGrands) cur.execute(SQLGrands)
list_grands = cur.fetchall() list_grands = cur.fetchall()
GrandUser = [] GrandUser = []
for grandsFind in list_grands: for grandsFind in list_grands:
GrandUser.append(grandsFind[0]) GrandUser.append(grandsFind[0])
if dbtype == sigdb: # Récupération de la liste des favoris de l'utilisateur
SQLFavTable = "SELECT concat(schema_name, '.', table_name) FROM admin_sig.favtable WHERE utilisateur LIKE '" + user + "';" SQLFavTable = "SELECT concat(schema_name, '.', table_name) FROM admin_sig.favtable WHERE utilisateur LIKE '" + user + "';"
if dbtype == refdb:
conSIG = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + sigdb + " user=" + user + " password=" + mdp)
curSIG = conSIG.cursor(cursor_factory=psycopg2.extras.DictCursor)
curSIG.execute(SQLFavTable)
list_fav = curSIG.fetchall()
conSIG.close()
print('SIG')
else:
cur.execute(SQLFavTable) cur.execute(SQLFavTable)
list_fav = cur.fetchall() list_fav = cur.fetchall()
FavList = [] FavList = []
for favFind in list_fav: for favFind in list_fav:
FavList.append(favFind[0]) FavList.append(favFind[0])
else:
FavList = []
# Remplissage du tableau avec les couches disponibles
self.tableWidget.setRowCount(len(list_schema)) self.tableWidget.setRowCount(len(list_schema))
self.tableWidget.setColumnCount(5) self.tableWidget.setColumnCount(5)
i = 0 i = 0
for value in list_schema: for value in list_schema:
MetadataXML = value[2]
if dbtype == sigdb: if dbtype == sigdb:
type_val = str(value[0])[1:3] type_val = str(value[0])[1:3]
schema_name = str(value[0])[4:] schema_name = str(value[0])[4:]
@@ -299,6 +397,8 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
table_name = str(value[1][len(value[0]) + 1:]) table_name = str(value[1][len(value[0]) + 1:])
else: else:
table_name = str(value[1]) table_name = str(value[1])
if type_val == "00":
table_name = value[1]
else: else:
type_val = '' type_val = ''
schema_name = str(value[0]) schema_name = str(value[0])
@@ -330,8 +430,6 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
iconFav.addPixmap(QtGui.QPixmap(resources_path('icons', FAV)), QIcon.Mode(0), QIcon.State(1)) iconFav.addPixmap(QtGui.QPixmap(resources_path('icons', FAV)), QIcon.Mode(0), QIcon.State(1))
self.FavButton = QPushButton(iconFav, "") self.FavButton = QPushButton(iconFav, "")
self.FavButton.clicked.connect(self.AddOrDelToUserFav) self.FavButton.clicked.connect(self.AddOrDelToUserFav)
if dbtype == refdb:
self.FavButton.setEnabled(False)
if (str(value[0]) + '.' + str(value[1])) in RasterList: if (str(value[0]) + '.' + str(value[1])) in RasterList:
SVG = 'mIconRaster.svg' SVG = 'mIconRaster.svg'
@@ -354,9 +452,12 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.tableWidget.setItem(i, 4, item) self.tableWidget.setItem(i, 4, item)
self.tableWidget.setCellWidget(i, 4, self.FavButton) self.tableWidget.setCellWidget(i, 4, self.FavButton)
if (str(value[0]) + '.' + str(value[1])) in GrandUser: # Coloration des lignes selon les droits d'accès
pass if (str(value[0]) + '.' + str(value[1])) in GrandUser: # L'utilisateur a les droits
for j in range(self.tableWidget.columnCount()):
self.tableWidget.item(i, j).setToolTip(MetadataXML)
else: else:
# Coloration en violet si droits insuffisants
for j in range(self.tableWidget.columnCount()): for j in range(self.tableWidget.columnCount()):
self.tableWidget.item(i, j).setBackground(QtGui.QColor(187, 134, 192, 50)) self.tableWidget.item(i, j).setBackground(QtGui.QColor(187, 134, 192, 50))
self.tableWidget.item(i, j).setToolTip('Droit insuffisant pour ouvrire la couche !') self.tableWidget.item(i, j).setToolTip('Droit insuffisant pour ouvrire la couche !')
@@ -424,6 +525,7 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.filtre_dynamique(self.lineEdit.text()) self.filtre_dynamique(self.lineEdit.text())
def selection_flux(self): def selection_flux(self):
"""Ajoute une couche sélectionnée à la liste des flux à charger"""
selected_row = 0 selected_row = 0
selected_items = self.tableWidget.selectedItems() selected_items = self.tableWidget.selectedItems()
@@ -452,7 +554,8 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
return len(existing_items) > 0 return len(existing_items) > 0
def limite_flux(self): def limite_flux(self):
"""Vérifie le nombre de flux à charger et affiche un avertissement si > 5"""
# Avertissement si plus de 5 couches sélectionnées (risque de performance)
if self.tableWidget_2.rowCount() > 5: if self.tableWidget_2.rowCount() > 5:
self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !", self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !",
"Le nombre de flux à charger en une seule fois est limité à 5 pour des questions de performances. Souhaitez vous tout de même charger les " + str( "Le nombre de flux à charger en une seule fois est limité à 5 pour des questions de performances. Souhaitez vous tout de même charger les " + str(
@@ -468,6 +571,7 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.chargement_flux() self.chargement_flux()
def chargement_flux(self): def chargement_flux(self):
"""Charge les couches sélectionnées dans QGIS"""
managerAU = QgsApplication.authManager() managerAU = QgsApplication.authManager()
managerAU.availableAuthMethodConfigs().keys() managerAU.availableAuthMethodConfigs().keys()
@@ -481,6 +585,7 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
} }
return switcher.get(type, "nothing") return switcher.get(type, "nothing")
# Vérification de la présence de l'extension PostGIS Raster
SQLloadRaster = """SELECT concat(r_table_schema,'.',r_table_name) from public.raster_columns; """ SQLloadRaster = """SELECT concat(r_table_schema,'.',r_table_name) from public.raster_columns; """
SQLextension = """SELECT count(extname) FROM pg_catalog.pg_extension WHERE extname LIKE 'postgis_raster';""" SQLextension = """SELECT count(extname) FROM pg_catalog.pg_extension WHERE extname LIKE 'postgis_raster';"""
@@ -497,10 +602,10 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
for rasterFind in list_raster: for rasterFind in list_raster:
RasterList.append(rasterFind[0]) RasterList.append(rasterFind[0])
# Chargement de chaque couche sélectionnée
for row in range(0, self.tableWidget_2.rowCount()): for row in range(0, self.tableWidget_2.rowCount()):
color_rgba_db = 855030089 color_rgba_db = 855030089 # Code couleur pour couche dans autre BD
color_rgba_droit = 851150528 color_rgba_droit = 851150528 # Code couleur pour droits insuffisants
print(self.tableWidget_2.item(row, 1).background().color().rgba())
if self.tableWidget_2.item(row, 1).background().color().rgba() == color_rgba_droit: if self.tableWidget_2.item(row, 1).background().color().rgba() == color_rgba_droit:
self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !", "Vous ne disposez pas des droit pour la couche «" + str(self.tableWidget_2.item(row, 1).text()) + ' ' + str(self.tableWidget_2.item(row, 2).text()) + "» !", QMessageBox.StandardButton(0x00004000)) self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !", "Vous ne disposez pas des droit pour la couche «" + str(self.tableWidget_2.item(row, 1).text()) + ' ' + str(self.tableWidget_2.item(row, 2).text()) + "» !", QMessageBox.StandardButton(0x00004000))
elif self.tableWidget_2.item(row, 1).background().color().rgba() != color_rgba_db: elif self.tableWidget_2.item(row, 1).background().color().rgba() != color_rgba_db:
@@ -520,26 +625,30 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
code = self.tableWidget_2.item(row, 1).text() code = self.tableWidget_2.item(row, 1).text()
schema = self.tableWidget_2.item(row, 2).text() schema = self.tableWidget_2.item(row, 2).text()
table = self.tableWidget_2.item(row, 3).text() table = self.tableWidget_2.item(row, 3).text()
# Configuration de l'URI de connexion PostGIS
uri = QgsDataSourceUri() uri = QgsDataSourceUri()
uri.setConnection(host, port, dbtype, user, mdp) uri.setConnection(host, port, dbtype, user, mdp)
# nom du schéma à remplacer: "hydrographie" à supprimer et mettre "couches_collaboratives" lorsqu'on aura regroupé les couches à modifier dans un même
# Chargement selon le type de couche (raster, vecteur ou projet QGIS)
if (schema + '.' + table) in RasterList: if (schema + '.' + table) in RasterList:
# Chargement d'une couche raster
uri.setDataSource(schema, table, "rast") uri.setDataSource(schema, table, "rast")
uri.setKeyColumn('rid') uri.setKeyColumn('rid') # Clé primaire pour les rasters
uri.setSrid('2154') uri.setSrid('2154') # Lambert 93
layer = QgsRasterLayer(uri.uri(), table, "postgresraster") layer = QgsRasterLayer(uri.uri(), table, "postgresraster")
QgsProject.instance().addMapLayer(layer) QgsProject.instance().addMapLayer(layer)
elif code == 'qgis': elif code == 'qgis':
# Chargement d'un projet QGIS stocké dans la base de données
schema = self.tableWidget_2.item(row, 2).text() schema = self.tableWidget_2.item(row, 2).text()
print(schema)
table = self.tableWidget_2.item(row, 3).text() table = self.tableWidget_2.item(row, 3).text()
uri_project = 'postgresql://' + user + ':' + mdp + '@' + host + ':' + port + '?sslmode=disable&dbname=' + dbtype + "&schema=" + schema + '&project=' + table uri_project = 'postgresql://' + user + ':' + mdp + '@' + host + ':' + port + '?sslmode=disable&dbname=' + dbtype + "&schema=" + schema + '&project=' + table
QgsProject.instance().read(uri_project) QgsProject.instance().read(uri_project)
else: else:
uri.setDataSource(schema, table, "geom") # Chargement d'une couche vecteur
uri.setKeyColumn('gid') uri.setDataSource(schema, table, "geom") # Colonne géométrie
uri.setKeyColumn('gid') # Clé primaire
# Chargement de la couche PostGIS # Détection du type de géométrie
geom_type = 'SELECT right(st_geometrytype(geom),-3) as a FROM ' + schema + '.' + table + ' GROUP BY a' geom_type = 'SELECT right(st_geometrytype(geom),-3) as a FROM ' + schema + '.' + table + ' GROUP BY a'
cur.execute(geom_type) cur.execute(geom_type)
list_typegeom = cur.fetchall() list_typegeom = cur.fetchall()
@@ -570,8 +679,9 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !", "La couche «" + str(self.tableWidget_2.item(row, 1).text()) + ' ' + str(self.tableWidget_2.item(row, 2).text()) + "» ne ce trouve pas dans cette BD !", QMessageBox.StandardButton(0x00004000)) self.QMBquestion = QMessageBox.question(iface.mainWindow(), u"Attention !", "La couche «" + str(self.tableWidget_2.item(row, 1).text()) + ' ' + str(self.tableWidget_2.item(row, 2).text()) + "» ne ce trouve pas dans cette BD !", QMessageBox.StandardButton(0x00004000))
def combobox_custom(self): def combobox_custom(self):
"""Remplit la liste déroulante des catégories selon la base de données sélectionnée"""
global NoSignals global NoSignals
NoSignals = 1 NoSignals = 1 # Désactive temporairement les signaux pour éviter les boucles
if dbtype == sigdb: if dbtype == sigdb:
self.toolButton.setEnabled(1) self.toolButton.setEnabled(1)
self.comboBox.clear() self.comboBox.clear()
@@ -598,7 +708,8 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
NoSignals = 0 NoSignals = 0
def filtre_favorit(self, filter_fav): def filtre_favorit(self, filter_fav):
if self.toolButton_2.text() == "0": """Active ou désactive le filtre des favoris"""
if self.toolButton_2.text() == "0": # Activation du filtre
self.toolButton_2.setText("1") self.toolButton_2.setText("1")
self.toolButton_2.setDown(True) self.toolButton_2.setDown(True)
if self.lineEdit.text() != 'Recherche par mots-clés': if self.lineEdit.text() != 'Recherche par mots-clés':
@@ -618,6 +729,8 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.tableWidget.setRowHidden(i, False) self.tableWidget.setRowHidden(i, False)
def filtre_dynamique(self, filter_text): def filtre_dynamique(self, filter_text):
"""Filtre dynamique des couches selon le texte saisi"""
# Remplacement des espaces par des underscores pour la recherche
if filter_text.find(' ') >= 0: if filter_text.find(' ') >= 0:
filter_text = filter_text.replace(" ", "_") filter_text = filter_text.replace(" ", "_")
for i in range(self.tableWidget.rowCount()): for i in range(self.tableWidget.rowCount()):
@@ -633,6 +746,24 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
break break
def getCanevas(self): def getCanevas(self):
"""Filtre les couches selon l'emprise du canevas QGIS actuel"""
# Récupération de la liste des favoris de l'utilisateur
SQLFavTable = "SELECT concat(schema_name, '.', table_name) FROM admin_sig.favtable WHERE utilisateur LIKE '" + user + "';"
if dbtype == refdb:
conSIG = psycopg2.connect("host=" + host + " port=" + port + " dbname=" + sigdb + " user=" + user + " password=" + mdp)
curSIG = conSIG.cursor(cursor_factory=psycopg2.extras.DictCursor)
curSIG.execute(SQLFavTable)
list_fav = curSIG.fetchall()
conSIG.close()
else:
cur.execute(SQLFavTable)
list_fav = cur.fetchall()
FavList = []
for favFind in list_fav:
FavList.append(favFind[0])
# Récupération de l'emprise du canevas
poly = iface.mapCanvas().extent() poly = iface.mapCanvas().extent()
geom = (str(poly.xMinimum()) + ',' + str(poly.yMinimum()) + ',' + str(poly.xMaximum()) + ',' + str(poly.yMaximum())) geom = (str(poly.xMinimum()) + ',' + str(poly.yMinimum()) + ',' + str(poly.xMaximum()) + ',' + str(poly.yMaximum()))
@@ -664,13 +795,28 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
TableSomme = cur.fetchall()[0][0] TableSomme = cur.fetchall()[0][0]
if e[0][1:3] != 'fo': if e[0][1:3] != 'fo':
schema_name = e[0][4:]
DepName = QTableWidgetItem(e[0][1:3]) DepName = QTableWidgetItem(e[0][1:3])
SchemaName = QTableWidgetItem(e[0][4:]) SchemaName = QTableWidgetItem(schema_name)
else: else:
schema_name = e[0][6:]
DepName = QTableWidgetItem('form') DepName = QTableWidgetItem('form')
SchemaName = QTableWidgetItem(e[0][6:]) SchemaName = QTableWidgetItem(schema_name)
TableName = QTableWidgetItem(e[1][len(e[0]) + 1:]) table_name = e[1][len(e[0]) + 1:]
FavoritStatut = QTableWidgetItem('FavIcon') TableName = QTableWidgetItem(table_name)
FavStatut = 0
if (schema_name + '.' + table_name) not in FavList:
FAV = "mStarIconAdd.png"
FavStatut = 0
else:
FAV = "mStarIconDel.png"
FavStatut = 1
iconFav = QIcon()
iconFav.addPixmap(QtGui.QPixmap(resources_path('icons', FAV)), QIcon.Mode(0), QIcon.State(1))
self.FavButton = QPushButton(iconFav, "")
self.FavButton.clicked.connect(self.AddOrDelToUserFav)
self.tableWidget.insertRow(row_count) self.tableWidget.insertRow(row_count)
itemIcon = QTableWidgetItem() itemIcon = QTableWidgetItem()
@@ -682,7 +828,9 @@ class Flux_Editor(QDialog, EDITOR_CLASS):
self.tableWidget.setItem(row_count, 1, DepName) self.tableWidget.setItem(row_count, 1, DepName)
self.tableWidget.setItem(row_count, 2, SchemaName) self.tableWidget.setItem(row_count, 2, SchemaName)
self.tableWidget.setItem(row_count, 3, TableName) self.tableWidget.setItem(row_count, 3, TableName)
self.tableWidget.setItem(row_count, 3, FavoritStatut) item = QTableWidgetItem(str(FavStatut))
self.tableWidget.setItem(row_count, 4, item)
self.tableWidget.setCellWidget(row_count, 4, self.FavButton)
if TableSomme == 0: if TableSomme == 0:
for j in range(self.tableWidget.columnCount()): for j in range(self.tableWidget.columnCount()):
self.tableWidget.item(row_count, j).setBackground(QtGui.QColor(246, 185, 73, 50)) self.tableWidget.item(row_count, j).setBackground(QtGui.QColor(246, 185, 73, 50))
+3 -2
View File
@@ -5,9 +5,10 @@
[general] [general]
name=CenRa_FLUX name=CenRa_FLUX
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=Permet d'ouvrire une table dans la base PostGis description=Permet d'ouvrire une table dans la base PostGis
version=3.16 version=3.20
author=Conservatoire d'Espaces Naturels de Rhône-Alpes author=Conservatoire d'Espaces Naturels de Rhône-Alpes
email=si_besoin@cen-rhonealpes.fr email=si_besoin@cen-rhonealpes.fr
@@ -32,7 +33,7 @@ icon=icon.png
# experimental flag # experimental flag
experimental=False experimental=False
changelog=<h2>CenRa_FLUX:</h2></br><p><h3>11/02/2026 - Version 3.16: </h3> - Creation de favori et visualisation.</p></br><p><h3>10/02/2026 - Version 3.15: </h3> - Sauvegarde la recherche.</p></br><p><h3>08/12/2025 - Version 3.14: </h3> - message d'erreur pour les drois de couche sur la DB.</p></br><p><h3>08/12/2025 - Version 3.13: </h3> - Detection des droit utilisateur.</p></br><p><h3>25/09/2025 - Version 3.12: </h3> - version +1.</p></br><p><h3>25/09/2025 - Version 3.11: </h3> - Correctife sur les code 00.</p></br><p><h3>24/09/2025 - Version 3.10: </h3> - Erreur sur l ouverture des couche raster. </p></br><p><h3>24/09/2025 - Version 3.9: </h3> - bugfix lier aux extention pgsql.</p></br><p><h3>09/09/2025 - Version 3.8: </h3> - Bug REF fix.</p></br><p><h3>05/09/2025 - Version 3.7: </h3> - Ouverture de projet QGIS contenue dans la base de donnees.</p></br><p><h3>30/07/2025 - Version 3.6: </h3> - Correctife de bug.</p></br><p><h3>29/07/2025 - Version 3.5: </h3> - Bug fix sur les donnee raster.</p></br><p><h3>23/07/2025 - Version 3.4: </h3> - Ouverture raster dans la base SIG.</p></br><p><h3>23/07/2025 - Version 3.3: </h3> - Optimisation des chargement.</p></br><p><h3>22/07/2025 - Version 3.2: </h3> - Visualisation des format raster et vecteur dans REF.</p></br><p><h3>21/07/2025 - Version 3.1: </h3> - Bug fix pour l'ouverture de plus de 5 couches.</p></br><p><h3>19/05/2025 - Version 3.0: </h3> - Compatible PyQt5 et PyQt6.</p></br><p><h3>09/04/2025 - Version 2.9: </h3> - Correctif bug en TT.</p></br><p><h3>09/04/2025 - Version 2.8: </h3> - Optimisation pour le TT.</p></br><p><h3>07/04/2025 - Version 2.7: </h3> - mode debug.</p></br><p><h3>03/04/2025 - Version 2.6: </h3> - Mise a jour de securite.</p></br><p><h3>20/03/2025 - Version 2.5: </h3> - Visualisation distincte des couches ne se trouvant pas dans l'antenne.</p></br><p><h3>13/02/2025 - Version 2.4: </h3> - Ajoute redimensionnement et déplacement mollette.</p></br><p><h3>05/02/2025 - Version 2.3: </h3> - Bouton de visualisation des couches se trouvent uniquement dans le canva de la carte.</p></br><p><h3>07/01/2025 - Version 2.2: </h3> - ByPass du certif ssl ci erreur.</p></br><p><h3>22/10/2024 - Version 2.1:</h3> - Correctif de bug.</br> - Evolution de la limit de 3 à 5. </br></p></br><p><h3>22/10/2024 - Version 2.0:</h3> - Reformatage du code.</br></p></br><p><h3>03/10/2024 - Version 1.14:</h3> - Remonte la fênetre dans la pille.</br></p><p><h3>13/09/2024 - Version 1.13:</h3>- MAJ sur le lien du changelog</br>- Bug-fix: Ouvre MultiPolygone et Polygon séparément.</p></br><p><h3>10/09/2024 - Version 1.11:</h3>- Ouverture de table contenant plusieurs géométries.</p></br><p><h3>26/08/2024 - Version 1.10:</h3>- Ajoute d'un changelog et vérification de mise à jour.</p> changelog=<h2>CenRa_FLUX:</h2></br><p><h3>05/03/2026 - Version 3.20: </h3> - Visualisation des metadonnees avec un tooltip au survol de la couche.</p></br><p><h3>26/02/2026 - Version 3.19: </h3> - Visualisation des droits pour les vues materialisees.</p></br><p><h3>25/02/2026 - Version 3.18: </h3> - Filtre par secteur geographique compatible avec les favoris.</p></br><p><h3>12/02/2026 - Version 3.17: </h3> - Favorit dans la BD Ref.</p></br><p><h3>11/02/2026 - Version 3.16: </h3> - Creation de favori et visualisation.</p></br><p><h3>10/02/2026 - Version 3.15: </h3> - Sauvegarde la recherche.</p></br><p><h3>08/12/2025 - Version 3.14: </h3> - message d'erreur pour les drois de couche sur la DB.</p></br><p><h3>08/12/2025 - Version 3.13: </h3> - Detection des droit utilisateur.</p></br><p><h3>25/09/2025 - Version 3.12: </h3> - version +1.</p></br><p><h3>25/09/2025 - Version 3.11: </h3> - Correctife sur les code 00.</p></br><p><h3>24/09/2025 - Version 3.10: </h3> - Erreur sur l ouverture des couche raster. </p></br><p><h3>24/09/2025 - Version 3.9: </h3> - bugfix lier aux extention pgsql.</p></br><p><h3>09/09/2025 - Version 3.8: </h3> - Bug REF fix.</p></br><p><h3>05/09/2025 - Version 3.7: </h3> - Ouverture de projet QGIS contenue dans la base de donnees.</p></br><p><h3>30/07/2025 - Version 3.6: </h3> - Correctife de bug.</p></br><p><h3>29/07/2025 - Version 3.5: </h3> - Bug fix sur les donnee raster.</p></br><p><h3>23/07/2025 - Version 3.4: </h3> - Ouverture raster dans la base SIG.</p></br><p><h3>23/07/2025 - Version 3.3: </h3> - Optimisation des chargement.</p></br><p><h3>22/07/2025 - Version 3.2: </h3> - Visualisation des format raster et vecteur dans REF.</p></br><p><h3>21/07/2025 - Version 3.1: </h3> - Bug fix pour l'ouverture de plus de 5 couches.</p></br><p><h3>19/05/2025 - Version 3.0: </h3> - Compatible PyQt5 et PyQt6.</p></br><p><h3>09/04/2025 - Version 2.9: </h3> - Correctif bug en TT.</p></br><p><h3>09/04/2025 - Version 2.8: </h3> - Optimisation pour le TT.</p></br><p><h3>07/04/2025 - Version 2.7: </h3> - mode debug.</p></br><p><h3>03/04/2025 - Version 2.6: </h3> - Mise a jour de securite.</p></br><p><h3>20/03/2025 - Version 2.5: </h3> - Visualisation distincte des couches ne se trouvant pas dans l'antenne.</p></br><p><h3>13/02/2025 - Version 2.4: </h3> - Ajoute redimensionnement et déplacement mollette.</p></br><p><h3>05/02/2025 - Version 2.3: </h3> - Bouton de visualisation des couches se trouvent uniquement dans le canva de la carte.</p></br><p><h3>07/01/2025 - Version 2.2: </h3> - ByPass du certif ssl ci erreur.</p></br><p><h3>22/10/2024 - Version 2.1:</h3> - Correctif de bug.</br> - Evolution de la limit de 3 à 5. </br></p></br><p><h3>22/10/2024 - Version 2.0:</h3> - Reformatage du code.</br></p></br><p><h3>03/10/2024 - Version 1.14:</h3> - Remonte la fênetre dans la pille.</br></p><p><h3>13/09/2024 - Version 1.13:</h3>- MAJ sur le lien du changelog</br>- Bug-fix: Ouvre MultiPolygone et Polygon séparément.</p></br><p><h3>10/09/2024 - Version 1.11:</h3>- Ouverture de table contenant plusieurs géométries.</p></br><p><h3>26/08/2024 - Version 1.10:</h3>- Ajoute d'un changelog et vérification de mise à jour.</p>
# deprecated flag (applies to the whole plugin, not just a single version) # deprecated flag (applies to the whole plugin, not just a single version)
deprecated=False deprecated=False
+51 -7
View File
@@ -2,13 +2,57 @@
schemaname_distinct = """SELECT DISTINCT schemaname from pg_catalog.pg_tables schemaname_distinct = """SELECT DISTINCT schemaname from pg_catalog.pg_tables
WHERE schemaname NOT LIKE '_archives' AND schemaname NOT LIKE 'topology' AND schemaname NOT LIKE 'information_schema' AND schemaname NOT LIKE 'pg_catalog' and schemaname NOT LIKE 'public' AND schemaname NOT LIKE '_trier' WHERE schemaname NOT LIKE '_archives' AND schemaname NOT LIKE 'topology' AND schemaname NOT LIKE 'information_schema' AND schemaname NOT LIKE 'pg_catalog' and schemaname NOT LIKE 'public' AND schemaname NOT LIKE '_trier'
order by schemaname;""" order by schemaname;"""
schemaname_list_ref = """SELECT schemaname,tablename from pg_catalog.pg_tables
WHERE schemaname NOT LIKE '_archives' AND schemaname NOT LIKE 'topology' AND schemaname NOT LIKE 'information_schema' AND schemaname NOT LIKE 'pg_catalog' and schemaname NOT LIKE 'public' AND schemaname NOT LIKE '_trier' schemaname_list_ref = """
order by schemaname,tablename;""" WITH TableList AS (SELECT
schemaname_list = """(SELECT schemaname,tablename from pg_catalog.pg_tables schemaname,
where schemaname like 'trav%' or schemaname like '\\_ag%' or schemaname like '\\_00%' or schemaname like '\\_01%' or schemaname like '\\_07%' or schemaname like '\\_26%' or schemaname like '\\_form%' or schemaname like '\\_42%' or schemaname like '\\_69%' order by schemaname,tablename) tablename
UNION FROM pg_catalog.pg_tables
(SELECT schemaname,matviewname AS tablename FROM pg_catalog.pg_matviews order by schemaname,tablename) order by schemaname,tablename;""" WHERE
schemaname NOT LIKE '_archives' AND schemaname NOT LIKE 'topology' AND schemaname NOT LIKE 'information_schema' AND schemaname NOT LIKE 'pg_catalog' and schemaname NOT LIKE 'public' AND schemaname NOT LIKE '_trier'
ORDER BY schemaname,tablename),
TableDescription AS (SELECT
schemaname,
tablename,
description
FROM TableList
LEFT JOIN pg_description ON objoid = (quote_ident(schemaname)||'.'||quote_ident(tablename))::regclass
ORDER BY schemaname, tablename)
SELECT * FROM TableDescription;
"""
schemaname_list = """
WITH
TableList as (SELECT
schemaname,
tablename
FROM pg_catalog.pg_tables
WHERE schemaname LIKE 'trav%' OR schemaname LIKE '_ag%' OR schemaname LIKE '_00%' OR schemaname LIKE '_01%' OR schemaname LIKE '_07%' OR schemaname LIKE '_26%' OR schemaname LIKE '_form%' OR schemaname LIKE '_42%' OR schemaname LIKE '_69%'
ORDER BY schemaname,tablename),
ViewList as (SELECT
schemaname,
matviewname AS tablename
FROM pg_catalog.pg_matviews
ORDER BY schemaname,tablename),
TableAndView as (SELECT
schemaname,
tablename
FROM TableList
UNION SELECT schemaname,tablename FROM ViewList),
TableViewDescription as (SELECT
schemaname,
tablename,
description
FROM TableAndView
LEFT JOIN pg_description ON objoid = (quote_ident(schemaname)||'.'||quote_ident(tablename))::regclass
ORDER BY schemaname,tablename)
SELECT * FROM TableViewDescription;
"""
geom = "geom" geom = "geom"
champ_travaux_prevus_multipolygon = """(gid serial NOT NULL, groupe_gestion text, gestion_lib text, id_gestion text, datedebut date, datefin date, commentaire text, surface_m2 double precision, surface_ha double precision, date_creation date, date_maj date, geom geometry(MultiPolygon,2154))""" champ_travaux_prevus_multipolygon = """(gid serial NOT NULL, groupe_gestion text, gestion_lib text, id_gestion text, datedebut date, datefin date, commentaire text, surface_m2 double precision, surface_ha double precision, date_creation date, date_maj date, geom geometry(MultiPolygon,2154))"""
champ_travaux_prevus_multilinestring = """(gid serial NOT NULL, groupe_gestion text, gestion_lib text, id_gestion text, datedebut date, datefin date, commentaire text, longueur_m double precision, longueur_km double precision, date_creation date, date_maj date, geom geometry(MultiLineString,2154))""" champ_travaux_prevus_multilinestring = """(gid serial NOT NULL, groupe_gestion text, gestion_lib text, id_gestion text, datedebut date, datefin date, commentaire text, longueur_m double precision, longueur_km double precision, date_creation date, date_maj date, geom geometry(MultiLineString,2154))"""
+1
View File
@@ -5,6 +5,7 @@
[general] [general]
name=CenRa_Metabase name=CenRa_Metabase
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=CenRa_METABASE description=CenRa_METABASE
version=0.3.1 version=0.3.1
+1
View File
@@ -5,6 +5,7 @@
[general] [general]
name=CenRa_PageRender name=CenRa_PageRender
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=CenRa_PageRender description=CenRa_PageRender
version=0.2.3 version=0.2.3
+1
View File
@@ -11,6 +11,7 @@
[general] [general]
name=CenRa_POSTGIS name=CenRa_POSTGIS
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=Permet de crée un dossier dans la base PostGis description=Permet de crée un dossier dans la base PostGis
version=3.1 version=3.1
+1
View File
@@ -11,6 +11,7 @@
[general] [general]
name=CenRa SICEN name=CenRa SICEN
qgisMinimumVersion=3.0 qgisMinimumVersion=3.0
qgisMaximumVersion=4.99
supportsQt6=True supportsQt6=True
description=Filtre des données saisies dans SICEN et export d'une liste d'espèces. description=Filtre des données saisies dans SICEN et export d'une liste d'espèces.
version=2.1 version=2.1
+3 -3
View File
@@ -50,9 +50,9 @@
<tags>cenra,sicen</tags> <tags>cenra,sicen</tags>
</pyqgis_plugin> </pyqgis_plugin>
<pyqgis_plugin name="CenRa_FLUX" version="3.16"> <pyqgis_plugin name="CenRa_FLUX" version="3.20">
<description>Depot pour les extensiont QGIS du CEN Rhone-Alpes, sur GitHub.</description> <description>Depot pour les extensiont QGIS du CEN Rhone-Alpes, sur GitHub.</description>
<version>3.16</version> <version>3.20</version>
<qgis_minimum_version>3.16</qgis_minimum_version> <qgis_minimum_version>3.16</qgis_minimum_version>
<homepage>https://plateformesig.cenra-outils.org/</homepage> <homepage>https://plateformesig.cenra-outils.org/</homepage>
<file_name>CenRa_FLUX.zip</file_name> <file_name>CenRa_FLUX.zip</file_name>
@@ -61,7 +61,7 @@
<download_url>https://gitea.cenra-outils.org/CEN-RA/Plugin_QGIS/releases/download/latest/CenRa_FLUX.zip</download_url> <download_url>https://gitea.cenra-outils.org/CEN-RA/Plugin_QGIS/releases/download/latest/CenRa_FLUX.zip</download_url>
<uploaded_by>CEN-Rhone-Alpes</uploaded_by> <uploaded_by>CEN-Rhone-Alpes</uploaded_by>
<create_date>2024-02-06</create_date> <create_date>2024-02-06</create_date>
<update_date>2026-02-11</update_date> <update_date>2026-03-05</update_date>
<experimental>False</experimental> <experimental>False</experimental>
<deprecated>False</deprecated> <deprecated>False</deprecated>
<tags>cenra,flux</tags> <tags>cenra,flux</tags>