Modèles de formulaire#

Les modèles de formulaire sont définis par l’administrateur de données pour tous les utilisateurs de son service. Etant partagés, ils sont stockés sur le serveur PostgreSQL, dans un ensemble de tables créées par l’extension PostgreSQL PlumePg.

Leur usage est totalement optionnel.

PrincipeGestion dans PostgreSQLImport des modèles par PlumeAvec les modèles stockés en localGestion des modèles via Plume

Principe#

Par défaut, les formulaires de Plume présentent toutes les catégories de métadonnées définies par le schéma des métadonnées communes, shape.ttl[1]. Souvent, c’est trop. Selon l’organisation du service, selon la table considérée, selon le profil de l’utilisateur, les catégories de métadonnées réellement pertinentes ne seront pas les mêmes, et il est peu probable que toutes les catégories communes le soient.

Parfois, c’est au contraire insuffisant. Toujours selon l’organisation du service, la table considérée et le profil de l’utilisateur, il peut être très utile voire indispensable de disposer d’informations qui ne sont pas prévues dans les catégories communes.

Plume y remédie, via l’extension PostgreSQL PlumePg, en permettant à l’administrateur de définir des modèles de formulaire adaptés à son service, que le plugin QGIS saura exploiter.

Concrètement, un modèle de formulaire déclare un ensemble de catégories de métadonnées à présenter à l’utilisateur avec leurs modalités d’affichage. Il restreint ainsi les catégories communes par un système de liste blanche, tout en autorisant l’ajout de catégories locales supplémentaires.

L’admistrateur de données peut prévoir autant de modèles qu’il le souhaite et, selon les besoins, il peut définir des conditions dans lesquelles un modèle sera appliqué par défaut à une fiche de métadonnées (si cela se justifie). Dans l’interface QGIS de Plume, l’utilisateur peut basculer à sa convenance d’un modèle à l’autre.

Gestion dans PostgreSQL#

L’extension PostgreSQL PlumePg crée une structure de données adaptée au stockage des modèles. Pour bénéficier des modèles, elle doit être installée sur toutes les bases du serveur PostgreSQL dont on souhaite gérer les métadonnées avec Plume[2].

Cf. Installation et gestion de l’extension PostgreSQL PlumePg pour plus de détails sur l’installation et la maintenance de cette extension.

PlumePg crée dans le schéma z_plume un ensemble de tables et vues permettant de définir les modèles de formulaires :

  • meta_template liste les modèles.

  • meta_categorie liste les catégories de métadonnées disponibles, qu’il s’agisse des catégories du schéma commun ou de catégories locales, et paramètre leur affichage dans les formulaires.

  • meta_tab liste des noms d’onglets de formulaires dans lesquels pourront être classées les catégories.

  • meta_template_categories permet de déclarer les catégories utilisées par chaque modèle, de les ranger dans des onglets, et de définir si besoin des paramètres d’affichage spécifiques à un modèle pour certaines catégories, qui remplaceront ceux de meta_categorie.

Création d’un modèle de formulaire#

La table z_plume.meta_template comporte une ligne par modèle. Un modèle doit obligatoirement avoir un nom, différent pour chaque modèle et renseigné dans le champ tpl_label. Ce nom devra être aussi parlant que possible pour les utilisateurs, qui n’auront accès qu’à cette information au moment de sélectionner un modèle. Sa longueur est limitée à 48 caractères.

Les champs sql_filter et md_conditions servent à définir des conditions selon lesquelles le modèle sera appliqué automatiquement à la fiche de métadonnées considérée. Les remplir est bien évidemment optionnel.

  • sql_filter est un filtre SQL, qui peut se référer au nom du schéma avec $1 et/ou de la table / vue avec $2. Il est évalué côté serveur au moment de l’import des modèles par le plugin, par la fonction z_plume.meta_execute_sql_filter(text, text, text).

Par exemple, le filtre suivant appliquera le modèle aux tables des schémas des blocs « données référentielles » (préfixe 'r_') et « données externes » (préfixe 'e_') de la nomenclature nationale :

'$1 ~ ANY(ARRAY[''^r_'', ''^e_'']'

D’une manière générale, toute commande renvoyant un booléen peut être utilisée. Ainsi, le filtre suivant appliquera le modèle pour toutes les fiches de métadonnées dès lors que l””utilisateur est membre du rôle g_admin :

'pg_has_role(''g_admin'', ''USAGE'')'
  • md_conditions est une liste JSON définissant des ensembles de conditions portant cette fois sur les métadonnées.

[
    {
        "plume:isExternal": true,
        "dcat:keyword": "IGN"
    },
    {
        "dct:publisher / foaf:name": "Institut national de l''information géographique et forestière (IGN-F)"
    }
]

Les ensembles sont combinés entre eux avec l’opérateur OR. Au sein d’un ensemble, les conditions sont combinées avec l’opérateur AND.

Les clés des conditions sont les chemins des catégories de métadonnées (champ path de la vue meta_categorie évoquée ci-après) et les valeurs des valeurs qui doivent apparaître dans les métadonnées pour les catégories considérées.

Dans l’exemple ci-avant, une table validera les conditions du modèle si :

  • il s’agit d’une donnée externe (valeur True pour la catégorie plume:isExternal) ET l’un de ses mots-clés (dcat:keyword) est 'IGN' ;

  • OU le nom du diffuseur (dct:publisher / foaf:name) est 'Institut national de l'information géographique et forestière (IGN-F)'.

La comparaison des valeurs ne tient pas compte de la casse.

Il faudra soit que toutes les conditions de l’un des ensembles du JSON soient vérifiées, soit que le filtre SQL ait renvoyé True pour que le modèle soit considéré comme applicable. Si un jeu de données remplit les conditions de plusieurs modèles, c’est celui dont le niveau de priorité, (champ priority) est le plus élevé qui sera retenu.

À noter que les conditions ne valent qu’à l’ouverture de la fiche. L’utilisateur du plugin pourra a posteriori choisir librement un autre modèle dans la liste, y compris un modèle sans conditions définies ou dont les conditions d’application automatique ne sont pas vérifiées. Il aura aussi la possibilité de n’appliquer aucun modèle, auquel cas le schéma des métadonnées communes s’appliquera tel quel.

Le champ enabled de meta_template permet de désactiver un modèle, qui ne sera alors plus proposé aux utilisateurs du plugin QGIS, en passant simplement la valeur du champ de True (valeur par défaut) à False. Ce mécanisme peut notamment être utilisé pour les modèles en cours de construction, dont l’administrateur pourra souhaiter qu’ils n’apparaissent pas dans la liste des modèles disponibles tant qu’ils ne sont pas prêts à l’usage.

On pourra ainsi initier la création d’un nouveau modèle avec une requête de ce type :

INSERT INTO z_plume.meta_template (tpl_label, enabled)
    VALUES ('Mon nouveau modèle', False) ;

Puis, une fois finalisée l’association des catégories au modèle (cf. ci-après), on activera le modèle :

UPDATE z_plume.meta_template
    SET enabled = True
    WHERE tpl_label = 'Mon nouveau modèle' ;

Onglets des formulaires#

Sans que ce soit obligatoire en aucune façon, les modèles de formulaires peuvent répartir les catégories de métadonnées par onglets.

Avant d’y affecter des catégories, les onglets doivent être définis dans la table z_plume.meta_tab. Celle-ci contient trois champs :

  • tab_id est une clé primaire entière générée automatiquement.

  • tab_label fournit le nom de l’onglet. Il est limité à 48 caractères et doit obligatoirement être renseigné.

  • tab_num sert à ordonner les onglets. Les onglets sont affichés du plus petit numéro au plus grand (NULL à la fin), puis par ordre alphabétique en cas d””égalité. Les numéros n””ont pas à se suivre et peuvent être répétés. NB. Tous les onglets de meta_tab ne seront évidemment pas présents dans tous les modèles, mais ceux qui le sont seront donc toujours présentés dans le même ordre quel que soit le modèle.

Catégories de métadonnées#

La vue éditable z_plume.meta_categorie répertorie toutes les catégories de métadonnées disponibles, à la fois celle qui sont décrites par le schéma SHACL des catégories communes (fichier shape.ttl) et les catégories supplémentaires locales définies par l’ADL pour le seul usage de son service.

Cette vue correspond en fait à la concaténation de deux tables sources :

  • z_plume.meta_shared_categorie pour les catégories communes (origin vaut shared) ;

  • z_plume.meta_local_categorie pour les catégories supplémentaires locales (origin vaut local).

L’utilisateur peut écrire soit dans les tables meta_shared_categorie et meta_local_categorie, soit dans la vue meta_categorie sans se préoccuper de là où sont effectivement stockées les données.

Concrètement, la vue meta_categorie a deux fonctions :

  • elle permet de créer les catégories supplémentaires locales, en ajoutant une ligne à la table par nouvelle catégorie. Il est a minima nécessaire de renseigner un libellé, histoire de savoir de quoi il est question ;

  • elle permet de modifier la manière dont les catégories communes sont présentées par le plugin QGIS (nouveau label, nouveau texte d’aide, etc.), en jouant sur les nombreux champs de paramétrage.

Les champs sur lesquels l’ADL peut intervenir sont :

Nom du champ

Description

Remarques

label

Libellé de la catégorie.

description

Description de la catégorie. Elle sera affichée sous la forme d’un texte d’aide dans le formulaire.

datatype

Type de valeur attendu pour la catégorie, parmi (type énuméré z_plume.meta_data_type) : 'xsd:string', 'xsd:integer', 'xsd:decimal', 'xsd:boolean', 'xsd:date', 'xsd:time', 'xsd:dateTime', 'xsd:duration', 'rdf:langString' (chaîne de caractères avec une langue associée[3]) et 'gsp:wktLiteral' (géométrie au format textuel WKT). Cette information détermine notamment la nature des widgets utilisés par Plume pour afficher et éditer les valeurs, ainsi que les validateurs appliqués.

Pour les catégories communes, les modifications apportées sur ce champ ne seront pas prises en compte sauf s’il s’agit d’utiliser des dates avec heures ('xsd:dateTime') à la place des dates simples ('xsd:date') ou réciproquement. Si, pour une catégorie locale, aucune valeur n’est renseignée pour ce champ (ni dans meta_categorie ni dans meta_template_categories), le plugin considérera que la catégorie prend des valeurs de type 'xsd:string'.

is_long_text

True pour une catégorie appelant un texte de plusieurs lignes.

Cette information ne sera prise en compte que si le type de valeur (datatype) est 'xsd:string' ou 'rdf:langString'. Pour le type 'gsp:wktLiteral', elle vaut implicitement toujours True. Pour les autres types, notamment 'xsd:string' et 'rdf:langString', elle vaut implicitement toujours False (si tant est qu’elle ait encore un objet).

rowspan

Nombre de lignes occupées par le widget de saisie, s’il y a lieu de modifier le comportement par défaut de Plume.

La valeur ne sera considérée que si is_long_text vaut True.

special

Le cas échéant, mise en forme spécifique à appliquer au champ. Valeurs autorisées (type énuméré z_plume.meta_datatype) : 'url', 'email', et 'phone'.

Pour les catégories communes, les modifications apportées à ce champ ne seront pas prises en compte.

placeholder

Valeur fictive pré-affichée en tant qu’exemple dans le widget de saisie, s’il y a lieu.

input_mask

Masque de saisie, s’il y a lieu. La syntaxe est décrite dans la documentation de l’API Qt for python.

La valeur sera ignorée si le widget utilisé pour la catégorie ne prend pas en charge ce mécanisme.

is_multiple

True si la catégorie admet plusieurs valeurs.

Pour les catégories communes, les modifications apportées sur ce champ ne seront pas prises en compte.

unilang

True si la catégorie n’admet plusieurs valeurs que si elles sont dans des langues différentes (par exemple un jeu de données n’a en principe qu’un seul titre, mais il peut être traduit).

Pour les catégories communes, les modifications apportées sur ce champ ne seront pas prises en compte. is_multiple est ignoré quand unilang vaut True. Cette information n’est considérée que si datatype vaut 'rdf:langString'.

is_mandatory

True si une valeur doit obligatoirement être saisie pour cette catégorie.

Modifier cette valeur permet de rendre obligatoire une catégorie commune optionnelle, mais pas l””inverse.

sources

Pour une catégorie prenant ses valeurs dans un ou plusieurs thésaurus, liste des sources admises.

Cette information n’est considérée que pour les catégories communes. Il n’est pas possible d’ajouter des sources ni de les retirer toutes - Plume reviendrait alors à la liste initiale -, mais ce champ permet de restreindre la liste à un ou plusieurs thésaurus jugés les mieux adaptés. Il importe de noter que certains vocabulaires comptent plusieurs dizaines de milliers de termes. S’ils ne paraissent pas indispensables, il est préférable de ne pas les activer dans les modèles pour raccourcir les temps de chargement (principalement le délai d’affichage de la première fiche de métadonnées ouverte en édition).

geo_tools

Pour une catégorie prenant pour valeurs des géométries, liste des fonctionnalités d’aide à la saisie à proposer, parmi 'show' (visualisation de la géométrie saisie), 'point' (tracé manuel d’une géométrie ponctuelle), 'linestring' (tracé manuel d’une géométrie linéaire), 'rectangle' (tracé manuel d’un rectangle), 'polygon' (tracé manuel d’un polygone), 'circle' (tracé manuel d’un cercle), 'bbox' (calcul du rectangle d’emprise de la couche courante), 'centroid' (calcul du centre du rectangle d’emprise de la couche courante).

Cette information ne sera considérée que si le type (datatype) est 'gsp:wktLiteral'. Pour retirer toutes les fonctionnalités proposées par défaut pour une catégorie commune, on saisira une liste vide, soit ARRAY[]::z_plume.meta_geo_tool[].

compute

Liste des fonctionnalités de calcul à proposer, parmis, 'auto' (déclenchement automatique lorsque la fiche de métadonnées est générée), 'manual' (déclenchement à la demande, lorsque l’utilisateur clique sur le bouton qui apparaîtra alors à côté du champ de saisie dans le formulaire).

Cette information ne sera considérée que si une méthode de calcul est effectivement disponible pour la catégorie. Pour retirer toutes les fonctionnalités proposées par défaut pour une catégorie commune, on saisira une liste vide, soit ARRAY[]::z_plume.meta_compute[].

compute_params

Paramètres optionnels attendus par la méthode de calcul, si opportun. À spécifier sous la forme d’un dictionnaire JSON dont les clés correspondent aux noms des paramètres et les valeurs sont les valeurs des paramètres. Cf. Métadonnées calculées pour plus de détails.

Cette information ne sera considérée que si une méthode de calcul est effectivement disponible pour la catégorie et qu””elle admet un ou plusieurs paramètres.

template_order

Ordre d’apparence de la catégorie dans le formulaire. Les plus petits numéros sont affichés en premier, il n’est pas nécessaire que les numéros se suivent. Dans le cas des catégories communes, qui ont une structure arborescente, il s’agit de l’ordre parmi les catégories de même niveau dans la branche.

Les champs path (chemin SPARQL identifiant la catégorie), origin et is_node sont calculés automatiquement. Il est fortement recommandé de ne pas les modifier à la main.

Les caractéristiques spécifiées dans la vue meta_categorie seront utilisées pour tous les modèles, sauf – cette possibilité sera détaillée par la suite – à avoir prévu des valeurs spécifiques au modèle via la table meta_template_categories. Pour les catégories partagées, elles se substitueront au paramétrage par défaut défini par le schéma des catégories communes, qui est repris dans meta_categorie lors de l’initialisation de l’extension.

Association de catégories à un modèle#

La définition des catégories associées à chaque modèle (relation n-n) se fait par l’intermédiaire de la table de correspondance meta_template_categories.

Le modèle est identifié par son identifiant (champ tpl_id), la catégorie par son chemin (champ path de meta_categorie, repris dans loccat_path pour une catégorie locale et dans shrcat_path pour une catégorie commune).

Hormis ces champs de clés étrangères et la clé primaire séquentielle tplcat_id, tous les champs de la table meta_template_categories servent au paramétrage du modèle. Les valeurs qui y sont saisies remplaceront (pour le modèle considéré) celles qui avaient éventuellement été renseignées dans meta_categorie et le paramétrage du schéma des catégories communes.

Soit un modèle M, une catégorie de métadonnées C et une propriété P.

  • Si une valeur est renseignée pour la propriété P, la catégorie C et le modèle M dans meta_template_categories, alors elle s’applique au modèle M pour la catégorie C et la propriété P.

  • Sinon, si une valeur est renseignée pour la propriété P et la catégorie C dans meta_categorie, alors elle s’applique au modèle M pour la catégorie C et la propriété P.

  • Sinon, pour une catégorie commune, la valeur éventuellement prévue par le schéma des catégories communes s’appliquera au modèle M pour la catégorie C et la propriété P. Pour les catégories locales, il sera considéré par défaut que la catégorie doit être affichée sous la forme d’une zone de texte sur une seule ligne et qu’elle n’admet qu’une seule valeur.

Aux champs de paramétrage qui apparaissaient déjà dans meta_categorie, la table meta_template_categories ajoute deux champs optionnels :

  • un champ booléen read_only qui pourra valoir True si la catégorie doit être en lecture seule pour le modèle considéré. Il peut notamment être mis à profit lorsque des fiches de métadonnées sont co-rédigées par un service métier référent et l’administrateur de données, pour permettre à chacun de voir les informations saisies par l’autre, sans qu’il risque de les modifier involontairement (sauf à ce qu’il change de modèle, bien sûr, ce n’est pas un dispositif de verrouillage, seulement de l’aide à la saisie) ;

  • un champ tab_id qui permet de spécifier l’onglet (préalablement déclaré dans meta_tab) dans lequel devra être placée la catégorie. Cette information n’est considérée que pour les catégories locales et les catégories communes de premier niveau (par exemple la catégorie de deuxième niveau 'dcat:distribution / dct:issued' ira nécessairement dans le même onglet que 'dcat:distribution'). Pour celles-ci, si aucun onglet n’est fourni, la catégorie ira toujours dans le premier onglet cité pour le modèle ou, si le modèle n’utilise explicitement aucun onglet, dans un onglet « Général ».

NB. Pour les catégories de métadonnées communes imbriquées, il n’est pas nécessaire que le modèle fasse systématiquement apparaître tous les chemins intermédiaires (par exemple 'dcat:distribution' et 'dcat:distribution / dct:license' pour 'dcat:distribution / dct:license / rdfs:label'). Le plugin saura ajouter lui-même les chemins intermédiaires manquants, de même qu’il enlèvera les chemins intermédiaires sans catégorie finale. Ainsi, l’administrateur pourra se contenter d’associer 'dcat:distribution / dct:license / rdfs:label' à son modèle, sauf à avoir envie de renommer les catégories 'dcat:distribution' et/ou 'dcat:distribution / dct:license', de leur ajouter un texte d’aide, etc.

Modèles pré-configurés#

L’extension propose des modèles pré-configurés - quatre à ce stade - qui peuvent être importés via la commande suivante :

SELECT * FROM z_plume.meta_import_sample_template(%tpl_label) ;

%tpl_label est à remplacer par une chaîne de caractères correspondant au nom du modèle à importer. Il est aussi possible de ne donner aucun argument à la fonction, dans ce cas tous les modèles pré-configurés sont importés.

SELECT * FROM z_plume.meta_import_sample_template() ;

La fonction renvoie une table dont la première colonne contient les noms des modèles traités et la seconde indique si le modèle a été créé ('created') ou, dans le cas d’un modèle déjà répertorié, mis à jour / réinitialisé ('updated').

Modèles pré-configurés disponibles :

Nom du modèle (tpl_label)

Description

'Basique'

Modèle limité à quelques catégories de métadonnées essentielles.

'Donnée externe'

Modèle assez complet adapté à des données externes.

'Classique'

Modèle comportant les principales catégories de métadonnées intéressantes pour des données produites par le service.

'INSPIRE'

Ce modèle inclut toutes les catégories de métadonnées utilisées pour retranscrire les fiches de métadonnées fournies par les catalogues INSPIRE. À noter que certaines de ces catégories ne correspondent pas à des informations obligatoires du point de vue d’INSPIRE, mais à des éléments de métadonnées prévus par le standard ISO 19139 et souvent disponibles sur les catalogues, tels que la fréquence d’actualisation des données (dct:accrualPeriodicity) ou l’état du jeu de données (adms:status). Il est normal d’observer un délai de quelques secondes lors de la première utilisation de ce modèle, car il fait appel à des vocabulaires comprenant plusieurs dizaines de milliers de termes.

Import des modèles par Plume#

La gestion des modèles par le plugin fait intervenir :

  • le module plume.pg.queries pour les requêtes SQL pré-écrites à exécuter sur les curseurs de Psycopg ;

  • le module plume.pg.template pour le traitement du résultat de ces requêtes.

Aucune des fonctions de ces deux modules n’envoie à proprement parler de requête au serveur PostgreSQL.

Concrètement, l’import du modèle de formulaire se fait en six temps :

  1. vérification de la présence de l’extension PlumePg.

  2. récupération sur le serveur PostgreSQL de la liste des modèles disponibles et de leurs conditions d’application → templates.

  3. sélection du modèle à utiliser parmi la liste → tpl_label.

  4. récupération sur le serveur PostgreSQL des catégories associées au modèle sélectionné avec leurs paramètres d’affichage → categories.

  5. récupération sur le serveur PostgreSQL de la liste des onglets du modèle → tabs.

  6. mise au propre du modèle sous la forme d’un dictionnaire → template.

À l’ouverture de la fiche de métadonnées, le choix du modèle (étape 3) est automatique. Les étapes 4 à 6 devront être relancées à chaque fois que l’utilisateur change manuellement le modèle courant.

Soit :

  • connection_string la chaîne de connexion à la base de données PostgreSQL ;

  • table_name le nom de la table ou vue dont l’utilisateur cherche à consulter / éditer les métadonnées ;

  • schema_name le nom de son schéma.

Présence de l’extension PlumePg#

La première étape consiste à déterminer :

  • si l’extension PlumePg est installée sur la base d’où provient la table considérée ;

  • si la version installée est compatible avec la version du plugin QGIS de l’utilisateur ;

  • si l’utilisateur dispose bien des privilèges requis sur les objets de l’extension.

Pour le vérifier, une seule requête :


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        query = queries.query_plume_pg_check()
        cur.execute(*query)
        result = cur.fetchone()

conn.close()

Si le premier élément du tuple result vaut True, toutes les conditions sont réunies pour utiliser les modèles définis par PlumePg. On pourra donc poursuivre avec les opérations décrites dans les parties suivantes.


if result[0]:
    ...

Dans le cas contraire, on se reportera à la partie Avec les modèles stockés en local.

Les autres éléments de result permettent de décrire plus précisément le problème :

  • result[1] est une liste de longueur 2 qui rappelle les versions de PlumePg compatibles avec la version du plugin QGIS dont dispose l’utilisateur. Concrètement, toutes les versions supérieures ou égales au premier élément de la liste et strictement inférieures au second sont considérées comme compatibles.

  • result[2] fournit la version de PlumePg installée sur la base cible. Si cet élément vaut None, l’extension n’est pas installée sur la base.

  • result[3] fournit la version de référence de PlumePg disponible sur le serveur. Si cet élément vaut None, l’extension n’est pas disponible pour l’installation.

  • result[4] est une liste de schémas sur lesquels l’utilisateur ne dispose par du privilège USAGE requis. La liste est vide si l’extension n’est pas installée dans une version compatible ou si les droits sur les schémas sont suffisants.

  • result[5] est une liste de tables et vues sur lesquelles l’utilisateur ne dispose par du privilège SELECT requis. La liste est vide si l’extension n’est pas installée dans une version compatible, si l’utilisateur ne dispose pas des droits requis sur les schémas, ou si les droits sur les tables sont suffisants.

L’infobulle du bouton de sélection du modèle pourrait expliquer pourquoi les modèles n’ont pas pu être chargés depuis la base. Le message serait alors construit par une suite de conditions similaire à celle-ci :


if not result[3]:
    pb = "L'extension PlumePg n'est pas disponible sur le serveur cible."
elif not result[2]:
    pb = "L'extension PlumePg n'est pas installée sur la base cible."
elif result[5]:
    pb = 'Votre rôle de connexion ne dispose pas du privilège ' \
        'SELECT sur {} {}.'.format(
        'la table' if len(result[5]) == 1 else 'les tables',
        ', '.join(result[5]))
elif result[4]:
    pb = 'Votre rôle de connexion ne dispose pas du privilège ' \
        'USAGE sur {} {}.'.format(
        'le schéma' if len(result[4]) == 1 else 'les schémas',
        ', '.join(result[4]))
else:
    pb = 'Votre version de Plume est incompatible avec PlumePg < {} ou ≥ {}' \
        ' (version base cible : PlumePg {}).'.format(result[1][0],
        result[1][1], result[2])

Récupération de la liste des modèles#

Il s’agit simplement d’aller chercher sur le serveur le contenu de la table meta_template, à la petite nuance près que query_list_templates() exécute ce faisant les filtres SQL du champ sql_filter (côté serveur, grâce à la fonction z_plume.meta_execute_sql_filter(text, text, text) de l’extension PlumePg) et c’est le booléen résultant qui est importé plutôt que le filtre lui-même.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(*queries.query_list_templates(schema_name, table_name))
        templates = cur.fetchall()

conn.close()

Puisque l’utilisateur devra avoir la possibilité de choisir ultérieurement un nouveau modèle (ou aucun modèle), la liste doit être gardée en mémoire. Les conditions ne servant plus à rien, on pourra se contenter des noms :


templateLabels = [t[0] for t in templates]

Si cette liste est vide, on utilisera les versions locales des modèles pré-configurés - cf. Avec les modèles stockés en local. Ce sera le cas si l’administrateur a activé PlumePg sur la base, mais n’a encore ni chargé de modèles pré-configurés, ni créé ses propres modèles.

Sélection automatique du modèle#

Cette étape détermine le modèle qui sera utilisé à l’ouverture initiale du formulaire. Elle mobilise la fonction plume.pg.template.search_template, qui parcourt la liste des modèles (avec le résultat du filtre SQL / champ sql_filter de la table meta_template) en déterminant si les conditions d’usage portant sur les métadonnées (champ md_conditions) sont vérifiées. Elle renvoie le nom du modèle de plus haut niveau de priorité (champ priority) dont le filtre SQL ou les conditions sur les métadonnées sont satisfaites.


from plume.pg.template import search_template

tpl_label = search_template(templates, metagraph)

metagraph est le graphe contenant les métadonnées de la table ou vue considérée. Cf. Génération du dictionnaire des widgets.

Il est tout à possible que la fonction search_template ne renvoie rien, d’autant que tous les services ne souhaiteront pas nécessairement utiliser ce mécanisme d’application automatique des modèles. Dans ce cas, on utilisera le « modèle préféré » (preferedTemplate) désigné dans les paramètres de configuration de l’utilisateur – sous réserve qu’il soit défini et fasse bien partie de templateLabels – ou, à défaut, aucun modèle (template vaut None).

À noter que l’utilisateur peut décider que son modèle préféré prévaut sur toute sélection automatique, en mettant à True le paramètre utilisateur enforcePreferedTemplate. Dans ce cas, il n’est même pas utile de lancer search_template, on a directement :


if preferedTemplate and enforcePreferedTemplate \
    and templateLabels and preferedTemplate in templateLabels:
    tpl_label = preferedTemplate

Récupération des catégories associées au modèle retenu#

Une fois le nom du modèle à appliquer connu (s’il y en a un), on peut aller chercher dans la table meta_template_categories les catégories associées au modèle.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(*queries.query_get_categories(tpl_label))
        categories = cur.fetchall()

conn.close()

Récupération des onglets associés au modèle retenu#

La liste des onglets est obtenue en exécutant la requête renvoyée par plume.pg.queries.query_template_tabs, avec le nom du modèle en paramètre.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(*queries.query_template_tabs(tpl_label))
        tabs = cur.fetchall()

conn.close()

Si tabs est une liste vide, toutes les catégories seront affectées à un unique onglet nommé « Général ».

Génération de template#

À ce stade, categories est une liste de tuples, qui doit être consolidée avant de pouvoir être utilisée pour générer le dictionnaire de widgets.

Concrètement, il s’agit de créer un objet plume.pg.template.TemplateDict à partir de categories et tabs :


template = TemplateDict(categories, tabs)

Le modèle de formulaire ainsi obtenu peut être passé dans l’argument template du constructeur de plume.rdf.widgetsdict.WigdetsDict. Cf. Génération du dictionnaire des widgets.

Avec les modèles stockés en local#

Cette partie décrit la méthode alternative de gestion des modèles mise en oeuvre par Plume dans le cas où l’extension PostgreSQL PlumePg n’est pas active sur la base cible (cf. Présence de l’extension PlumePg pour le test). Le processus est similaire à celui décrit dans la partie Import des modèles par Plume, si ce n’est que certaines étapes ne sont plus nécessaires.

Faute de pouvoir importer les modèles personnalisés par l’administrateur de données depuis le serveur PostgreSQL, Plume utilise des copies locales des modèles pré-configurés de PlumePg. Ceux-ci sont stockés dans le fichier templates.json, dont on chargera le contenu en créant un objet de classe plume.pg.template.LocalTemplatesCollection. Celui-ci n’étant jamais modifié, on pourra le générer lorsque Plume rencontre pour la première fois une base sans PlumePg et le référencer de manière à pouvoir le réutiliser à chaque nouvelle occurrence par la suite.


from plume.pg.template import LocalTemplatesCollection

templates_collection = LocalTemplatesCollection()

Pour connaître l’éventuel modèle à appliquer automatiquement à une table donnée, on procédera comme décrit dans la partie Sélection automatique du modèle. La seule différence est que la variable templates ne résulte pas d’une requête générée par plume.pg.queries.query_list_templates mais par plume.pg.queries.query_evaluate_local_templates.


import psycopg2
from plume.pg import queries
from plume.pg.template import search_template

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        query = queries.query_evaluate_local_templates(templates_collection, schema_name, table_name)
        cur.execute(*query)
        templates = cur.fetchall()

conn.close()

tpl_label = search_template(templates, metagraph)

table_name est le nom de la table ou vue dont on affiche les métadonnées, schema_name le nom de son schéma, et metagraph le graphe contenant ses métadonnées.

Puisque les modèles sont stockés localement, il n’est pas nécessaire d’envoyer des requêtes pour connaître les catégories de métadonnées et onglets associés (autrement dit, les parties Récupération des catégories associées au modèle retenu et Récupération des onglets associés au modèle retenu n’ont pas d’équivalent ici). On obtiendra directement l’objet plume.pg.template.TemplateDict à fournir en argument au constructeur de plume.rdf.widgetsdict.WidgetsDict en interrogeant le répertoire des modèles locaux.


template = templates_collection[tpl_label] if tpl_label else None

Gestion des modèles via Plume#

Voir aussi Gestion de PlumePg via Plume pour l’administration générale de l’extension PlumePg avec l’interface de Plume.

Plume propose aux administrateurs des serveurs PostgreSQL une interface simplificatrice pour la conception de leurs modèles, accessible via le menu Configuration de la barre d’outils configuration.svg.

Conditions d’apparition#

Les fonctionnalités suivantes ne doivent être montrées à l’utilisateur de Plume que si :

  • L’extension PostgreSQL PlumePg est active sur la base courante, dans une version compatible avec celle de Plume - cf. Présence de l’extension PlumePg.

  • L’utilisateur est effectivement habilité à gérer les modèles. Cette condition est contrôlée par la requête générée par plume.pg.queries.query_is_template_admin.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_is_template_admin()
            )
        res = cur.fetchone()
        is_template_admin = res[0]

conn.close()

connection_string est la chaîne de connexion à la base de données PostgreSQL.

Le résultat is_template_admin est un booléen, qui vaudra True si et seulement si l’utilisateur dispose des privilèges nécessaires pour modifier les modèles.

NB : Les privilèges contrôlés par la requête ne sont pas tout à fait suffisants pour pouvoir éditer les modèles - elle vérifie les droits sur le schéma et les tables, il faut aussi des droits sur les séquences, les types, etc. Néanmoins, disposer de ces privilèges signifer qu’on a voulu habiliter l’utilisateur à modifier les modèles. S’il lui manque des droits annexes, Plume lui fera savoir par un message d’erreur qui permettra à l’administrateur du serveur d’accorder les privilèges manquants.

Manipulation des données des modèles#

Les données des modèles sont stockées en base dans une structure de données mise en place par PlumePg. Elles peuvent être consultées et éditées via des requêtes générées par des fonctions du module plume.pg_queries.

  • Objet « modèle » (table z_plume.meta_template) :

    Action

    Fonction pour générer la requête

    Lecture

    query_read_meta_template

    Insertion & Mise à jour

    query_insert_or_update_meta_template

    Suppression

    query_delete_meta_template

  • Objet « catégorie de métadonnée » (vue éditable z_plume.meta_categorie) :

    Action

    Fonction pour générer la requête

    Lecture

    query_read_meta_categorie

    Insertion & Mise à jour

    query_insert_or_update_meta_categorie

    Suppression

    query_delete_meta_categorie

  • Objet « onglet » (table z_plume.meta_tab) :

    Action

    Fonction pour générer la requête

    Lecture

    query_read_meta_tab

    Insertion & Mise à jour

    query_insert_or_update_meta_tab

    Suppression

    query_delete_meta_tab

  • Objet « association modèle-catégorie » (table z_plume.meta_template_categories) :

    Action

    Fonction pour générer la requête

    Lecture

    query_read_meta_template_categories

    Insertion & Mise à jour

    query_insert_or_update_meta_template_categories

    Suppression

    query_delete_meta_template_categories

Les requêtes de lecture renvoient les données sous la forme d’une liste de tuples contenant chacun un unique dictionnaire. Ce dictionnaire contient les attributs d’un objet (modèle, onglet, etc. selon la table consultée). Ses clés sont les noms des champs de la table PostgreSQL.

Par exemple, pour la récupération des données de la table des onglets :


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_read_meta_tab()
        )
        tabs = cur.fetchall()

conn.close()

connection_string est la chaîne de connexion à la base de données PostgreSQL.

Le contenu de tabs aura cette forme :

[
    (
        {
            'tab_id': 1,
            'tab_label': 'Onglet n°1',
            'tab_num': 10
        }
    ),
    (
        {
            'tab_id': 2,
            'tab_label': 'Onglet n°2',
            'tab_num': 20
        }
    )
] 

Les requêtes d’insertion/modification et les requêtes de suppression fonctionnent de la même façon : elles prennent en argument un dictionnaire data décrivant l’objet à créer, modifier ou supprimer, sous la même forme que les dictionnaires renvoyés par les fonctions de consultation. Ses clés doivent être nommées d’après des champs qui existent effectivement dans la table cible, ses valeurs doivent être fournies dans un format compatible avec les types desdits champs - cf. Modalités de saisie spécifiques à certains champs.

Le dictionnaire peut contenir les valeurs de tous les champs de la table (le cas échéant vides), ou seulement ceux qui ont été renseignés/modifiés, auxquels il faudra parfois ajouter quelques champs nécessaires au traitement - cf. Champs obligatoires. Dans le cas d’une suppression, seul le champ de clé primaire est attendu. Les autres informations peuvent néanmoins être fournies, elles ne seront simplement pas utilisées.

Les fonctions d’insertion/modification renvoient l’enregistrement mis à jour, ci-après new_data, tel qu’il est stocké dans la base à l’issue de l’opération. Il est indispensable d’actualiser le formulaire de saisie avec ces informations, qui peuvent différer significativement de la saisie initiale de l’utilisateur (ajout des valeurs par défaut sur les nouveaux enregistrements, etc.).

Exemple de la définition d’un nouveau modèle :


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_insert_or_update_meta_template(data)
        )
        new_data = cur.fetchone()[0]

conn.close()

data pourrait ressembler à ceci :

{
    'tpl_label': 'Mon nouveau modèle',
    'priority': 50
}

Il est également possible de saisir dans data une liste de valeurs, en utilisant le second argument columns pour les noms des champs.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_insert_or_update_meta_template(
                data, columns=columns
            )
        )
        new_data = cur.fetchone()[0]

conn.close()

Avec l’exemple précédent, data serait ici la liste ['Mon nouveau modèle', 50], et columns la liste ['tpl_label', 'priority'].

Champs obligatoires#

Les champs qui suivent doivent impérativement apparaître dans l’argument data fourni au constructeur de requêtes avec une valeur non nulle. Les champs précédés d’un astérisque peuvent être omis, mais ne doivent pas être vides s’ils sont présents.

Objet

Insertion

Modification

Suppression

Modèle

tpl_label, *enabled

tpl_id, tpl_label, *enabled

tpl_id

Catégorie de métadonnée (locale)

label, *origin (valant 'local'), *is_node

path, label, *origin (valant 'local'), *is_node

path

Catégorie de métadonnée (commune)

INTERDIT

path, origin (valant 'shared'), label, *is_node

path

Onglet

tab_label

tab_id, tab_label

tab_id

Association modèle-catégorie

tpl_id, shrcat_path ou loccat_path

tplcat_id, tpl_id

tplcat_id

Les catégories de métadonnées communes sont distinguées des catégories locales par le fait que l’attribut origin est présent et vaut shared. Il n’est pas permis d’ajouter de métadonnée commune : si origin vaut shared, la clé primaire path devra impérativement être renseignée et correspondre à l’identifiant d’une catégorie pré-existante que l’utilisateur souhaite mettre à jour.

Champs non éditables#

Les champs listés ci-après ne doivent pas pouvoir faire l’objet d’une modification manuelle par l’utilisateur.

Objet

Champs

Modèle

tpl_id (renseigné automatiquement à la création en base)

Catégorie de métadonnée (locale)

path (renseigné automatiquement à la création en base), is_node, sources, origin (vaut toujours 'local')

Catégorie de métadonnée (commune)

path (pré-renseigné), origin (vaut toujours 'shared'), is_node

Onglet

tab_id (renseigné automatiquement à la création en base)

Association modèle-catégorie

tpl_id + shrcat_path + loccat_path (définis une fois pour toute quand l’utilisateur déclare la catégorie comme utilisée par le modèle), tplcat_id (renseigné automatiquement à la création en base)

Champs avec des valeurs imposées#

Les champs ci-après n’admettent qu’un nombre fini de valeurs, qui peuvent être listées grâce à des requêtes du module plume.pg.queries.

Champ

Objet(s)

Requête listant les valeurs

special

Catégories et Associations modèle-catégorie

query_read_enum_meta_special

datatype

Catégories et Associations modèle-catégorie

query_read_enum_meta_datatype

geo_tools

Catégories et Associations modèle-catégorie

query_read_enum_meta_geo_tool

compute

Catégories et Associations modèle-catégorie

query_read_enum_meta_compute

Exemple de la récupération des valeurs admises par le champ datatype :


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_read_enum_meta_datatype()
        )
        enum_values = cur.fetchone()[0]

conn.close()

enum_values est la liste des valeurs, triées par ordre alphabétique.

Champ sources#

Le champ sources correspond à la liste des sources de vocabulaire contrôlé utilisables pour la catégorie commune de métadonnée. L’ADL peut supprimer des sources dont il ne souhaite pas que ses utilisateurs se servent. Les seules valeurs qu’il peut ajouter sont celles qu’il avait auparavant supprimées, ou qui étaient désactivées par défaut dans les modèles pré-configurés.

Pour lister toutes les sources disponibles pour une catégorie - ici, la license de distribution du jeu de données :

>>> from plume.rdf.properties import property_sources
>>> property_sources('dcat:distribution / dct:license')

L’argument de plume.rdf.properties.property_sources() est le chemin de la catégorie, qui peut être saisi tel qu’il apparaît dans le champ path de meta_categorie ou le champ shrcat_path de meta_template_categories.

Le résultat est une liste d’URI sous forme de chaînes de caractères, soit la forme utilisée pour les champs sources des tables susmentionnées.

Pour obtenir le libellé d’une source, on pourra avoir recours à la fonction plume.rdf.thesaurus.source_label() :


from plume.rdf.thesaurus import source_label

label = source_label(uri, langlist)

uri est l’URI de la source considérée. Elle peut notamment être saisie sous la forme d’une chaîne de caractères, soit telle que renvoyée par property_sources() ou récupérée dans les champs sources de meta_categorie et meta_template_categories. langlist est la liste des langues autorisées pour les traductions. On utilisera de préférence l’attribut plume.rdf.widgetsdict.WidgetsDict.langlist du dictionnaire de widgets qui a servi à générer la fiche de métadonnées courante.

En jouant sur l’argument optionnel linked, il est possible d’obtenir non une simple chaîne de caractères mais un élément HTML avec hyperlien :

>>> from plume.rdf.thesaurus import source_label
>>> source_label('http://inspire.ec.europa.eu/theme', 'fr', linked=True)
'<a href="http://inspire.ec.europa.eu/theme">Thème (INSPIRE)</a>'

Une seconde fonction, plume.rdf.thesaurus.source_url(), permet de récupérer uniquement l’URL de l’hyperlien (qui pourrait servir pour un bouton, par exemple) :


from plume.rdf.thesaurus import source_url

url = source_url(uri, langlist)

plume.rdf.thesaurus.source_nb_values() donne le nombre de valeurs de la source considérée :


from plume.rdf.thesaurus import source_nb_values

nb_values = source_nb_values(uri, langlist)

Enfin, la fonction plume.rdf.thesaurus.source_examples() permet de récupérer tout ou partie des valeurs proposées par la source.


from plume.rdf.thesaurus import source_examples

examples = source_examples(uri, langlist, start, limit, dots)

limit, start et dots sont des paramètres optionnels. limit indique le nombre maximum de valeurs à renvoyer (10 par défaut). start est l’indice de la première valeur renvoyée (0 par défaut, soit la première de la liste). dots est un booléen. S’il vaut True, comme c’est le cas par défaut, la valeur '...' est ajoutée à la fin de la liste lorsqu’il reste des valeurs non affichées.

Modalités de saisie spécifiques à certains champs#

D’une manière générale, le widget à utiliser pour un champ peut être déduit de son type.

Type PostgreSQL

Type Python

Widget

Remarques

boolean

bool

QCheckbox

Avec trois états sauf mention contraire ci-après.

text

str

QLineEdit

varchar

str

QLineEdit

Validateur QRegularExpressionValidator avec l’expression régulière QRegularExpression(f'^.{{0,{m}}}$')m est une variable correspondant à la longueur limite. En inscrivant plutôt la limite en dur, on aurait par exemple QRegularExpression('^.{0,48}$') pour le type varchar(48).

int

int

QLineEdit

Validateur QIntValidator pour assurer que seuls des chiffres sont saisis.

type énuméré

str

QCombobox, ou un QRadioButton par valeur selon leur nombre

Seules les valeurs prévues par le type sont autorisées. Cf. Champs avec des valeurs imposées.

liste de type énuméré

list(str)

Un QCheckbox par valeur autorisée ?

Idem type énuméré.

text[]

list(str)

à déterminer

Les champs de type jsonb - compute_params dans meta_template_categories et meta_categorie et md_conditions dans meta_template - nécessiteront des modalités de saisie et validation adaptées.

Les champs qui suivent requièrent des ajustements spécifiques pour assurer le respect des contraintes définies sur les tables de stockage.

Champ

Objet(s)

Particularité

tpl_label

Modèles

Expression régulière à adapter pour assurer aussi la non nullité : QRegularExpression('^.{1,48}$').

enabled

Modèles

Seulement deux états pour la case à cocher, qui doit être cochée par défaut.

tab_label

Onglets

Expression régulière à adapter pour assurer aussi la non nullité : QRegularExpression('^.{1,48}$').

label

Catégories

Validateur QRegularExpressionValidator avec l’expression régulière QRegularExpression('.') pour la non nullité.

rowspan

Catégories et Associations modèle-catégorie

Le QIntValidator doit aussi fixer la valeur minimum à 1 et maximum à 99.

Chargement des modèles pré-configurés#

Pour continuer à bénéficier des modèles pré-configurés après l’activation de PlumePg sur la base et pouvoir ensuite les éditer, l’administrateur doit commencer par charger ces modèles dans les tables de stockage.

Cette action pourra être proposée via le menu Configuration de la barre d’outils configuration.svg. Ses conditions d’apparition sont les mêmes que pour l’interface de configuration des modèles.

Libellé : Importer ou réinitialiser les modèles pré-configurés

Infobulle : Charge en base les modèles de fiche de métadonnées pré-configurés de Plume (Basique, Classique…). Réexécuter cette action a pour effet de réinitialiser les modèles. Les modifications réalisées après l’import initial seront perdues.

L’action est réalisée via la requête générée par la fonction plume.pg.queries.query_plume_pg_import_sample_template.


import psycopg2
from plume.pg import queries

conn = psycopg2.connect(connection_string)

with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_plume_pg_import_sample_template()
        )

conn.close()

À noter que la fonction permet aussi l’import d’un unique modèle ou d’une liste de modèles, dont le ou les noms sont alors à fournir en argument.

Il pourra être pertinent de recharger depuis le serveur la liste des modèles disponibles (utilisée pour le bouton de choix du modèle dans la barre d’outils de Plume) après l’exécution de cette requête - cf. Récupération de la liste des modèles.

Export et import de modèles#

Le gestionnaire des modèles accessible via le menu Configuration de la barre d’outils configuration.svg permet à l’administrateur d’exporter un ou plusieurs modèles en JSON et d’importer des données en base à partir d’un JSON de même format.

L’export est réalisé par la fonction plume.pg.template.dump_template_data(). Outre le chemin du fichier cible, celle-ci prend en argument le résultat brut des requêtes générées avec les functions query_read_meta_template(), query_read_meta_categorie(), query_read_meta_tab(), query_read_meta_template_categories(). Il est possible de spécifier un identifiant de modèle ou une liste d’identifiants de modèles via l’argument tpl_id, ce qui permet de n’exporter que les données relatives à ces modèles.


import psycopg2
from plume.pg import queries
from plume.pg.template import dump_template_data

conn = psycopg2.connect(connection_string)

# récupération des données en base
with conn:
    with conn.cursor() as cur:
    
        cur.execute(
            *queries.query_read_meta_template()
        )
        templates = cur.fetchall()

        cur.execute(
            *queries.query_read_meta_categorie()
        )
        categories = cur.fetchall()

        cur.execute(
            *queries.query_read_meta_tab()
        )
        tabs = cur.fetchall()

        cur.execute(
            *queries.query_read_meta_template_categories()
        )
        template_categories = cur.fetchall()

conn.close()

# export
try:
    dump_template_data(
        filepath,
        templates=templates,
        categories=categories,
        tabs=tabs,
        template_categories=template_categories,
        tpl_id=tpl_id
    )
except:
    ...

connection_string est la chaîne de connexion à la base de données. filepath est le chemin absolu du fichier de destination. S’il existait déjà, il est écrasé. tpl_id est l’identifiant du modèle considéré, ou une liste de modèles considérés. S’il est omis, vaut None ou une liste vide, toutes les données seront exportées.

Le contrôle d’erreur devrait essentiellement permettre de capturer des erreurs découlant de chemins inaccessibles. Le processus de sérialisation en lui-même n’est pas susceptible d’échouer lorsque les données ont été extraites de la base avec les requêtes prévues à cet effet.

Pour importer en base des données des modèles préalablement exportées en JSON, on utilise la classe plume.pg.template.TemplateQueryBuilder. Son générateur plume.pg.template.TemplateQueryBuilder.queries() produit des requêtes à exécuter sur la base. Si l’attribut plume.pg.template.TemplateQueryBuilder.waiting vaut True, le résultat doit être récupéré et fournit en argument de la méthode plume.pg.template.TemplateQueryBuilder.feedback(). Ces informations sont utilisées pour établir - grâce aux noms des modèles et onglets - la correspondance entre les identifiants numériques présents dans le fichier source et ceux de la base cible.


import psycopg2
from plume.pg import queries
from plume.pg.template import TemplateQueryBuilder

try:
    builder = TemplateQueryBuilder(filepath, no_update=no_update)
    conn = psycopg2.connect(connection_string)
    with conn:
        with conn.cursor() as cur:
            for query in builder.queries():
                cur.execute(*query)
                if builder.waiting:
                    result = cur.fetchone()
                    builder.feedback(result)
    conn.close()
except:
    ...

connection_string est la chaîne de connexion à la base de données. filepath est le chemin absolu du fichier source. no_update est un booléen qui détermine si les données des tables des catégories et des onglets sont mises à jour en fonction des données du fichier source (False, valeur par défaut) ou si les informations en base sont préservées (True).