From 02681490f6f4ff760a6937da1f285443153e92a4 Mon Sep 17 00:00:00 2001
From: tlaveille
Date: Fri, 9 Jan 2026 11:16:56 +0100
Subject: [PATCH] Repaire
---
CenRa_METABASE/CenRa_Metabase.py | 217 ++
CenRa_METABASE/README.md | 1 +
CenRa_METABASE/__init__.py | 10 +
CenRa_METABASE/about_form.py | 46 +
CenRa_METABASE/dock.py | 453 ++++
CenRa_METABASE/editor.py | 775 +++++++
CenRa_METABASE/icon.png | Bin 0 -> 4977 bytes
CenRa_METABASE/issues.py | 89 +
CenRa_METABASE/metadata.txt | 49 +
CenRa_METABASE/tools/css/dock.css | 61 +
CenRa_METABASE/tools/html/contact.html | 7 +
CenRa_METABASE/tools/html/link.html | 7 +
CenRa_METABASE/tools/html/main.html | 123 ++
CenRa_METABASE/tools/icons/CEN_RA.png | Bin 0 -> 8893 bytes
CenRa_METABASE/tools/icons/auto_add.png | Bin 0 -> 42290 bytes
CenRa_METABASE/tools/icons/icon.png | Bin 0 -> 15984 bytes
CenRa_METABASE/tools/icons/icon_2.png | Bin 0 -> 27716 bytes
.../tools/images/must_be_a_file.png | Bin 0 -> 25798 bytes
CenRa_METABASE/tools/resources.py | 184 ++
CenRa_METABASE/tools/ui/CenRa_IssuesSend.ui | 332 +++
.../ui/CenRa_Metabase_dockwidget_base.ui | 74 +
.../ui/CenRa_Metabase_editorwidget_base.ui | 1946 +++++++++++++++++
CenRa_METABASE/tools/ui/CenRa_about_form.ui | 81 +
CenRa_METABASE/tools/xml/dcat.xml | 26 +
CenRa_METABASE/tools/xml/distribution.xml | 9 +
CenRa_METABASE/tools/xml/publisher.xml | 6 +
CenRa_PAGERENDER/CenRa_PageRender.py | 141 ++
CenRa_PAGERENDER/README.md | 1 +
CenRa_PAGERENDER/__init__.py | 10 +
CenRa_PAGERENDER/about_form.py | 46 +
CenRa_PAGERENDER/canvas_editor.py | 1 -
CenRa_PAGERENDER/demoV2.py | 169 ++
CenRa_PAGERENDER/icon.png | Bin 0 -> 4977 bytes
CenRa_PAGERENDER/issues.py | 89 +
CenRa_PAGERENDER/tools/icons/CEN_RA.png | Bin 0 -> 8893 bytes
CenRa_PAGERENDER/tools/icons/icon.png | Bin 0 -> 45060 bytes
CenRa_PAGERENDER/tools/resources.py | 174 ++
CenRa_PAGERENDER/tools/ui/CenRa_IssuesSend.ui | 332 +++
.../tools/ui/CenRa_PageRender_base.ui | 109 +-
CenRa_PAGERENDER/tools/ui/CenRa_about_form.ui | 81 +
CenRa_PAGERENDER/tools/ui/rotate.png | Bin 0 -> 19524 bytes
CenRa_PAGERENDER/tools/ui/size.png | Bin 0 -> 17021 bytes
CenRa_PAGERENDER/tools/ui/size_0.png | Bin 0 -> 17730 bytes
43 files changed, 5562 insertions(+), 87 deletions(-)
create mode 100644 CenRa_METABASE/CenRa_Metabase.py
create mode 100644 CenRa_METABASE/README.md
create mode 100644 CenRa_METABASE/__init__.py
create mode 100644 CenRa_METABASE/about_form.py
create mode 100644 CenRa_METABASE/dock.py
create mode 100644 CenRa_METABASE/editor.py
create mode 100644 CenRa_METABASE/icon.png
create mode 100644 CenRa_METABASE/issues.py
create mode 100644 CenRa_METABASE/metadata.txt
create mode 100644 CenRa_METABASE/tools/css/dock.css
create mode 100644 CenRa_METABASE/tools/html/contact.html
create mode 100644 CenRa_METABASE/tools/html/link.html
create mode 100644 CenRa_METABASE/tools/html/main.html
create mode 100644 CenRa_METABASE/tools/icons/CEN_RA.png
create mode 100644 CenRa_METABASE/tools/icons/auto_add.png
create mode 100644 CenRa_METABASE/tools/icons/icon.png
create mode 100644 CenRa_METABASE/tools/icons/icon_2.png
create mode 100644 CenRa_METABASE/tools/images/must_be_a_file.png
create mode 100644 CenRa_METABASE/tools/resources.py
create mode 100644 CenRa_METABASE/tools/ui/CenRa_IssuesSend.ui
create mode 100644 CenRa_METABASE/tools/ui/CenRa_Metabase_dockwidget_base.ui
create mode 100644 CenRa_METABASE/tools/ui/CenRa_Metabase_editorwidget_base.ui
create mode 100644 CenRa_METABASE/tools/ui/CenRa_about_form.ui
create mode 100644 CenRa_METABASE/tools/xml/dcat.xml
create mode 100644 CenRa_METABASE/tools/xml/distribution.xml
create mode 100644 CenRa_METABASE/tools/xml/publisher.xml
create mode 100644 CenRa_PAGERENDER/CenRa_PageRender.py
create mode 100644 CenRa_PAGERENDER/README.md
create mode 100644 CenRa_PAGERENDER/__init__.py
create mode 100644 CenRa_PAGERENDER/about_form.py
create mode 100644 CenRa_PAGERENDER/demoV2.py
create mode 100644 CenRa_PAGERENDER/icon.png
create mode 100644 CenRa_PAGERENDER/issues.py
create mode 100644 CenRa_PAGERENDER/tools/icons/CEN_RA.png
create mode 100644 CenRa_PAGERENDER/tools/icons/icon.png
create mode 100644 CenRa_PAGERENDER/tools/resources.py
create mode 100644 CenRa_PAGERENDER/tools/ui/CenRa_IssuesSend.ui
create mode 100644 CenRa_PAGERENDER/tools/ui/CenRa_about_form.ui
create mode 100644 CenRa_PAGERENDER/tools/ui/rotate.png
create mode 100644 CenRa_PAGERENDER/tools/ui/size.png
create mode 100644 CenRa_PAGERENDER/tools/ui/size_0.png
diff --git a/CenRa_METABASE/CenRa_Metabase.py b/CenRa_METABASE/CenRa_Metabase.py
new file mode 100644
index 0000000..d1e265a
--- /dev/null
+++ b/CenRa_METABASE/CenRa_Metabase.py
@@ -0,0 +1,217 @@
+__copyright__ = "Copyright 2021, 3Liz"
+__license__ = "GPL version 3"
+__email__ = "info@3liz.org"
+
+
+from qgis.core import QgsApplication
+from qgis.PyQt.QtCore import Qt, QUrl, QSettings
+from qgis.PyQt.QtGui import QDesktopServices, QIcon
+from qgis.PyQt.QtWidgets import QAction
+from qgis.utils import iface
+import qgis
+
+# include
+'''
+from pg_metadata.connection_manager import (
+ store_connections,
+ validate_connections_names,
+)
+
+
+from pg_metadata.locator import LocatorFilter
+from pg_metadata.processing.provider import PgMetadataProvider
+from pg_metadata.qgis_plugin_tools.tools.custom_logging import setup_logger
+'''
+import os
+from .about_form import MetabaseAboutDialog
+from .tools.resources import (
+ # plugin_path,
+ pyperclip,
+ resources_path,
+ maj_verif,
+)
+pyperclip()
+from .dock import CenRa_Metabase
+from .editor import Metabase_Editor
+
+# from CenRa_Metabase.issues import CenRa_Issues
+
+
+class PgMetadata:
+ def __init__(self):
+ """ Constructor. """
+ self.dock = None
+ self.editor = None
+# self.issues = None
+ self.provider = None
+ self.locator_filter = None
+ self.dock_action = None
+ self.help_action = None
+ plugin_dir = os.path.dirname(__file__)
+ end_find = plugin_dir.rfind('\\') + 1
+ global NAME
+ NAME = plugin_dir[end_find:]
+ maj_verif(NAME)
+
+ # Display About window on first use
+ version = qgis.utils.pluginMetadata('CenRa_METABASE', 'version')
+ s = QSettings()
+ versionUse = s.value("metadata/version", 1, type=str)
+ if str(versionUse) != str(version):
+ s.setValue("metadata/version", str(version))
+ print(versionUse, version)
+ self.open_about_dialog()
+# setup_logger('pg_metadata')
+
+ # locale, file_path = setup_translation(
+ # folder=plugin_path("i18n"), file_pattern="CenRa_Metabase_{}.qm")
+ # if file_path:
+ # self.translator = QTranslator()
+ # self.translator.load(file_path)
+ # noinspection PyCallByClass,PyArgumentList
+ # QCoreApplication.installTranslator(self.translator)
+
+ # noinspection PyPep8Naming
+ # def initProcessing(self):
+ # """ Add the QGIS Processing provider. """
+ # if not self.provider:
+ # self.provider = PgMetadataProvider()
+ # QgsApplication.processingRegistry().addProvider(self.provider)
+
+ # noinspection PyPep8Naming
+ def initGui(self):
+ """ Build the plugin GUI. """
+ # self.initProcessing()
+
+ # self.check_invalid_connection_names()
+
+ self.toolBar = iface.addToolBar("CenRa_Metabase")
+ self.toolBar.setObjectName("CenRa_Metabase")
+
+ icon = QIcon(resources_path('icons', 'icon.png'))
+ icon2 = QIcon(resources_path('icons', 'icon_2.png'))
+
+ # Open the online help
+ self.help_action = QAction(icon, 'CenRa_Metabase', iface.mainWindow())
+ iface.pluginHelpMenu().addAction(self.help_action)
+ self.help_action.triggered.connect(self.open_help)
+ if not self.editor:
+ self.editor = Metabase_Editor()
+
+ self.editor_action = QAction(icon2, 'CenRa_Metabase', None)
+ self.toolBar.addAction(self.editor_action)
+ self.editor_action.triggered.connect(self.open_editor)
+
+ if not self.dock:
+ self.dock = CenRa_Metabase()
+ iface.addDockWidget(Qt.DockWidgetArea(0x2), self.dock)
+
+ # Open/close the dock from plugin menu
+ self.dock_action = QAction(icon, 'CenRa_Metabase', iface.mainWindow())
+ iface.pluginMenu().addAction(self.dock_action)
+ self.dock_action.triggered.connect(self.open_dock)
+# if not self.issues:
+# self.issues = CenRa_Issues()
+
+
+# self.issues_action = QAction(icon, 'CenRa_Metabase',None)
+# self.toolBar.addAction(self.issues_action)
+# self.issues_action.triggered.connect(self.open_issues)
+ '''
+ if not self.locator_filter:
+ self.locator_filter = LocatorFilter(iface)
+ iface.registerLocatorFilter(self.locator_filter)
+
+ @staticmethod
+ def check_invalid_connection_names():
+ """ Check for invalid connection names in the QgsSettings. """
+ valid, invalid = validate_connections_names()
+ n_invalid = len(invalid)
+
+ if n_invalid == 0:
+ return
+
+ invalid_text = ', '.join(invalid)
+ msg = QMessageBox()
+ msg.setIcon(QMessageBox.Warning)
+ msg.setWindowTitle(tr('PgMetadata: Database connection(s) not available'))
+ msg.setText(tr(
+ f'{n_invalid} connection(s) listed in PgMetadata’s settings are invalid or '
+ f'no longer available: {invalid_text}'))
+ msg.setInformativeText(tr(
+ 'Do you want to remove these connection(s) from the PgMetadata settings? '
+ '(You can also do this later with the “Set Connections” tool.)'))
+ msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
+ clicked = msg.exec()
+
+ if clicked == QMessageBox.Yes:
+ iface.messageBar().pushSuccess('PgMetadata', tr(f'{n_invalid} invalid connection(s) removed.'))
+ store_connections(valid)
+ if clicked == QMessageBox.No:
+ iface.messageBar().pushInfo('PgMetadata', tr(f'Keeping {n_invalid} invalid connections.'))
+ '''
+ def open_about_dialog(self):
+ """
+ About dialog
+ """
+ dialog = MetabaseAboutDialog(iface)
+ dialog.exec()
+
+ def open_help():
+ """ Open the online help. """
+ QDesktopServices.openUrl(QUrl('https://plateformesig.cenra-outils.org/'))
+
+ def open_dock(self):
+ """ Open the dock. """
+ self.dock.show()
+ self.dock.raise_()
+
+ def open_editor(self):
+ self.editor.show()
+ self.editor.raise_()
+
+# def open_issues(self):
+# self.issues.show()
+# self.issues.raise_()
+
+ def unload(self):
+ """ Unload the plugin. """
+ # if self.editor:
+ # iface.removePluginMenu('CenRa_Metabase',self.editor_action)
+ # self.editor.removeToolBarIcon(self.editor_action)
+
+ if self.dock:
+ iface.removeDockWidget(self.dock)
+ self.dock.deleteLater()
+
+ if self.provider:
+ QgsApplication.processingRegistry().removeProvider(self.provider)
+ del self.provider
+
+ if self.locator_filter:
+ iface.deregisterLocatorFilter(self.locator_filter)
+ del self.locator_filter
+
+ if self.help_action:
+ iface.pluginHelpMenu().removeAction(self.help_action)
+ del self.help_action
+
+ if self.dock_action:
+ iface.pluginMenu().removeAction(self.dock_action)
+ del self.dock_action
+
+ @staticmethod
+ def run_tests(pattern='test_*.py', package=None):
+ """Run the test inside QGIS."""
+ try:
+ from pathlib import Path
+
+ from pg_metadata.qgis_plugin_tools.infrastructure.test_runner import (
+ test_package,
+ )
+ if package is None:
+ package = '{}.__init__'.format(Path(__file__).parent.name)
+ test_package(package, pattern)
+ except (AttributeError, ModuleNotFoundError):
+ message = 'Could not load tests. Are you using a production package?'
+ print(message) # NOQA
diff --git a/CenRa_METABASE/README.md b/CenRa_METABASE/README.md
new file mode 100644
index 0000000..86abd18
--- /dev/null
+++ b/CenRa_METABASE/README.md
@@ -0,0 +1 @@
+# CenRa_Metabase
\ No newline at end of file
diff --git a/CenRa_METABASE/__init__.py b/CenRa_METABASE/__init__.py
new file mode 100644
index 0000000..4eaf7d0
--- /dev/null
+++ b/CenRa_METABASE/__init__.py
@@ -0,0 +1,10 @@
+__copyright__ = "Copyright 2021, 3Liz"
+__license__ = "GPL version 3"
+__email__ = "info@3liz.org"
+
+
+# noinspection PyPep8Naming
+def classFactory(iface): # pylint: disable=invalid-name
+ _ = iface
+ from CenRa_METABASE.CenRa_Metabase import PgMetadata
+ return PgMetadata()
diff --git a/CenRa_METABASE/about_form.py b/CenRa_METABASE/about_form.py
new file mode 100644
index 0000000..b9fa5a7
--- /dev/null
+++ b/CenRa_METABASE/about_form.py
@@ -0,0 +1,46 @@
+import os.path
+
+from pathlib import Path
+
+from qgis.PyQt import uic
+# from qgis.PyQt.QtGui import QPixmap
+from qgis.PyQt.QtWidgets import QDialog
+
+from .tools.resources import devlog
+
+ABOUT_FORM_CLASS, _ = uic.loadUiType(
+ os.path.join(
+ str(Path(__file__).resolve().parent),
+ 'tools/ui',
+ 'CenRa_about_form.ui'
+ )
+)
+
+
+class MetabaseAboutDialog(QDialog, ABOUT_FORM_CLASS):
+
+ """ About - Let the user display the about dialog. """
+
+ def __init__(self, iface, parent=None):
+ super().__init__(parent)
+ self.iface = iface
+ self.setupUi(self)
+
+ self.viewer.setHtml(devlog('CenRa_METABASE'))
+
+ self.rejected.connect(self.onReject)
+ self.buttonBox.rejected.connect(self.onReject)
+ self.buttonBox.accepted.connect(self.onAccept)
+
+ def onAccept(self):
+ """
+ Save options when pressing OK button
+ """
+ self.accept()
+
+ def onReject(self):
+ """
+ Run some actions when
+ the user closes the dialog
+ """
+ self.close()
diff --git a/CenRa_METABASE/dock.py b/CenRa_METABASE/dock.py
new file mode 100644
index 0000000..a2979e2
--- /dev/null
+++ b/CenRa_METABASE/dock.py
@@ -0,0 +1,453 @@
+"""Dock file."""
+
+__copyright__ = 'Copyright 2020, 3Liz'
+__license__ = 'GPL version 3'
+__email__ = 'info@3liz.org'
+
+import logging
+import os
+
+from collections import namedtuple
+from enum import Enum
+from functools import partial
+from pathlib import Path
+from xml.dom.minidom import parseString
+
+from qgis.core import (
+ QgsApplication,
+ QgsProviderRegistry,
+ QgsSettings,
+)
+from qgis.PyQt.QtCore import QLocale, QUrl
+from qgis.PyQt.QtGui import QDesktopServices, QIcon
+from qgis.PyQt.QtPrintSupport import QPrinter
+# from qgis.PyQt.QtWebKitWidgets import QWebPage
+from qgis.PyQt.QtWidgets import (
+ QAction,
+ QDockWidget,
+ QFileDialog,
+ QMenu,
+ QToolButton,
+)
+from qgis.utils import iface
+import qgis
+from .tools.resources import (
+ load_ui,
+ resources_path,
+)
+try:
+ from .tools.PythonSQL import login_base
+except ValueError:
+ print('Pas de fichier PythonSQL')
+
+DOCK_CLASS = load_ui('CenRa_Metabase_dockwidget_base.ui')
+LOGGER = logging.getLogger('CenRa_Metabase')
+
+
+class Format(namedtuple('Format', ['label', 'ext'])):
+ """ Format available for exporting metadata. """
+ pass
+
+
+class OutputFormats(Format, Enum):
+ """ Output format for a metadata sheet. """
+ PDF = Format(label='PDF', ext='pdf')
+ HTML = Format(label='HTML', ext='html')
+ DCAT = Format(label='DCAT', ext='xml')
+
+
+class CenRa_Metabase(QDockWidget, DOCK_CLASS):
+
+ def __init__(self, parent=None):
+ _ = parent
+ super().__init__()
+ self.setupUi(self)
+ self.settings = QgsSettings()
+
+ self.current_datasource_uri = None
+ self.current_connection = None
+
+ # self.viewer.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
+ # self.viewer.page().linkClicked.connect(self.open_link)
+
+ # Help button
+ self.external_help.setText('')
+ self.external_help.setIcon(QIcon(QgsApplication.iconPath('mActionHelpContents.svg')))
+ self.external_help.clicked.connect(self.open_external_help)
+
+ # Flat table button
+ self.flatten_dataset_table.setText('')
+ # self.flatten_dataset_table.setToolTip(tr("Add the catalog table"))
+ self.flatten_dataset_table.setIcon(QgsApplication.getThemeIcon("/mActionAddHtml.svg"))
+ # self.flatten_dataset_table.clicked.connect(self.add_flatten_dataset_table)
+
+ # Settings menu
+ self.config.setAutoRaise(True)
+ # self.config.setToolTip(tr("Settings"))
+ self.config.setPopupMode(QToolButton.ToolButtonPopupMode(2))
+ self.config.setIcon(QgsApplication.getThemeIcon("/mActionOptions.svg"))
+
+ self.auto_open_dock_action = QAction(
+ ("Dommage, cette option n'existe pas encore."),
+ iface.mainWindow())
+ self.auto_open_dock_action.setCheckable(True)
+ self.auto_open_dock_action.setChecked(
+ self.settings.value("pgmetadata/auto_open_dock", True, type=bool)
+ )
+ self.auto_open_dock_action.triggered.connect(self.save_auto_open_dock)
+ menu = QMenu()
+ menu.addAction(self.auto_open_dock_action)
+ self.config.setMenu(menu)
+
+ # Setting PDF/HTML menu
+ self.save_button.setAutoRaise(True)
+ # self.save_button.setToolTip(tr("Save metadata"))
+ self.save_button.setPopupMode(QToolButton.ToolButtonPopupMode(2))
+ self.save_button.setIcon(QIcon(QgsApplication.iconPath('mActionFileSave.svg')))
+
+ self.save_as_pdf = QAction(
+ ('Enregistrer en PDF') + '…',
+ iface.mainWindow())
+ self.save_as_pdf.triggered.connect(partial(self.export_dock_content, OutputFormats.PDF))
+
+ self.save_as_html = QAction(
+ ('Enregistrer en HTML') + '…',
+ iface.mainWindow())
+ self.save_as_html.triggered.connect(partial(self.export_dock_content, OutputFormats.HTML))
+ self.save_as_dcat = QAction(
+ ('Enregistrer en DCAT') + '…',
+ iface.mainWindow())
+ self.save_as_dcat.triggered.connect(partial(self.export_dock_content, OutputFormats.DCAT))
+
+ self.menu_save = QMenu()
+ self.menu_save.addAction(self.save_as_pdf)
+ self.menu_save.addAction(self.save_as_html)
+ self.menu_save.addAction(self.save_as_dcat)
+ self.save_button.setMenu(self.menu_save)
+ self.save_button.setEnabled(False)
+
+ self.metadata = QgsProviderRegistry.instance().providerMetadata('postgres')
+
+ # Display message in the dock
+ # if not settings_connections_names():
+ # self.default_html_content_not_installed()
+ # else:
+ self.default_html_content_not_pg_layer()
+ iface.layerTreeView().currentLayerChanged.connect(self.layer_changed)
+ try:
+ login_base()
+ iface.layerTreeView().currentLayerChanged.connect(self.layer_changed)
+ except ValueError:
+ # qgis.utils.plugins['CenRa_METABASE'].initGui()
+ qgis.utils.plugins['CenRa_METABASE'].unload()
+ # self.default_html_content_not_pg_layer()
+
+ if iface.activeLayer():
+ layer = iface.activeLayer()
+ iface.layerTreeView().setCurrentLayer(None)
+ iface.layerTreeView().setCurrentLayer(layer)
+
+ def export_dock_content(self, output_format: OutputFormats):
+ """ Export the current displayed metadata sheet to the given format. """
+ layer_name = iface.activeLayer().name()
+
+ file_path = os.path.join(
+ os.environ['USERPROFILE'],
+ 'Desktop\\{name}.{ext}'.format(name=layer_name, ext=output_format.ext)
+ )
+ output_file = QFileDialog.getSaveFileName(
+ self,
+ ("Enregistrer en {format}").format(format=output_format.label),
+ file_path,
+ "{label} (*.{ext})".format(
+ label=output_format.label,
+ ext=output_format.ext,
+ )
+ )
+ if output_file[0] == '':
+ return
+
+ self.settings.setValue("UI/lastFileNameWidgetDir", os.path.dirname(output_file[0]))
+
+ output_file_path = output_file[0]
+ parent_folder = str(Path(output_file_path).parent)
+
+ if output_format == OutputFormats.PDF:
+ printer = QPrinter()
+ printer.setOutputFormat(QPrinter.OutputFormat(1))
+ # printer.setPageMargins(20,20,20,20,QPrinter.Unit(0))
+ printer.setOutputFileName(output_file_path)
+ self.viewer.print(printer)
+ iface.messageBar().pushSuccess(
+ ("Export PDF"),
+ (
+ "The metadata has been exported as PDF successfully in "
+ "{}").format(parent_folder, output_file_path)
+ )
+
+ elif output_format in [OutputFormats.HTML, OutputFormats.DCAT]:
+ if output_format == OutputFormats.HTML:
+ data_str = self.viewer.page().currentFrame().toHtml()
+ else:
+ layer = iface.activeLayer()
+ uri = layer.dataProvider().uri()
+ dataall = self.sql_info(uri)
+ data = self.sql_to_xml(dataall)
+
+ with open(resources_path('xml', 'dcat.xml'), encoding='utf8') as xml_file:
+ xml_template = xml_file.read()
+
+ xml = parseString(xml_template.format(language=data[0][0], content=data[0][1]))
+
+ data_str = xml.toprettyxml()
+
+ with open(output_file[0], "w", encoding='utf8') as file_writer:
+ file_writer.write(data_str)
+ iface.messageBar().pushSuccess(
+ ("Export") + ' ' + output_format.label,
+ (
+ "The metadata has been exported as {format} successfully in "
+ "{path}").format(
+ format=output_format.label, folder=parent_folder, path=output_file_path)
+ )
+
+ def save_auto_open_dock(self):
+ """ Save settings about the dock. """
+ self.settings.setValue("pgmetadata/auto_open_dock", self.auto_open_dock_action.isChecked())
+
+ def sql_to_xml(self, dataall):
+ distribution = ''
+ for y in dataall[1]:
+ distribution = distribution + ('' + '' + '{data}'.format(data=y[0]) + '{data}'.format(data=y[1]) + '{data}'.format(data=y[2]) + '{data}'.format(data=y[3]) + '{data}'.format(data=y[4]) + '' + '')
+ publisher = ''
+ for z in dataall[2]:
+ publisher = publisher + ('' + '' + '{data}'.format(data=z[1]) + '{data}'.format(data=z[3]) + '' + '')
+ data_str = [[dataall[0][26], '{data}'.format(data=dataall[0][1]) + '{data}'.format(data=dataall[0][4]) + '{data}'.format(data=dataall[0][5]) + '{data}'.format(data=dataall[0][26]) + '{data}'.format(data=dataall[0][28]) + '{data}'.format(data=dataall[0][20]) + '{data}'.format(data=dataall[0][11]) + '{data}'.format(data=dataall[0][21]) + '{data}'.format(data=dataall[0][13]) + distribution + publisher + '{data}'.format(data=", ".join(str(x) for x in dataall[0][24])) + '{data}'.format(data=", ".join(str(x) for x in dataall[0][6])) + '{data}'.format(data=dataall[0][12])]]
+
+ return data_str
+
+ @staticmethod
+ def sql_for_layer(uri, output_format: OutputFormats):
+ """ Get the SQL query for a given layer and output format. """
+ locale = QgsSettings().value("locale/userLocale", QLocale().name())
+ locale = locale.split('_')[0].lower()
+
+ if output_format == [OutputFormats.HTML, OutputFormats.DCAT]:
+ sql = (
+ "SELECT pgmetadata.get_dataset_item_html_content('{schema}', '{table}', '{locale}');"
+ ).format(schema=uri.schema(), table=uri.table(), locale=locale)
+ else:
+ raise NotImplementedError('Output format is not yet implemented.')
+
+ return sql
+
+ def layer_changed(self, layer):
+ """ When the layer has changed in the legend, we must check this new layer. """
+ self.current_datasource_uri = None
+ self.current_connection = None
+ self.ce_trouve_dans_psql(layer)
+
+ def add_flatten_dataset_table(self):
+ """ Add a flatten dataset table with all links and contacts. """
+ '''
+ connections, message = connections_list()
+ if not connections:
+ LOGGER.critical(message)
+ self.set_html_content('PgMetadata', message)
+ return
+
+ if len(connections) > 1:
+ dialog = QInputDialog()
+ dialog.setComboBoxItems(connections)
+ dialog.setWindowTitle(tr("Database"))
+ dialog.setLabelText(tr("Choose the database to add the catalog"))
+ if not dialog.exec_():
+ return
+ connection_name = dialog.textValue()
+ else:
+ connection_name = connections[0]
+
+ metadata = QgsProviderRegistry.instance().providerMetadata('postgres')
+ connection = metadata.findConnection(connection_name)
+
+ locale = QgsSettings().value("locale/userLocale", QLocale().name())
+ locale = locale.split('_')[0].lower()
+
+ uri = QgsDataSourceUri(connection.uri())
+ uri.setTable(f'(SELECT * FROM pgmetadata.export_datasets_as_flat_table(\'{locale}\'))')
+ uri.setKeyColumn('uid')
+
+ layer = QgsVectorLayer(uri.uri(), '{} - {}'.format(tr("Catalog"), connection_name), 'postgres')
+ QgsProject.instance().addMapLayer(layer)
+ '''
+
+ @staticmethod
+ def open_external_help():
+ QDesktopServices.openUrl(QUrl('https://plateformesig.cenra-outils.org/'))
+
+ @staticmethod
+ def open_link(url):
+ QDesktopServices.openUrl(url)
+
+ def set_html_content(self, title=None, body=None):
+ """ Set the content in the dock. """
+ # ink_logo=resources_path('icons', 'CEN_RA.png')
+ css_file = resources_path('css', 'dock.css')
+ with open(css_file, 'r', encoding='utf8') as f:
+ css = f.read()
+
+ html = ''
+ # html += ''
+ html += ''.format(css=css)
+ # html += ''
+ # html += ''
+ # html += ''
+ if title:
+ html += '{title} 
'.format(title=title)
+ if body:
+ html += body
+
+ html += ''
+
+ # It must be a file, even if it does not exist on the file system.
+ # base_url = QUrl.fromLocalFile(resources_path('images', 'must_be_a_file.png'))
+ self.viewer.setHtml(html)
+
+ def ce_trouve_dans_psql(self, layer):
+ try:
+ uri = layer.dataProvider().uri()
+ except AttributeError:
+ self.default_html_content_not_pg_layer()
+ self.save_button.setEnabled(False)
+ uri = ''
+ if uri != '':
+ if not uri.table():
+ layertype = layer.providerType().lower()
+ if layertype == 'wms' or layertype == 'wfs':
+ self.set_html_to_wms(layer)
+ else:
+ self.default_html_content_not_pg_layer()
+ self.save_button.setEnabled(False)
+ else:
+ data_count = self.sql_check(uri)
+ # print(data_count)
+ if data_count == 0:
+ self.default_html_content_not_metadata()
+ self.save_button.setEnabled(False)
+ else:
+ self.build_html_content(layer, uri)
+ self.save_button.setEnabled(True)
+
+ def build_html_content(self, layer, uri):
+ body = ''
+
+ dataall = self.sql_info(uri)
+ data = dataall[0]
+ data_url = dataall[1]
+ data_contact = dataall[2]
+ # print(len(data_url))
+ # data_collonne = [field.name() for field in layer.dataProvider().fields()]
+
+ body += 'Identification
'
+ body += '| Titre | {data[4]} |
'.format(data=data)
+ body += '| Description | {data[5]} |
'.format(data=data)
+ body += '| Categories | {data} |
'.format(data=(", ".join(str(x) for x in data[6])))
+ body += '| Thèmes | {data} |
'.format(data=(", ".join(str(x) for x in data[24])))
+ body += '| Mots-clés | {data[7]} |
'.format(data=data)
+ body += '| Dernier mise à jour | {data[23]} |
'.format(data=data)
+ body += '| Langue | {data[26]} |
'.format(data=data)
+ body += '
'
+
+ body += 'Properties spatial
'
+ body += '| Niveau | {data[8]} |
'.format(data=data)
+ body += '| Echelle minimum | {data[9]} |
'.format(data=data)
+ body += '| Echelle maximum | {data[10]} |
'.format(data=data)
+ body += '| Nombre d\'entités | {data[15]} |
'.format(data=data)
+ body += '| Type de géométrie | {data[16]} |
'.format(data=data)
+ body += '| Nom de projection | {data[17]} |
'.format(data=data)
+ body += '| ID de projection | {data[18]} |
'.format(data=data)
+ body += '| Emprise | {data[28]} |
'.format(data=data)
+ body += '
'
+
+ # body += ''
+
+ body += 'Publication
'
+ body += '| Date | {data[11]} |
'.format(data=data)
+ body += '| Fréquence de mise à jour | {data[12]} |
'.format(data=data)
+ body += '| Licence | {data[13]} |
'.format(data=data)
+ body += '| Licence attribué | {data[25]} |
'.format(data=data)
+ body += '| Restriction | {data[14]} |
'.format(data=data)
+ body += '
'
+
+ body += 'Lien
'
+ body += '| Type | URL | Type MIME | Format | Taille |
'
+ for value_url in data_url:
+ body += '| {value_url[0]} | {value_url[1]} | {value_url[2]} | {value_url[3]} | {value_url[4]} |
'.format(value_url=value_url)
+ body += '
'
+ '''
+ body += 'Liste des champs
'
+ for collonne in data_collonne:
+ body += '| {collonne} | {defini} |
'.format(collonne=collonne,defini='')
+ body += '
'
+ '''
+ body += 'Contacts
'
+ body += '| Rôle | Nom | Organisation | Email | Télèphone |
'
+ for value_contact in data_contact:
+ body += '| {value_contact[0]} | {value_contact[1]} | {value_contact[2]} | {value_contact[3]} | {value_contact[4]} |
'.format(value_contact=value_contact)
+ body += '
'
+
+ body += 'Metadata
'
+ body += '| Table | {data[2]} |
'.format(data=data)
+ body += '| Schema | {data[3]} |
'.format(data=data)
+ body += '| Date de création | {data[20]} |
'.format(data=data)
+ body += '| Date de modification | {data[21]} |
'.format(data=data)
+ body += '| Encodage | {data[27]} |
'.format(data=data)
+ body += '| UUID | {data[1]} |
'.format(data=data)
+ body += '
'
+
+ self.set_html_content(
+ layer.name(), body)
+
+ def set_html_to_wms(self, layer):
+ self.set_html_content(
+ 'CenRa Metadata', (layer.htmlMetadata()))
+
+ def default_html_content_not_pg_layer(self):
+ """ When it's not a PostgreSQL layer. """
+ self.set_html_content(
+ 'CenRa Metadata', ('Vous devez cliquer sur une couche dans la légende qui est stockée dans PostgreSQL.'))
+
+ def default_html_content_not_metadata(self):
+ self.set_html_content(
+ 'CenRa Metadata', ('La couche ne contien pas de métadonnée.'))
+
+ def sql_check(self, uri):
+ cur = login_base()
+ table = uri.table()
+ schema = uri.schema()
+
+ sql_count = """SELECT count(uid) FROM metadata.dataset
+ WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+
+ cur.execute(sql_count)
+ data_count = cur.fetchall()
+ cur.close()
+ return data_count[0][0]
+
+ def sql_info(self, uri):
+ cur = login_base()
+ table = uri.table()
+ schema = uri.schema()
+ # [s for s in iface.activeLayer().source().split(" ") if "dbname" in s][0].split("'")[1]
+ sql_find = """SELECT *,right(left(st_astext(geom,2),-2),-9) FROM metadata.dataset
+ WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_find)
+ data_general = cur.fetchall()
+ sql_findurl = """SELECT type,url,mime,format,taille FROM metadata.dataurl WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_findurl)
+ data_url = cur.fetchall()
+ sql_findcontact = """SELECT role,nom,organisation,email,telephone FROM metadata.datacontact WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_findcontact)
+ data_contact = cur.fetchall()
+ cur.close()
+ return data_general[0], data_url, data_contact
diff --git a/CenRa_METABASE/editor.py b/CenRa_METABASE/editor.py
new file mode 100644
index 0000000..7896e2d
--- /dev/null
+++ b/CenRa_METABASE/editor.py
@@ -0,0 +1,775 @@
+import logging
+import os
+
+# from collections import namedtuple
+# from enum import Enum
+# from functools import partial
+# from pathlib import Path
+# from xml.dom.minidom import parseString
+# from qgis.gui import *
+from qgis.core import (
+ QgsApplication,
+ QgsSettings,
+ QgsGeometry,
+ QgsWkbTypes,
+ Qgis,
+)
+from qgis.PyQt import QtGui, QtCore
+from qgis.PyQt.QtGui import QIcon
+# from qgis.PyQt.QtPrintSupport import QPrinter
+# from qgis.PyQt.QtWebKitWidgets import QWebPage
+import psycopg2
+from qgis.PyQt.QtWidgets import (
+ QDialog,
+ QFileDialog,
+ QTableWidgetItem,
+)
+from qgis.utils import iface
+
+try:
+ from .tools.PythonSQL import login_base
+except ValueError:
+ print('Pas de fichier PythonSQL')
+
+from .tools.resources import (
+ load_ui,
+ resources_path,
+ # send_issues,
+)
+# from .issues import CenRa_Issues
+
+EDITOR_CLASS = load_ui('CenRa_Metabase_editorwidget_base.ui')
+LOGGEr = logging.getLogger('CenRa_Metabase')
+
+
+class Metabase_Editor(QDialog, EDITOR_CLASS):
+ def __init__(self, parent=None):
+ _ = parent
+ super().__init__()
+ self.setupUi(self)
+ self.settings = QgsSettings()
+ self.setWindowIcon(QtGui.QIcon(resources_path('icons', 'icon.png')))
+ self.import_xml.setAutoRaise(True)
+ self.import_xml.setText('')
+ self.import_xml.setIcon(QIcon(QgsApplication.iconPath('mActionAddHtml.svg')))
+ self.import_xml.clicked.connect(self.py_import_xml)
+ self.issues_app.setAutoRaise(True)
+ self.issues_app.setText('')
+ self.issues_app.setIcon(QIcon(QgsApplication.iconPath('mIconInfo.svg')))
+ self.auto_adding.setIcon(QtGui.QIcon(resources_path('icons', 'auto_add.png')))
+ self.auto_adding.hide()
+ if os.getlogin() == 'tlaveille' or 'lpoulin' or 'rclement':
+ self.auto_adding.show()
+
+ self.auto_adding.clicked.connect(self.auto_run)
+ # self.issues_app.clicked.connect(self.issues_open)
+ self.categories_select_view.itemDoubleClicked.connect(self.add_categories_view)
+ self.categories_view.itemDoubleClicked.connect(self.deleter_categories_view)
+ self.themes_select_view.itemDoubleClicked.connect(self.add_themes_view)
+ self.themes_view.itemDoubleClicked.connect(self.deleter_themes_view)
+
+ self.annuler_button.clicked.connect(self.close)
+ self.ok_button.clicked.connect(self.add_metadata)
+
+ self.add_lien_button.clicked.connect(self.add_lien)
+ self.add_contact_button.clicked.connect(self.add_contact)
+ self.delete_lien_button.clicked.connect(self.delete_lien)
+ self.delete_contact_button.clicked.connect(self.delete_contact)
+
+ def auto_run(self):
+ self.role_box.setCurrentIndex(1)
+ self.nom_line.setText('LAVEILLE')
+ self.organisation_box.setCurrentIndex(1)
+ self.email_line.setText('tom.laveille@cen-rhonealpes.fr')
+ self.telephone_line.setText('0451260811')
+ self.add_contact()
+
+ self.type_box.setCurrentIndex(16)
+ self.url_line.setText('www.cen-rhonealpes.fr')
+ self.mime_box.setCurrentIndex(16)
+ self.format_box.setCurrentIndex(0)
+ self.taille_line.setText('45')
+ self.add_lien()
+
+ def add_metadata(self):
+ table_name = layer.dataProvider().uri().table()
+ schema_name = layer.dataProvider().uri().schema()
+ text_titre = self.titre_line.text()
+ text_description = self.description_text.toPlainText()
+ text_mots_cles = self.mots_cles_text.toPlainText()
+ text_date_maj = str(self.date_maj_date.date().toPyDate())
+ text_langue = self.langue_box.currentText()
+
+ row_count_categories = self.categories_view.rowCount()
+ row_count_themes = self.themes_view.rowCount()
+ row = 1
+ array_categories = '{'
+ while row_count_categories >= row:
+ if row_count_categories != row:
+ array_categories += (self.categories_view.item(row - 1, 0).text()) + ', '
+ else:
+ array_categories += (self.categories_view.item(row - 1, 0).text())
+ row = row + 1
+ array_categories += '}'
+ row = 1
+ array_themes = '{'
+ while row_count_themes >= row:
+ if row_count_themes != row:
+ array_themes += (self.themes_view.item(row - 1, 0).text()) + ', '
+ else:
+ array_themes += (self.themes_view.item(row - 1, 0).text())
+ row = row + 1
+ array_themes += '}'
+
+ text_date_creation = str(self.date_creation_date.date().toPyDate())
+ text_date_modification = str(self.date_modification_date.date().toPyDate())
+ text_encode = self.encodage_box.currentText()
+ text_extend = self.extend_plaintext.toPlainText()
+ int_nbr_entites = (self.nbr_layers.toPlainText())
+ text_geomtype = self.typegeom_plaintext.toPlainText()
+ text_crsname = self.crsname_plaintext.toPlainText()
+ text_crscode = self.crscode_plaintext.toPlainText()
+ text_niveau = self.niveau_plain.toPlainText()
+
+ text_echelle_min = self.echelle_min_plain.toPlainText()
+ text_echelle_max = self.echelle_max_plain.toPlainText()
+ if text_echelle_min == '':
+ text_echelle_min = 'NULL'
+ if text_echelle_max == '':
+ text_echelle_max = 'NULL'
+
+ text_date_publication = str(self.date_publication_date.date().toPyDate())
+
+ text_frequence = self.frequence_box.currentText()
+ text_restriction = self.restriction_box.currentText()
+ text_licence = self.licence_box.currentText()
+ text_licence_attrib = self.licence_attrib_box.currentText()
+
+ '''
+ row_count_link = self.table_lien.rowCount()
+ row = 1
+ array_link = ''
+ while row_count_link >= row:
+ if row_count_link != row:
+ array_link += "('" + table_name + "', '" + schema_name + "', '" + (self.table_lien.item(row - 1,1).text()) + "', '" + (self.table_lien.item(row - 1,2).text()) + "', '" + (self.table_lien.item(row - 1,3).text()) + "', '" + (self.table_lien.item(row - 1,4).text()) + "', '" + (self.table_lien.item(row - 1,5).text()) + "')" + ', '
+ else:
+ array_link += "('" + table_name + "', '" + schema_name + "', '" + (self.table_lien.item(row - 1,1).text()) + "', '" + (self.table_lien.item(row - 1,2).text()) + "', '" + (self.table_lien.item(row - 1,3).text()) + "', '" + (self.table_lien.item(row - 1,4).text()) + "', '" + (self.table_lien.item(row - 1,5).text()) + "')"
+ row = row + 1
+
+ row_count_contact = self.table_contact.rowCount()
+ row = 1
+ array_contact = ''
+ while row_count_contact >= row:
+ if row_count_contact != row:
+ array_contact += "('" + table_name + "', '" + schema_name + "', '" + (self.table_contact.item(row - 1,1).text()) + "', '" + (self.table_contact.item(row - 1,2).text()) + "', '" + (self.table_contact.item(row - 1,3).text()) + "', '" + (self.table_contact.item(row - 1,4).text()) + "', '" + (self.table_contact.item(row - 1,5).text()) + "')" + ', '
+ else:
+ array_contact += "('" + table_name + "', '" + schema_name + "', '" + (self.table_contact.item(row - 1,1).text()) + "', '" + (self.table_contact.item(row - 1,2).text()) + "', '" + (self.table_contact.item(row - 1,3).text()) + "', '" + (self.table_contact.item(row - 1,4).text()) + "', '" + (self.table_contact.item(row - 1,5).text()) + "')"
+ row = row + 1
+ '''
+
+ exist = self.status_metadata(layer)
+ cur_con = login_base(take=True)
+ cur = cur_con[0]
+ con = cur_con[1]
+ list_champs_sql = ''
+ values_sql_add = ''
+ if exist:
+ SQL_uid = """SELECT uid from metadata.dataset where table_name like '""" + table_name + """' and schema_name like '""" + schema_name + """';"""
+ cur.execute(SQL_uid)
+ text_uid = (cur.fetchall())[0][0]
+
+ SQL_delete = """DELETE from metadata.dataset where table_name like '""" + table_name + """' and schema_name like '""" + schema_name + """';"""
+ cur.execute(SQL_delete)
+ values_sql_add += "'" + text_uid + "',"
+
+ list_champs_sql += 'uid,'
+ global uid_delete_list_link, uid_delete_list_contact
+ if len(uid_delete_list_link) >= 35:
+ SQL_delete_link = """DELETE FROM metadata.dataurl WHERE uid IN (""" + uid_delete_list_link[:- 1] + """);"""
+ cur.execute(SQL_delete_link)
+ uid_delete_list_link = ''
+ if len(uid_delete_list_contact) >= 35:
+ SQL_delete_contact = """DELETE FROM metadata.datacontact WHERE uid IN (""" + uid_delete_list_contact[:- 1] + """);"""
+ cur.execute(SQL_delete_contact)
+ uid_delete_list_contact = ''
+
+ list_champs_sql += 'table_name, schema_name, title, abstract, keywords, data_last_update, langue, categories, themes, creation_date, update_date, encode, geom, spatial_extent, feature_count, geometry_type, projection_name, projection_authid, spatial_level, minimum_optimal_scale, maximum_optimal_scale, publication_date, publication_frequency, confidentiality, license, license_attribution'
+ values_sql_add += "'" + table_name + "', '" + schema_name + "', '" + text_titre + "', '" + text_description + "', '" + text_mots_cles + "', '" + text_date_maj + "', '" + text_langue + "', '" + array_categories + "', '" + array_themes + "', '" + text_date_creation + "', '" + text_date_modification + "', '" + text_encode + "', '" + text_extend + "', '" + text_extend + "', '" + int_nbr_entites + "', '" + text_geomtype + "', '" + text_crsname + "', '" + text_crscode + "', '" + text_niveau + "'," + text_echelle_min + "," + text_echelle_max + ",'" + text_date_publication + "', '" + text_frequence + "', '" + text_restriction + "', '" + text_licence + "', '" + text_licence_attrib + "'"
+
+ SQL_add = """INSERT INTO metadata.dataset (""" + list_champs_sql + """) VALUES (""" + values_sql_add + """);"""
+
+ cur.execute(SQL_add)
+
+ global array_link, array_contact
+ if len(array_link) >= 25:
+ array_link = array_link[:- 1]
+ SQL_add_link = """INSERT INTO metadata.dataurl (table_name, schema_name, type, url, mime, format, taille) VALUES """ + array_link + """;"""
+ cur.execute(SQL_add_link)
+ array_link = ''
+
+ if len(array_contact) >= 25:
+ array_contact = array_contact[0:- 1]
+ SQL_add_contact = """INSERT INTO metadata.datacontact (table_name, schema_name, role, nom, organisation, email, telephone) VALUES """ + array_contact + """;"""
+ cur.execute(SQL_add_contact)
+ array_contact = ''
+
+ con.commit()
+ cur.close()
+ self.close()
+
+ iface.layerTreeView().setCurrentLayer(None)
+ iface.layerTreeView().setCurrentLayer(layer)
+
+ def raise_(self):
+ self.activateWindow()
+ global layer
+ layer = iface.activeLayer()
+ global uid_delete_list_link, uid_delete_list_contact, array_link, array_contact
+ uid_delete_list_link = ''
+ uid_delete_list_contact = ''
+ array_link = ''
+ array_contact = ''
+
+ is_ok = self.is_in_psql(layer)
+ if is_ok:
+ exist = self.status_metadata(layer)
+ if exist:
+ self.reload_data(layer)
+ else:
+ self.new_data(layer)
+ else:
+ self.close()
+ iface.messageBar().pushMessage("Information:", "Cette couche n'est pas stockée dans PostgreSQL", level=Qgis.Warning, duration=30)
+
+ def is_in_psql(self, layer):
+ try:
+ uri = layer.dataProvider().uri()
+ except AttributeError:
+ uri = ''
+ return False
+ if uri != '':
+ if not uri.table():
+ return False
+ else:
+ return True
+
+ def status_metadata(self, layer):
+ uri = layer.dataProvider().uri()
+ table = uri.table()
+ schema = uri.schema()
+
+ cur = login_base()
+ count_sql = """ SELECT count(uid) FROM metadata.dataset WHERE table_name LIKE '""" + table + """' AND schema_name LIKE '""" + schema + """';"""
+
+ cur.execute(count_sql)
+ data_count = (cur.fetchall())[0][0]
+ if data_count == 1:
+ return True
+ else:
+ return False
+ cur.close()
+
+ def new_data(self, layer):
+ # print(layer.name(),'is new data')
+ reloader = False
+ self.interface_view(layer, reloader)
+
+ def reload_data(self, layer):
+ # print(layer.name(),'reload data')
+ reloader = True
+ self.interface_view(layer, reloader)
+
+ def interface_view(self, layer, reloader):
+
+ self.description_text.setText(None)
+ self.mots_cles_text.setText(None)
+ self.uuid_ligne.setText(None)
+ self.niveau_plain.setPlainText(None)
+ self.echelle_min_plain.setPlainText(None)
+ self.echelle_max_plain.setPlainText(None)
+ self.url_line.setText(None)
+ self.taille_line.setText(None)
+ self.nom_line.setText(None)
+ self.email_line.setText(None)
+ self.telephone_line.setText(None)
+ self.encodage_box.clear()
+ self.frequence_box.clear()
+ self.licence_box.clear()
+ self.licence_attrib_box.clear()
+ self.restriction_box.clear()
+
+ all_list = self.fletch_ref()
+
+ categories_list = all_list[0]
+ themes_list = all_list[1]
+ langue_list = all_list[2]
+ encodage_list = all_list[3]
+ frequency_list = all_list[4]
+ confidentiality_list = all_list[5]
+ license_list = all_list[6]
+ type_list = all_list[7]
+ mime_list = all_list[8]
+ format_list = all_list[9]
+ role_list = all_list[10]
+ organisation_list = all_list[11]
+
+ # langue_box
+ self.langue_box.clear()
+ self.langue_box.addItem('')
+ # self.langue_box.addItem('Fr')
+ # self.langue_box.addItem('En')
+ for langue_list_data in langue_list:
+ self.langue_box.addItem(langue_list_data[0])
+
+ for encodage_list_data in encodage_list:
+ self.encodage_box.addItem(encodage_list_data[0])
+
+ self.table_ligne.setText(layer.dataProvider().uri().table())
+ self.schema_ligne.setText(layer.dataProvider().uri().schema())
+
+ # categories_select_view
+ self.categories_select_view.setColumnCount(1)
+ self.categories_select_view.setColumnWidth(0, 230)
+ self.categories_select_view.setHorizontalHeaderLabels(['List des categories'])
+ # categories_view
+ self.categories_view.setRowCount(0)
+ self.categories_view.setColumnCount(1)
+ self.categories_view.setColumnWidth(0, 230)
+ self.categories_view.setHorizontalHeaderLabels(['Categories'])
+
+ # themes_select_view
+ self.themes_select_view.setColumnCount(1)
+ self.themes_select_view.setColumnWidth(0, 230)
+ self.themes_select_view.setHorizontalHeaderLabels(['List des thèmes'])
+ # themes_view
+ self.themes_view.setRowCount(0)
+ self.themes_view.setColumnCount(1)
+ self.themes_view.setColumnWidth(0, 230)
+ self.themes_view.setHorizontalHeaderLabels(['Thèmes'])
+
+ # lien_view
+ self.table_lien.setRowCount(0)
+ self.table_lien.setColumnCount(6)
+ self.table_lien.setColumnWidth(0, 0)
+ self.table_lien.setHorizontalHeaderLabels(['', 'Type', 'URL', 'MIME', 'Format', 'Taille'])
+
+ # contact_view
+ self.table_contact.setRowCount(0)
+ self.table_contact.setColumnCount(6)
+ self.table_contact.setColumnWidth(0, 0)
+ self.table_contact.setHorizontalHeaderLabels(['', 'Rôle', 'Nom', 'Organisation', 'Email', 'Telephone'])
+
+ # print(self.date_maj_date.date().toPyDate())
+ vector_extend = layer.extent()
+ polygone_extend = QgsGeometry.fromRect(vector_extend).asWkt()
+ self.extend_plaintext.setPlainText(str(polygone_extend))
+
+ qgstype = str(layer.type())[10:]
+
+ if qgstype != 'Raster':
+ count_layers = str(layer.featureCount())
+ geomtype = QgsWkbTypes.displayString(layer.wkbType())
+ elif qgstype == 'Raster':
+ count_layers = str(layer.dataProvider().bandCount())
+ geomtype = qgstype
+
+ self.nbr_layers.setPlainText(count_layers)
+ self.typegeom_plaintext.setPlainText(geomtype)
+ crs_name = str(layer.crs().description())
+ self.crsname_plaintext.setPlainText(crs_name)
+ crs_code = str(layer.crs().authid())
+ self.crscode_plaintext.setPlainText(crs_code)
+
+ self.frequence_box.addItem('')
+ self.restriction_box.addItem('')
+ self.licence_box.addItem('')
+ self.licence_attrib_box.addItem('')
+ for frequency_list_data in frequency_list:
+ self.frequence_box.addItem(frequency_list_data[0])
+ for confidentiality_list_data in confidentiality_list:
+ self.restriction_box.addItem(confidentiality_list_data[0])
+ for license_list_data in license_list:
+ self.licence_box.addItem(license_list_data[0])
+
+ self.type_box.clear()
+ self.mime_box.clear()
+ self.format_box.clear()
+ self.role_box.clear()
+ self.organisation_box.clear()
+
+ self.type_box.addItem('')
+ self.mime_box.addItem('')
+ self.format_box.addItem('')
+ self.role_box.addItem('')
+ self.organisation_box.addItem('')
+
+ for type_list_data in type_list:
+ self.type_box.addItem(type_list_data[0])
+ for mime_list_data in mime_list:
+ self.mime_box.addItem(mime_list_data[0])
+ for format_list_data in format_list:
+ self.format_box.addItem(format_list_data[0])
+ for role_list_data in role_list:
+ self.role_box.addItem(role_list_data[0])
+ for organisation_list_data in organisation_list:
+ self.organisation_box.addItem(organisation_list_data[0])
+
+ if reloader:
+ sql_dataload = self.sql_info(layer.dataProvider().uri())
+ sql_contactlink = self.sql_infoother(layer.dataProvider().uri())
+ sql_datalink = sql_contactlink[0]
+ sql_datacontact = sql_contactlink[1]
+
+ # print(sql_dataload)
+ self.titre_line.setText(sql_dataload[4])
+ self.date_maj_date.setDateTime(sql_dataload[23])
+ self.date_publication_date.setDateTime(sql_dataload[11])
+ self.description_text.setText(sql_dataload[5])
+ self.mots_cles_text.setText(sql_dataload[7])
+ array_langue_box = [self.langue_box.itemText(i) for i in range(self.langue_box.count())]
+ self.langue_box.setCurrentIndex(array_langue_box.index(sql_dataload[26]))
+ self.uuid_ligne.setText(sql_dataload[1])
+
+ self.categories_view.setRowCount(len(sql_dataload[6]))
+ i = 0
+ for categorie_data in sql_dataload[6]:
+ self.categories_view.setItem(i, 0, QTableWidgetItem(categorie_data))
+ i = i + 1
+ self.themes_view.setRowCount(len(sql_dataload[24]))
+ i = 0
+ for themes_data in sql_dataload[24]:
+ self.themes_view.setItem(i, 0, QTableWidgetItem(themes_data))
+ i = i + 1
+
+ self.categories_select_view.setRowCount(len(categories_list) - len(sql_dataload[6]))
+ self.themes_select_view.setRowCount(len(themes_list) - len(sql_dataload[24]))
+ i = 0
+ for categorie_select_data in categories_list:
+ try:
+ in_index = sql_dataload[6].index(categorie_select_data[0])
+ in_index = False
+ except ValueError:
+ in_index = True
+ if in_index:
+ self.categories_select_view.setItem(i, 0, QTableWidgetItem(categorie_select_data[0]))
+ i = i + 1
+ i = 0
+ for themes_select_data in themes_list:
+ try:
+ in_index = sql_dataload[24].index(themes_select_data[0])
+ in_index = False
+ except ValueError:
+ in_index = True
+ if in_index:
+ self.themes_select_view.setItem(i, 0, QTableWidgetItem(themes_select_data[0]))
+ i = i + 1
+
+ array_encodage_box = [self.encodage_box.itemText(i) for i in range(self.encodage_box.count())]
+ self.encodage_box.setCurrentIndex(array_encodage_box.index(sql_dataload[27]))
+
+ self.niveau_plain.setPlainText(sql_dataload[8])
+
+ if str(sql_dataload[9]) == 'None':
+ value_echelle_min = ''
+ else:
+ value_echelle_min = str(sql_dataload[9])
+
+ if str(sql_dataload[10]) == 'None':
+ value_echelle_max = ''
+ else:
+ value_echelle_max = str(sql_dataload[10])
+
+ self.echelle_min_plain.setPlainText(value_echelle_min)
+ self.echelle_max_plain.setPlainText(value_echelle_max)
+
+ array_frequence_box = [self.frequence_box.itemText(i) for i in range(self.frequence_box.count())]
+ self.frequence_box.setCurrentIndex(array_frequence_box.index(sql_dataload[12]))
+ array_licence_box = [self.licence_box.itemText(i) for i in range(self.licence_box.count())]
+ self.licence_box.setCurrentIndex(array_licence_box.index(sql_dataload[13]))
+ array_confidentiality_box = [self.restriction_box.itemText(i) for i in range(self.restriction_box.count())]
+ self.restriction_box.setCurrentIndex(array_confidentiality_box.index(sql_dataload[14]))
+ array_licence_attrib_box = [self.licence_attrib_box.itemText(i) for i in range(self.licence_attrib_box.count())]
+ self.licence_attrib_box.setCurrentIndex(array_licence_attrib_box.index(sql_dataload[25]))
+
+ c = 0
+ # self.table_lien.setRowCount(len(sql_datalink))
+ for lien_data in sql_datalink:
+ self.table_lien.insertRow(c)
+ self.table_lien.setItem(c, 0, QTableWidgetItem(lien_data[1]))
+ self.table_lien.setItem(c, 1, QTableWidgetItem(lien_data[4]))
+ self.table_lien.setItem(c, 2, QTableWidgetItem(lien_data[5]))
+ self.table_lien.setItem(c, 3, QTableWidgetItem(lien_data[6]))
+ self.table_lien.setItem(c, 4, QTableWidgetItem(lien_data[7]))
+ self.table_lien.setItem(c, 5, QTableWidgetItem(lien_data[8]))
+ c = c + 1
+ c = 0
+ # self.table_contact.setRowCount(len(sql_datacontact))
+ for contact_data in sql_datacontact:
+ self.table_contact.insertRow(c)
+ self.table_contact.setItem(c, 0, QTableWidgetItem(contact_data[1]))
+ self.table_contact.setItem(c, 1, QTableWidgetItem(contact_data[4]))
+ self.table_contact.setItem(c, 2, QTableWidgetItem(contact_data[5]))
+ self.table_contact.setItem(c, 3, QTableWidgetItem(contact_data[6]))
+ self.table_contact.setItem(c, 4, QTableWidgetItem(contact_data[7]))
+ self.table_contact.setItem(c, 5, QTableWidgetItem(contact_data[8]))
+ c = c + 1
+
+ else:
+
+ # titre_line
+ self.titre_line.setText(layer.name())
+ self.langue_box.setCurrentIndex(1)
+ # date_maj_date
+ now = QtCore.QDateTime.currentDateTime()
+ self.date_maj_date.setDateTime(now)
+ self.date_creation_date.setDateTime(now)
+ self.date_modification_date.setDateTime(now)
+ self.date_publication_date.setDateTime(now)
+ self.categories_select_view.setRowCount(len(categories_list))
+ self.themes_select_view.setRowCount(len(themes_list))
+ i = 0
+ for categorie_select_data in categories_list:
+ self.categories_select_view.setItem(i, 0, QTableWidgetItem(categorie_select_data[0]))
+ i = i + 1
+ i = 0
+ for themes_select_data in themes_list:
+ self.themes_select_view.setItem(i, 0, QTableWidgetItem(themes_select_data[0]))
+ i = i + 1
+
+ # print(self.langue_box.currentText())
+
+ def sql_info(self, uri):
+ cur = login_base()
+ table = uri.table()
+ schema = uri.schema()
+ # [s for s in iface.activeLayer().source().split(" ") if "dbname" in s][0].split("'")[1]
+ sql_find = """SELECT *, right(left(st_astext(geom,2),-2),-9) FROM metadata.dataset
+ WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_find)
+ data_general = cur.fetchall()
+ cur.close()
+ return data_general[0]
+
+ def sql_infoother(self, uri):
+ cur = login_base()
+ table = uri.table()
+ schema = uri.schema()
+
+ sql_findlink = """SELECT * FROM metadata.dataurl
+ WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_findlink)
+ data_link = cur.fetchall()
+
+ sql_findcontact = """SELECT * FROM metadata.datacontact
+ WHERE schema_name LIKE '""" + schema + """' AND table_name LIKE '""" + table + """';"""
+ cur.execute(sql_findcontact)
+ data_contact = cur.fetchall()
+
+ cur.close()
+ return data_link, data_contact
+
+ def add_categories_view(self):
+ values_add_categories = self.categories_select_view.selectedItems()[0].text()
+ self.categories_select_view.removeRow(self.categories_select_view.currentRow())
+ self.categories_view.insertRow(0)
+ self.categories_view.setItem(0, 0, QTableWidgetItem(values_add_categories))
+
+ def deleter_categories_view(self):
+ values_deleter_categories = self.categories_view.selectedItems()[0].text()
+ self.categories_view.removeRow(self.categories_view.currentRow())
+ self.categories_select_view.insertRow(0)
+ self.categories_select_view.setItem(0, 0, QTableWidgetItem(values_deleter_categories))
+
+ def add_themes_view(self):
+ values_add_themes = self.themes_select_view.selectedItems()[0].text()
+ self.themes_select_view.removeRow(self.themes_select_view.currentRow())
+ self.themes_view.insertRow(0)
+ self.themes_view.setItem(0, 0, QTableWidgetItem(values_add_themes))
+
+ def deleter_themes_view(self):
+ values_deleter_themes = self.themes_view.selectedItems()[0].text()
+ self.themes_view.removeRow(self.themes_view.currentRow())
+ self.themes_select_view.insertRow(0)
+ self.themes_select_view.setItem(0, 0, QTableWidgetItem(values_deleter_themes))
+
+ def add_lien(self):
+ cur = login_base()
+ maxrow = self.table_lien.rowCount()
+ self.table_lien.insertRow(maxrow)
+
+ table = layer.dataProvider().uri().table()
+ schema = layer.dataProvider().uri().schema()
+
+ if self.taille_line.text() == '':
+ sql_sizefile = """SELECT pg_size_pretty(pg_total_relation_size('""" + schema + '.' + table + """'));"""
+ try:
+ cur.execute(sql_sizefile)
+ boolean = True
+
+ except psycopg2.errors.UndefinedTable:
+ boolean = False
+
+ if boolean is True:
+ size_file = (cur.fetchall())[0][0]
+ else:
+ size_file = ''
+
+ else:
+ size_file = self.taille_line.text()
+
+ self.table_lien.setItem(maxrow, 0, QTableWidgetItem('new_value'))
+ self.table_lien.setItem(maxrow, 1, QTableWidgetItem(self.type_box.currentText()))
+ self.table_lien.setItem(maxrow, 2, QTableWidgetItem(self.url_line.text()))
+ self.table_lien.setItem(maxrow, 3, QTableWidgetItem(self.mime_box.currentText()))
+ self.table_lien.setItem(maxrow, 4, QTableWidgetItem(self.format_box.currentText()))
+ self.table_lien.setItem(maxrow, 5, QTableWidgetItem(str(size_file)))
+
+ global array_link
+ array_link += "('" + table + "', '" + schema + "', '" + self.type_box.currentText() + "', '" + self.url_line.text() + "', '" + self.mime_box.currentText() + "', '" + self.format_box.currentText() + "', '" + size_file + "'),"
+
+ cur.close()
+
+ def add_contact(self):
+ maxrow = self.table_contact.rowCount()
+ self.table_contact.insertRow(maxrow)
+
+ self.table_contact.setItem(maxrow, 0, QTableWidgetItem('new_value'))
+ self.table_contact.setItem(maxrow, 1, QTableWidgetItem(self.role_box.currentText()))
+ self.table_contact.setItem(maxrow, 2, QTableWidgetItem(self.nom_line.text()))
+ self.table_contact.setItem(maxrow, 3, QTableWidgetItem(self.organisation_box.currentText()))
+ self.table_contact.setItem(maxrow, 4, QTableWidgetItem(self.email_line.text()))
+ self.table_contact.setItem(maxrow, 5, QTableWidgetItem(self.telephone_line.text()))
+
+ table = layer.dataProvider().uri().table()
+ schema = layer.dataProvider().uri().schema()
+
+ global array_contact
+ array_contact += "('" + table + "', '" + schema + "', '" + self.role_box.currentText() + "', '" + self.nom_line.text() + "', '" + self.organisation_box.currentText() + "', '" + self.email_line.text() + "', '" + self.telephone_line.text() + "'),"
+
+ def delete_lien(self):
+ fin = ''
+ global uid_delete_list_link, array_link
+ try:
+ lien_uid = self.table_lien.item(self.table_lien.currentRow(), 0).text()
+ except AttributeError:
+ lien_uid = True
+ self.table_lien.removeRow(self.table_lien.currentRow())
+ if lien_uid == 'new_value':
+ position = self.table_lien.currentRow()
+ if position < 0:
+ position = position + 1
+ run_x = 0
+ while position >= run_x:
+ # print(position, run_x)
+ if run_x == 0:
+ debut = array_link.find("(")
+ else:
+ debut = array_link.find("(", fin + 1)
+ fin = array_link.find(")", debut)
+ # print(debut, fin)
+ if run_x == 50:
+ break
+ run_x += 1
+ # print(array_link[fin + 1:])
+ if debut <= 0:
+ debut = 1
+ fin += 1
+ array_link = array_link[:debut - 1] + array_link[fin + 1:]
+ # print('a:', array_link)
+ elif lien_uid is True:
+ print('Pas de ligne "Lien"')
+ else:
+ uid_delete_list_link += "'" + lien_uid + "',"
+
+ def delete_contact(self):
+ fin = ''
+ global uid_delete_list_contact, array_contact
+ try:
+ contact_uid = self.table_contact.item(self.table_contact.currentRow(), 0).text()
+ except AttributeError:
+ contact_uid = True
+ self.table_contact.removeRow(self.table_contact.currentRow())
+ if contact_uid == 'new_value':
+ position = self.table_contact.currentRow()
+ if position < 0:
+ position = position + 1
+ # print('p:', position)
+ run_x = 0
+ while position >= run_x:
+ if run_x == 0:
+ debut = array_contact.find("(")
+ else:
+ debut = array_contact.find("(", fin + 1)
+ fin = array_contact.find(")", debut)
+ # print(debut, fin)
+ if run_x == 50:
+ break
+ run_x += 1
+ # print(array_contact[fin + 1:])
+ if debut <= 0:
+ debut = 1
+ fin += 1
+ array_contact = array_contact[:debut - 1] + array_contact[fin + 1:]
+ # print('a:', array_contact)
+ elif contact_uid is True:
+ print('Pas de ligne "Contact"')
+ else:
+ uid_delete_list_contact += "'" + contact_uid + "',"
+
+ def fletch_ref(self):
+ cur = login_base()
+
+ SQL_categories = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.categories' ORDER BY code, item_order;"""
+ SQL_themes = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.themes' ORDER BY label_fr;"""
+ SQL_langue = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.langue';"""
+ SQL_encodage = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.encodage';"""
+ SQL_frequency = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.publication_frequency' ORDER BY label_fr;"""
+ SQL_confidentiality = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.confidentiality' ORDER BY label_fr;"""
+ SQL_license = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'dataset.license' ORDER BY label_fr;"""
+ SQL_type = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'link.type' ORDER BY label_fr;"""
+ SQL_mime = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'link.mime' ORDER BY label_fr;"""
+ SQL_format = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'link.format' ORDER BY label_fr;"""
+ SQL_role = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'contact.contact_role' ORDER BY label_fr;"""
+ SQL_organisation = """SELECT label_fr FROM metadata.glossary WHERE field LIKE 'contact.organisation' ORDER BY label_fr;"""
+
+ cur.execute(SQL_categories)
+ categories_list = cur.fetchall()
+ cur.execute(SQL_themes)
+ themes_list = cur.fetchall()
+ cur.execute(SQL_langue)
+ langue_list = cur.fetchall()
+ cur.execute(SQL_encodage)
+ encodage_list = cur.fetchall()
+
+ cur.execute(SQL_frequency)
+ frequency_list = cur.fetchall()
+ cur.execute(SQL_confidentiality)
+ confidentiality_list = cur.fetchall()
+ cur.execute(SQL_license)
+ license_list = cur.fetchall()
+
+ cur.execute(SQL_type)
+ type_list = cur.fetchall()
+ cur.execute(SQL_mime)
+ mime_list = cur.fetchall()
+ cur.execute(SQL_format)
+ format_list = cur.fetchall()
+ cur.execute(SQL_role)
+ role_list = cur.fetchall()
+ cur.execute(SQL_organisation)
+ organisation_list = cur.fetchall()
+
+ return categories_list, themes_list, langue_list, encodage_list, frequency_list, confidentiality_list, license_list, type_list, mime_list, format_list, role_list, organisation_list
+
+ cur.close()
+
+ def py_import_xml(self):
+ folder = QFileDialog.getOpenFileName()
+ if folder:
+ folder = folder[0]
+ if folder[len(folder) - 4:] == '.xml':
+ print('is .xml')
+
+ # def issues_open(self):
+ # self.issues = CenRa_Issues()
+ # self.issues.show()
diff --git a/CenRa_METABASE/icon.png b/CenRa_METABASE/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..d53a910fea37e4930c40122d285e6ea9fdc75ba7
GIT binary patch
literal 4977
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
literal 0
HcmV?d00001
diff --git a/CenRa_METABASE/issues.py b/CenRa_METABASE/issues.py
new file mode 100644
index 0000000..0d6416a
--- /dev/null
+++ b/CenRa_METABASE/issues.py
@@ -0,0 +1,89 @@
+import os
+plugin_dir = os.path.dirname(__file__)
+end_find = plugin_dir.rfind('\\')+1
+
+NAME = plugin_dir[end_find:]
+#print(NAME)
+
+from qgis.gui import *
+
+from qgis.core import (
+ NULL,
+ QgsApplication,
+ QgsDataSourceUri,
+ QgsProject,
+ QgsProviderConnectionException,
+ QgsProviderRegistry,
+ QgsRasterLayer,
+ QgsSettings,
+ QgsVectorLayer,
+ QgsGeometry,
+)
+from qgis.PyQt.QtWidgets import (
+ QDialog,
+ QAction,
+ QDockWidget,
+ QFileDialog,
+ QInputDialog,
+ QMenu,
+ QToolButton,
+ QTableWidget,
+ QTableWidgetItem,
+)
+from qgis.utils import iface
+
+
+from .tools.resources import (
+ load_ui,
+ resources_path,
+ send_issues,
+)
+
+EDITOR_CLASS = load_ui('CenRa_IssuesSend.ui')
+
+class CenRa_Issues(QDialog, EDITOR_CLASS):
+
+ def __init__(self, parent=None):
+ _ = parent
+ super().__init__()
+ self.setupUi(self)
+ self.settings = QgsSettings()
+
+ #place connect here
+ self.annuler_button.clicked.connect(self.close)
+ self.ok_button.clicked.connect(self.run_sendissues)
+
+ def run_sendissues(self):
+ text_titre = self.titre_line.text()
+ text_message = self.messages_plain.toPlainText()
+ statu_bug = self.check_bug.isChecked()
+ statu_aide = self.check_aide.isChecked()
+ statu_question = self.check_question.isChecked()
+ statu_amelioration = self.check_amelioration.isChecked()
+ statu_autre = self.check_autre.isChecked()
+
+ statu = []
+ if statu_bug == True : statu = statu + [1]
+ if statu_aide == True : statu = statu + [3]
+ if statu_question == True : statu = statu + [5]
+ if statu_amelioration == True : statu = statu + [2]
+ if statu_autre == True : statu = statu + [6]
+
+ if len(statu) >= 1:
+ import qgis
+ url = qgis.utils.pluginMetadata(NAME,'tracker')
+ print(text_message)
+ send_info = send_issues(url,text_titre,text_message,statu)
+ code = send_info.status_code
+ print(code)
+ else:
+ code = 423
+ if code == 201:
+ iface.messageBar().pushMessage("Envoyer :", "Votre messages à bien été envoyer.", level=Qgis.Success, duration=20)
+ self.close()
+ elif code == 422:
+ iface.messageBar().pushMessage("Erreur :", "Erreur dans le contenu du messages.", level=Qgis.Critical, duration=20)
+ elif code == 423:
+ iface.messageBar().pushMessage("Erreur :", "Pas de sujet sélectionné.", level=Qgis.Critical, duration=20)
+ elif code == 404:
+ iface.messageBar().pushMessage("Missing :", "Le serveur de messagerie est injoignable.", level=Qgis.Warning, duration=20)
diff --git a/CenRa_METABASE/metadata.txt b/CenRa_METABASE/metadata.txt
new file mode 100644
index 0000000..8c689d1
--- /dev/null
+++ b/CenRa_METABASE/metadata.txt
@@ -0,0 +1,49 @@
+# This file contains metadata for your plugin.
+
+# This file should be included when you package your plugin.# Mandatory items:
+
+[general]
+name=CenRa_Metabase
+qgisMinimumVersion=3.0
+supportsQt6=True
+description=CenRa_METABASE
+version=0.3.1
+author=Conservatoire d'Espaces Naturels de Rhône-Alpes
+email=si_besoin@cen-rhonealpes.fr
+
+about=Permet de saisire et de visualisé les information lier à la metadonné d'une couche ce trouvent sur PostgreSQL
+
+repository=https://gitea.cenra-outils.org/CEN-RA/Plugin_QGIS
+homepage=https://plateformesig.cenra-outils.org/
+tracker=https://gitea.cenra-outils.org/api/v1/repos/CEN-RA/Plugin_QGIS/issues
+# End of mandatory metadata
+
+# Recommended items:
+
+hasProcessingProvider=no
+# Uncomment the following line and add your changelog:
+changelog=CenRa_METABASE:
30/07/2025 - Version 0.3.1:
- Correctife de bug.
19/05/2025 - Version 0.3.0:
- Compatible PyQt5 et PyQt6
09/04/2025 - Version 0.2.3:
- Correctif bug en TT.09/04/2025 - Version 0.2.2:
- Optimisation pour le TT.03/04/2025 - Version 0.2.1:
- Mise a jour de securite.07/01/2025 - Version 0.2.0:
- Deployment sur serveur SIG.07/01/2025 - Version 0.1.6:
- ByPass du certif ssl ci erreur19/12/2024 - Version 0.1.5:
- Fix les problem de lenteur qu'en la base est down.12/12/2024 - Version 0.1.4:
- Crash Fix .08/10/2024 - Version 0.1.3:
- Lecture de métadonnée des flux WMS/WFS.07/10/2024 - Version 0.1.2:
- Correctif de bug.03/10/2024 - Version 0.1.1:
- Remonte la fênetre dans la pille.26/08/2024 - Version 0.1.0:
- Lancement du plugin CenRa_Metabase
+
+# Tags are comma separated with spaces allowed
+tags=python
+
+
+category=Plugins
+icon=icon.png
+# experimental flag
+experimental=True
+
+# deprecated flag (applies to the whole plugin, not just a single version)
+deprecated=False
+
+# Since QGIS 3.8, a comma separated list of plugins to be installed
+# (or upgraded) can be specified.
+# Check the documentation for more information.
+# plugin_dependencies=
+
+Category of the plugin: Raster, Vector, Database or Web
+# category=cenra,database,metadata
+
+# If the plugin can run on QGIS Server.
+server=False
+
diff --git a/CenRa_METABASE/tools/css/dock.css b/CenRa_METABASE/tools/css/dock.css
new file mode 100644
index 0000000..f05b850
--- /dev/null
+++ b/CenRa_METABASE/tools/css/dock.css
@@ -0,0 +1,61 @@
+body {
+ font-family: Ubuntu, Lucida Grande, Segoe UI, Arial, sans-serif;
+ margin-left: 0px;
+ margin-right: 0px;
+ margin-top: 0px;
+ font-size: 14px;
+}
+img {
+ max-width: 100%;
+}
+img.logo{
+ display: inline-block;
+ margin-left:0px;
+ margin-right: 0px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ vertical-align: top;
+ width:25%
+}
+h2, h3 {
+ color: #fff;
+ background-color: #8cb63c;
+ line-height: 2;
+ padding-left:5px;
+}
+
+table {
+ border-collapse: collapse;
+ width: 100%;
+ font-size: 13px;
+}
+th{
+ color: #2c4491
+}
+table tr th, table tr td {
+ text-align: left;
+ padding: 5px;
+}
+
+table.table-striped {
+ border: 1px solid #BBB;
+}
+table.table-striped tr td {
+ border: 1px solid #BBB;
+}
+table.table-striped tr th {
+ border: 1px solid #BBB;
+}
+table.table-striped tr:nth-child(even) {
+ background: #EEE;
+}
+table.table-striped tr:nth-child(odd) {
+ background: #FFF;
+}
+
+#map {
+ padding: 5px;
+ width: 400px;
+ height: 400px;
+ box-shadow: 0 0 10px #999;
+}
diff --git a/CenRa_METABASE/tools/html/contact.html b/CenRa_METABASE/tools/html/contact.html
new file mode 100644
index 0000000..a6cda01
--- /dev/null
+++ b/CenRa_METABASE/tools/html/contact.html
@@ -0,0 +1,7 @@
+
+ | [% contact_role %] |
+ [% name %] |
+ [% organisation_name %] ([% organisation_unit %]) |
+ [% email %] |
+ [% phone %] |
+
diff --git a/CenRa_METABASE/tools/html/link.html b/CenRa_METABASE/tools/html/link.html
new file mode 100644
index 0000000..cdcdabe
--- /dev/null
+++ b/CenRa_METABASE/tools/html/link.html
@@ -0,0 +1,7 @@
+
+ | [% type %] |
+ [% name %] |
+ [% mime %] |
+ [% format %] |
+ [% size %] |
+
diff --git a/CenRa_METABASE/tools/html/main.html b/CenRa_METABASE/tools/html/main.html
new file mode 100644
index 0000000..f7ff4a0
--- /dev/null
+++ b/CenRa_METABASE/tools/html/main.html
@@ -0,0 +1,123 @@
+
+
Identification
+
+
+ | Title | [% title %] |
+
+
+ | Abstract | [% abstract %] |
+
+
+ | Categories | [% categories %] |
+
+
+ | Themes | [% themes %] |
+
+
+ | Keywords | [% keywords %] |
+
+
+ | Data last update | [% data_last_update %] |
+
+
+
+
+
+
Spatial properties
+
+
+ | Level | [% spatial_level %] |
+
+
+ | Minimum scale | [% minimum_optimal_scale %] |
+
+
+ | Maximum scale | [% maximum_optimal_scale %] |
+
+
+ | Feature count | [% feature_count %] |
+
+
+ | Geometry | [% geometry_type %] |
+
+
+ | Extent | [% spatial_extent %] |
+
+
+ | Projection name | [% projection_name %] |
+
+
+ | Projection ID | [% projection_authid %] |
+
+
+
+
+
+
Publication
+
+
+ | Date | [% publication_date %] |
+
+
+ | Frequency | [% publication_frequency %] |
+
+
+ | License | [% license %] |
+
+
+ | License attribution / number | [% license_attribution %] |
+
+
+ | Confidentiality | [% confidentiality %] |
+
+
+
+
+
+
Links
+
+
+ | Type |
+ Name |
+ MIME |
+ Format |
+ Size |
+
+ [% meta_links %]
+
+
+
+
+
Contacts
+
+
+ | Role |
+ Name |
+ Organisation |
+ Email |
+ Phone |
+
+ [% meta_contacts %]
+
+
+
+
+
Metadata
+
+
+ | Table | [% table_name %] |
+
+
+ | Schema | [% schema_name %] |
+
+
+ | Creation | [% creation_date %] |
+
+
+ | Update | [% update_date %] |
+
+
+ | UUID | [% uid %] |
+
+
+
diff --git a/CenRa_METABASE/tools/icons/CEN_RA.png b/CenRa_METABASE/tools/icons/CEN_RA.png
new file mode 100644
index 0000000000000000000000000000000000000000..95ead1cf7313bc03e4637c9bb20945373f27ddca
GIT binary patch
literal 8893
zcmV;uB0}AXP)K)DMFBnwKo7Qh#EMIg|aFYHH(O}GbKNdB0Y``F^Pk*BOg1B5i^SS`ttYq
z`!Y(Epu%I^-|D}_+1}~S`~3c%rpKqQ%K$2ef{nQO`}TTTrIrPG6styGiTv;Na@f!q=eB)8v<+#8_yfy=(xk%74h)tYB}aPGFy>
z#&DRuRM_Rg@b&0tb*ca)g|f|qe2BG|yinom(7)B1>CwES#A^BS>i`{t#C|gG-p<~+
znbM$f*Q$cZh(!Pwfqbn8%#c;Xdn)6_s`TmJ01tp!g#P3J03ZNKL_t(|ob6o+TiZI;
zPF(M6780_OI3Wvx5C}^Wp!9Ornd!_o^Z$R%caAp8woJJ#w58=dcc#RaC0Va@Hk~7v
zN;&Fp?>Fni;rYYf(NS5@wm-ZY9-a&~$J>7XnKyb?+HMZ_j_lC3UDq`XBSE2TAD2$>
zs%^vV^7Z-2X7L8c%g|z24sBN+d>My9X=C#r01*HZ9-Xg`-@tfo+J4xx?E)0aW%s1C
zu+x_eJNy`yZ|~orct#qWEa3k)S7E_|aEGO8DF3#@>kk_$D*A6oJQtmWz-Z1lFs3Jl
zU4TPL;9%J9`9>c6Hzb~i1|gl}#$gpOaa}bI?6MK=ZL$@-;qV+JIF8|l!+xoM9v0(Z
zgjWki6>mU11Kmmszzqi=Cvs8(o9AtcvQl>imyG)wD#gANr;0QHD2x
z-U>LlWr-v8H^XZeYz;!;`M2x;k3xftWOj{hskD~{!rlit9o~v(s)N0>v0=Y)rZ1(d
zEE6(pU_RHWtKqh^{U{d0;yh<+AW(1gMP%em2E*Q#`epgD(Jw8yWLSWek$s}<9)8nz
z_yMS@Fc^$-KV?4~dr9&0P-W^#CEn<>Xpz@aVR~PcO6vp&RF^0XY=w1!I2hmZ9exDb
zR~pbE2!lN_@xx-aOn`=
z7xE;sX4HIX={ugT0qN1r_-npaF*Vxl_G5FR?s?|&tMx`u0S@I5EP@AI0O36;cl;;}
z$j21aF#&|gsW=fp;aq&p_bQ}j*Scvwa(|?2!8ceBMe@Ntr}yk3DYh*r#+T2Ps_tf2
z(>$}+TsFs+2S9umE28OWmB(=K%-~<&U_BJ!P_|2@en|KeP};0hpte?2U9$j&QB40{
z`I^@K9%RI)Z~(0$Zhf`h2r9%OTp(j&D(h8NQ!)$^HDF^<0a)ar${XH`G~E}P60CRs
z;A_3rBlIa8z7gFQ;eb+j
zK!5#CaaepTFp*I}+Mr
z-EeO}hkUVFpA2uWuFh`{*PDgXc-VPbbgFK1xeJ0yVAfCN5sllI0g!da-?ce6D^O}a
zNy<#-COlY~akM5;JzADpcNNfzn^E0Qiq5opgcZf(IzD2~JG@p$)9UrQHm5&3qb}ZF
z325&0x<`6&>ea1Qsj)Jt*j`>hpDX6-g=BaY$8{f|$OFM605)tn*zO?~u5N}$A&)k5
zL;K!-
znyFmIy#S1gN@P`FbR5PoYIERXjuWA!5fO3TF*Ut1+KmEF^L1Kk6=}Y6xx=yR()kO#
zuQJ_%KH$L0n|XRgtR>(+6NEw6qsnL=!Qfj5{ElV?uo$sb9iRs5ref)u#eg{Qb&KK<
zX->_DKB8F%ebxi$4fHv95%qg(V|3mS^qoNliI8y{V(A$eLQqT0+k?
zh$l;jJc&1|2KF!RuLC
zWUVG?2|V+062ui?Kx6UUjw>kM2LX;WaFZlhhz{9B?_<*9*lCkoyP>_`dpFbpsSwhw~
z$uTB~v&N^bNjPYpP9M4ien<<(j_$pLV}S1i86gbycF)e?fR6DHHz0=j9iYdY-8fZM
z9fp_pR?^=%T=9+ObGF;Ih(0p~9NO#>1qcQ(;PPg!Q&kLc&;l-kcd)+bUofnWB;HGe
zO;W8vS~Xzch*GeCXzeK+G^fEHws>dmq5^jNWzM3vNbAP55iROTO){ij*2B!x
zOfkMNBav$>a45||g!*l1Fz0%LD+~-_E(8Zrrsn7jlqlVHW6}c_n8g90NlF1zqt(ke
z@CKoq0WSy>M68YHBzc+};ik0Zfde+Vy$*5fc~4<=C%dIeLiup1xDNckB7SsWaV*t1
zOuITNHZ!66R&>1%wjk2woXHr#A(GUW3(&3bU#w58xn)l`Egv1f862RLFV+$+O;n*$
zIB-D%_3D?GqUmG>J|m^s4FFN4K(Oil{yp%n_bQWH=~GGw)~`}usn!2*rU_oCi
zVQ@bcO9|GN%AN+bRTn=dLOD|4fIoIHo57B&p@odxyp<4nK8*t~5Y(d@Cciod9&erE
ztZoFV^&mN3V9xuG;82&FehI($`^y~g-ZIVO0MPh}lcLU4VHFMluTwaH
z&qRB<;O<+p-(a)*n}Q8dDU`Bt(_Q8LogjmCUH*OJ+;s3g6CIG4z44zc!1(nT<_a8Ik|mQUPbwS^d`ZW9AFXsecgB?FfKJ2x?S2@wvxw
z0HX{?1`7MxMshfGEM13DqkuKcgfoH(kl?_?7Vs~jFaf)yej^nQ&}UDjgFh!J1{w`z
z=18q7xe{jXCRQAbB2a$e#wI#c>6pelVDl!KhdZBx7V|x81rD9vY{%G#$M%za;fpJ^
z4-N=t&*6Y(_=pc0@EYdgiZ3lmZ{?5Rlwg>EB%z*gB@T#^#q5;H>|pj1yFrI}9B@Hw
z_6zQ!d-mx3WOFQPfN7;^D&wXDoJp_Ld+pN;#}|(02Eb#jnvH22z0E`gRN!#h^}OI<
z+G;Ij_;5G&f$O~rip9tXtbNg%U;Fv1r8M`Fqtk9Kdz@5OX__{gs*i4CU5GcC;P+H+KsbGbAmvJy94y88q2KsD5$rlPU8z_>dg>8_I
z<}kt|v?r>A+oSNK*Z@KU3&~Mf42)EYY(vGr*sq`T0O*e{?h_%zMkL^_I%Tf>pbj9A8SO;TW2c}PIw1m@k
zY(~VME49t2n=qhGh4dL<9lcESPj!Pw31sFy50jLIhY>#DGC6r4Vrg5f2eQ$K9thQj
zVQX>`Ie}x%+p_qmb>@*}=?7wBE$B&sDqWYaQ{Tkzuyld4p>!h
zoDARepdQs{2vtv9?qD4e--GDY;n(O?bgy;Vo2LI@eQsl)z1>U2dJqPa-Js?!?W1I=
zgnLl%SR6SJioFNor^h*8FKD3_t90+$=Q7#Pez35PCyU+M3B{KDv8r5C*9YYM16D
zCdops0mv-!~x^q%V!hnF{XerUqrNjrSF$7nIZg;;)%NP)1g`sochS0s1R?^0
zsY{rpwMp+zCz}skJZN;Q`Heo8goDYo??vtv-E5=5ub2UGlD4ajWb85+(PvRriUP22DG*dIt%f97G@oM~ipb(bJZMRHQI2ALRA&ZPNB@aeO{YH6A2G+J
zs2KYL+Zw{_U_W4%;!)P|AsrtetcOPx*M_M()M9Q~m8=E)il{X{J^d2xyyNJPMrBI;
zAH+f5FxUw)gaIAqHb(87bBT~uxL48;K|jkt8C~{DsP-H8QhgqEq<}Of$v|xJa1Cle
zVARYuzXg#T^COFgtm{*v1P9#B`GObu#)MIUFN%Ytm}1`gAhb!k?)65sVJC%Z+)3i$
z5koG)p<|Iq%2!G+k3(rFxQ~5^CutBv?ohcLqzE!imvJb}40ywV(i3o?`YPv4eG=l0
z2O#G6O^pZf4BZmtl-oWQl;F^GUbDIPHE>9Bq){E(a7!(}T_-IEx+N-bP~_z{NRD8F
zL&KEaAFbtNvP|i*wR~zKO(ksQF6FWY^J$HZ=A@Z^?*=@ke0^3$W123HbtYF3hbN^2
z#k_Cn9;9%9t}InLd~);^G9@Mh^7++r_KB>uDsYGoFh{VvYDhuS^_z)&+i@!0ofy37
zs#|d?X4Gb*e)`+LCZ)zDTnxH9X@EK-d@?ZQYt4}f#mrHnv~_gTaU8QQuGGc>KGvP7
z=j5~(nL!YkV~%;NtwWvo08bwS_0Ts>^MCl;G`l2%Vzt>|=VlKe#|#os0#IXW%pnc_8O0nIqngB2Ub%Zjdh5eDzDe#N?_
zlgE5hp;5#;Gtx+=q{Cul8qg2gPQ*-9uBMZ%7A*;EgX=W-xQAJe(|{#6t^#P!haQ--
zEDjeHUGqrZ<1W9iB`k#`#U#&_q|C%LTgookDLGzaw9*3|w5nXv0LE=Nqr2V+BWfD-
z0lYF>;dpeHtbq}{nKSa>)pWKMT?6c(-rXv|ay;Y?^Bnz4XiJNzfr43$)~vU~bb8vc
zxu<#7Zs$RRK`vByYWbQML?g_Uq&8<9c)AbmTn4_za!AQ~fF<*1HqLl{zg;j!-HyuZUF$5t^8+RW3PI&=_wK;jv&
z2i>WRLk*THi8z^#e2erclj6psj`>k=*<<5UNkEM>TS^kz2u&*DJb~8S%i*w0M8>H_
zT~oe@H|?-|Ucf=;(`A*fLnM!o(|RcDh%L?Q(ovd=Atp=%6H8)NCTDSuJk4aa!{q8_
zrl`XL`BQSb2nUY^&m{r5G0OuKI4qMup-ZZ^{QhUEL9R3zOiVAb`wsHa0X;vBL;CG5
zyu|U>$R{%$h@`?&&C+pNx1uB!lZ^w$gL5H{p?X=B7b+oPQ&TfZ4r%|knzTr!Vlye#
zZOJ&~BLkXJ1l7#TDVIDiGNrc`;hRfHe+vhe+$i^*2h~dJL7%7O^Vz$1XaAKiu*1E%D^Cu~
zxA#(9tdo|~8Gq|Eko2egdcZtdaRn9@a)KUkA+sl204W^W>8R^?(p!NrAUN#2Z0^y{
zOBPRqPIlBAtV^}K^bs5ylisDSJsk(mTR}_+Pa5k%BBxPP(mDwezXA^L&i;_Q)PJxg
zNiFZzeMtAco>>m*^VFIZiBIKmKug?QRffZqP*pqSK^*|4Fxd$j$1P&PSsawtg
zAF)3WhYS#RtY
zwfK^@`X|953&i$3bQxp9Ja@ye!>i*`hP3Q*NbgU78V4QE-{C}MTWK-^;{j%hu;U~g
zM4m00dtOlQO=93j1P3p7h6Ij+RK3msoS7CTe0H;Hs}hGXv_p~{}O3MfgV?APfZhK*9Tn+LkEVZ-JA${uA6L3&(S1j)$w3fTvevsBfg)MFq
zY@i((HI73)m$Q{$A(Y}ao?w$ZG)9HP7|)owBg3Zs4LP0Qkk2Da`IlM~v_xq%`|s}x
zfPh6&Emf=4{&D{FG4eiU7KTjxQYsr$DIcP&H6Z$@d(5)S$%hW=Ub@ay5`hDl4_>;G
zxnAhkonAP(GaD5+G=POijDLR)4$y<#j#t*-8uw`O+OKEt1pS(Q=ux6c4{0+lwb*0%
z)Py_^qUS1Vp$UxRpTYrDQOslHvvim1^o7uqe#p?1D
zPfwif7+5QI#2fmTm@J4jmZk_sAH!yhvw`414%ed3PA^Gi=tkr@tmDhoyjE(>fapTX
zr*kQAK)(zT53M^{Z%x8YE3}Po~q(-0~un*%AD!cj8vuMnx#S
zcojHwfOBh;#`M%fGmPPYC(`wn%MK3eDZD_`o-W5e7}0*5!(n=ZJ?LN$94{iza~x)-
z=Lh3PEp9>^X0!Cq3=U_%JOPGIR|SJY4V{Bl<~xDo`JB=K~Jif
zX5fRV9djJ&p63JsDNEmV1lEEDS9E8!(uoHT2gM|bgqWAgwtcXh9x10q#K#Fh*0>}*UAg=+b(_K#1Y5U(mK6f~)QPAxQhHQlR>;-;xM?*4!r6{>p<5Akcv19Hr#
zAB=?GyVAhZLc#P5_`Ak+2hZ@t%*Syhx?!@S9ni`wv9-{Q9CAfuHI{l%MkDCtvg&T4
z8@@t1U7DHOz~?;}^-gI9)-NmP6YHOpU6THLQN&v@Qr0wvSx4WKVE3$EVG15l6PSW#bZxmUh8JErU
z#FEK{7L5)h-J`P`&1d-3@@_1AT5=aWX;T_R5dERXeZIHhYPu
zx5E+?Yn=;d2fbaB9Z6OEtQECW$&I`R;@U(s)0*t+kw2&P=5YWd
z{;fu$Hl6h9fv-jZ*wRa61BsJkesKDa3J6#ee`cX*oX#y^zDmJB>639epF=+j4*yo-
z07#sjrF5s>P2@d-0nm#aYWmaQ@aI_p5NUyp-ttdSaqh#H(9eRyuSGbV{UsO6?Y!le
zgVMSA#q_h_kalcHFr=dgrYbi^ieBgwQ*BG{{uDTreo+8%_V27+y^0=(A$o}u9Dy3X
z`8|Bg=4;Tef1RbEI6M0*uea9fj{>2;T+Z7!_+-+zqF?{~=NYHp|M82mOl_rf?J;_;
z4`KB;`}_NkAJ6+^Q~$qz{PN2$fBg3m&o>(41u|MG9Qsv3nOey;Wg91)esLaFbsG24BgBn5d3I3Je4R8?30T$
zI+jP`6yR{{Vj}7yl#l5BT;lQ3Qfli@jl%|La5AK4orUx)(gGY7p-Vo?I0zs6{L>SO
zgo9#et}8D6)Hv{8CnRQ;j&A`DhYt@`_Q&2AF9^)zcuyJ-}OZ@xuxB
z22=2p1SScG?RJYx?|6`HjnunI)eD8z>X63QK^YuK{x8)?2@a+H5GA-BCW7?%8o!na
z|D`@WR6R7vpde23@gEAC?
zQrGvNJrLiR9$ydkJwJ9)2;~!T9+!Qc%*Ye*6-~otBf1qQHo9(s6C|Vw9C#Lj&A%5S
zt%nfq@&gBrki)L1_grK$hljaFUI%4xK*7m!TS$r#GH|NTGD$ihuS~xYMLkbwDZ3B%+he?`{z?%R
z!P6)DSSeQt(l}rT&JQ2R5L2TY7mvFK^`6^2J((u~5u;yn56BRSXu0b?hvkl|K|&@w+!ldwQG-7`42
zS430`)JeVc_K?*}jwd7>>OS$xyf&gZxGwojHD$4EK*->*&wSWiqaku(k=)0!^dlhj
z;yGSK4z_m1Nh|oYVE+;whkT(BoY|7e@FjL=&W85@F=rjs6St{w3Sx
zt{&Nb;72kplG#YP`z@=2dtZsj#Trl*;<_jZ9YbMzf8b@QdxHt7G!BBHA;-hXe`O@6
z(n1dnc0Q!>@XB4~3H2tk1xY7LlS`jw5@FRZ)NgnE`7dyOVnRLWi@B53hGoNcs{2A@L6t4u@_c
zP>BMG{y8NM*<tIV
zoHi6*(_>EfBY%<|Enk2GmrSm7>Qsg{*LIm(k*|(YehovG5Gow*?WDjl|67Rzey$>M
zmnevwH*50<1?rPPFwW|r=rFUFQsF@CWmfYGRjcz2ERX#`y%M|w)f!X;@bEzadd0NS7j>R-VP7>{1^$?-pGUa=jbYl8aV^n+5!pP-g#Om`DgU
z4yEL4AO_o#P~!krt~evju(Qm9@OxXnLO9Pbhr;RT7@3od#(5#Jd
zmGBQ8b7CtT+cv2q*;vTeyLcb+E3ehFv0V>wc+^|57BW-@AYEaJ@H71q$Try&vvfL#bE0J^fvi
zwEyr%-;myL_&(?jhwp>laQHsx4TtZ8-f;Ln$T&JW`tsQR=cE4zjEFF|E~D~000000
LNkvXXu0mjfe10PI
literal 0
HcmV?d00001
diff --git a/CenRa_METABASE/tools/icons/auto_add.png b/CenRa_METABASE/tools/icons/auto_add.png
new file mode 100644
index 0000000000000000000000000000000000000000..021f85b54cbedd265057e378133784d5539e23b5
GIT binary patch
literal 42290
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
WH@FoI7yM%hC9yx;JGP>C604
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