From 812412c6f9ce0bbb0686fe995d17b0c2eeb97a9f Mon Sep 17 00:00:00 2001 From: Tom LAVEILLE Date: Fri, 2 Aug 2024 10:20:26 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"CenRa=5FMetabase"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CenRa_Metabase/CenRa_Metabase.py | 199 ++++++++ CenRa_Metabase/__init__.py | 10 + CenRa_Metabase/dock.py | 481 ++++++++++++++++++++ CenRa_Metabase/editor.py | 750 +++++++++++++++++++++++++++++++ CenRa_Metabase/icon.png | Bin 0 -> 15984 bytes 5 files changed, 1440 insertions(+) create mode 100644 CenRa_Metabase/CenRa_Metabase.py create mode 100644 CenRa_Metabase/__init__.py create mode 100644 CenRa_Metabase/dock.py create mode 100644 CenRa_Metabase/editor.py create mode 100644 CenRa_Metabase/icon.png diff --git a/CenRa_Metabase/CenRa_Metabase.py b/CenRa_Metabase/CenRa_Metabase.py new file mode 100644 index 0000000..fa85e93 --- /dev/null +++ b/CenRa_Metabase/CenRa_Metabase.py @@ -0,0 +1,199 @@ +__copyright__ = "Copyright 2021, 3Liz" +__license__ = "GPL version 3" +__email__ = "info@3liz.org" + + +from qgis.core import QgsApplication +from qgis.PyQt.QtCore import QCoreApplication, Qt, QTranslator, QUrl +from qgis.PyQt.QtGui import QDesktopServices, QIcon +from qgis.PyQt.QtWidgets import QAction, QMessageBox +from qgis.utils import iface +''' +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 +''' +from CenRa_Metabase.resources.i18n import setup_translation, tr +import os +from CenRa_Metabase.resources.resources import ( + plugin_path, + resources_path, + maj_verif, +) +from CenRa_Metabase.dock import CenRa_Metabase +from CenRa_Metabase.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) +# 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.RightDockWidgetArea, 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_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(tr('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.dock_action: + iface.pluginMenu().removeAction(self.dock_action) + del self.dock_action + + if self.help_action: + iface.pluginHelpMenu().removeAction(self.help_action) + del self.help_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/__init__.py b/CenRa_Metabase/__init__.py new file mode 100644 index 0000000..8a70ef0 --- /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/dock.py b/CenRa_Metabase/dock.py new file mode 100644 index 0000000..14498d9 --- /dev/null +++ b/CenRa_Metabase/dock.py @@ -0,0 +1,481 @@ +"""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 ( + NULL, + QgsApplication, + QgsDataSourceUri, + QgsProject, + QgsProviderConnectionException, + QgsProviderRegistry, + QgsRasterLayer, + QgsSettings, + QgsVectorLayer, +) +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 ( + QDialog, + QAction, + QDockWidget, + QFileDialog, + QInputDialog, + QMenu, + QToolButton, +) +from qgis.gui import * +from qgis.utils import iface +''' +from pg_metadata.connection_manager import ( + check_pgmetadata_is_installed, + connections_list, + settings_connections_names, +) +''' +from CenRa_Metabase.resources.i18n import tr + +from CenRa_Metabase.resources.resources import ( + load_ui, + resources_path, + login_base, +) + +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.InstantPopup) + self.config.setIcon(QgsApplication.getThemeIcon("/mActionOptions.svg")) + + self.auto_open_dock_action = QAction( + tr('Auto open dock from locator'), + 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.InstantPopup) + self.save_button.setIcon(QIcon(QgsApplication.iconPath('mActionFileSave.svg'))) + + self.save_as_pdf = QAction( + tr('Enregistrer en PDF') + '…', + iface.mainWindow()) + self.save_as_pdf.triggered.connect(partial(self.export_dock_content, OutputFormats.PDF)) + + self.save_as_html = QAction( + tr('Enregistrer en HTML') + '…', + iface.mainWindow()) + self.save_as_html.triggered.connect(partial(self.export_dock_content, OutputFormats.HTML)) + self.save_as_dcat = QAction( + tr('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) + + 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( + self.settings.value("UI/lastFileNameWidgetDir"), + '{name}.{ext}'.format(name=layer_name, ext=output_format.ext) + ) + output_file = QFileDialog.getSaveFileName( + self, + tr("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.PdfFormat) + printer.setPageMargins(20, 20, 20, 20, QPrinter.Millimeter) + printer.setOutputFileName(output_file_path) + self.viewer.print(printer) + iface.messageBar().pushSuccess( + tr("Export PDF"), + tr( + "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( + tr("Export") + ' ' + output_format.label, + tr( + "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. """ + #link_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, base_url) + + def ce_trouve_dans_psql(self,layer): + try: + uri = layer.dataProvider().uri() + except: + self.default_html_content_not_pg_layer() + self.save_button.setEnabled(False) + uri='' + if uri != '': + if not uri.table(): + 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 += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=(", ".join(str(x) for x in data[6]))) + body += ''.format(data=(", ".join(str(x) for x in data[24]))) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += '
Titre{data[4]}
Description{data[5]}
Categories{data}
Thèmes{data}
Mots-clés{data[7]}
Dernier mise à jour{data[23]}
Langue{data[26]}
' + + body += '

Properties spatial

' + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += '
Niveau{data[8]}
Echelle minimum{data[9]}
Echelle maximum{data[10]}
Nombre d\'entités {data[15]}
Type de géométrie{data[16]}
Nom de projection{data[17]}
ID de projection{data[18]}
Emprise{data[28]}
' + + #body += '
' + + body += '

Publication

' + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += '
Date{data[11]}
Fréquence de mise à jour{data[12]}
Licence{data[13]}
Licence attribué{data[25]}
Restriction{data[14]}
' + + body += '

Lien

' + body += '' + for value_url in data_url: + body += ''.format(value_url=value_url) + body += '
TypeURLType MIMEFormatTaille
{value_url[0]}{value_url[1]}{value_url[2]}{value_url[3]}{value_url[4]}
' + ''' + body += '

Liste des champs

' + for collonne in data_collonne: + body += ''.format(collonne=collonne,defini='') + body += '
{collonne}{defini}
' + ''' + body += '

Contacts

' + body += '' + for value_contact in data_contact: + body += ''.format(value_contact=value_contact) + body += '
RôleNomOrganisationEmailTélèphone
{value_contact[0]}{value_contact[1]}{value_contact[2]}{value_contact[3]}{value_contact[4]}
' + + body += '

Metadata

' + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += ''.format(data=data) + body += '
Table{data[2]}
Schema{data[3]}
Date de création{data[20]}
Date de modification{data[21]}
Encodage{data[27]}
UUID{data[1]}
' + + self.set_html_content( + layer.name(), body) + + def default_html_content_not_pg_layer(self): + """ When it's not a PostgreSQL layer. """ + self.set_html_content( + 'CenRa Metadata', tr('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', tr('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..5a7cced --- /dev/null +++ b/CenRa_Metabase/editor.py @@ -0,0 +1,750 @@ +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 ( + NULL, + QgsApplication, + QgsDataSourceUri, + QgsProject, + QgsProviderConnectionException, + QgsProviderRegistry, + QgsRasterLayer, + QgsSettings, + QgsVectorLayer, + QgsGeometry, +) +from qgis.PyQt.QtCore import QLocale, QUrl, QDateTime +from qgis.PyQt.QtGui import QDesktopServices, QIcon +from qgis.PyQt.QtPrintSupport import QPrinter +from qgis.PyQt.QtWebKitWidgets import QWebPage +from qgis.PyQt.QtWidgets import ( + QDialog, + QAction, + QDockWidget, + QFileDialog, + QInputDialog, + QMenu, + QToolButton, + QTableWidget, + QTableWidgetItem, +) +from qgis.utils import iface +from CenRa_Metabase.resources.i18n import tr + +from CenRa_Metabase.resources.resources import ( + load_ui, + resources_path, + login_base, + send_issues, +) +from CenRa_Metabase.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.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.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 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): + 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: + 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 = str(layer.wkbType())[8:] + 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: + 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: + 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: + boolean = False + + if boolean == 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): + + global uid_delete_list_link,array_link + lien_uid = self.table_lien.item(self.table_lien.currentRow(),0).text() + 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) + else: + uid_delete_list_link += "'"+lien_uid+"'," + + def delete_contact(self): + global uid_delete_list_contact,array_contact + contact_uid = self.table_contact.item(self.table_contact.currentRow(),0).text() + 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) + 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..96a568ef98b17d32e38baa714405144eebef4c59 GIT binary patch literal 15984 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