-
Notifications
You must be signed in to change notification settings - Fork 1
Gramplets.fr
Les Gramplets sont le système d'extensions de Gramps Connect : de
petits programmes Python, attachés à une vue (Individu, Famille, ou
tout autre type d'objet — ou « Toutes ») qui interrogent l'arbre et
affichent un résultat — un tableau, un diagramme, du texte brut, ou du
HTML. Ce qui les rend inhabituels, c'est où ils s'exécutent : chacun
est du vrai Python, s'exécutant directement dans votre onglet de
navigateur sous Pyodide (CPython compilé en
WebAssembly), contre des wheels construits localement du propre modèle
de données gramps.gen.lib de Gramps. Rien n'est installé sur votre
machine, et rien ne s'exécute sur un serveur — voir
Architecture.
Cela veut aussi dire que chaque Gramplet est modifiable. En ouvrir un depuis l'éditeur de Gramplets de l'application, et vous regardez (et pouvez changer) sa véritable source — pas une boîte noire derrière un panneau de réglages. Changer l'apparence d'un diagramme, ajuster ce qu'une recherche cherche, ou en écrire une nouvelle de zéro, tout cela sans quitter l'application.
La boutique de Gramplets est un catalogue consultable à l'intérieur de
l'application. Installer, mettre à jour, ou retirer n'importe quelle
entrée en un clic. Elle est publiée indépendamment des propres versions
de Gramps Connect — le catalogue est du contenu statique
(gramplet-store/
dans le dépôt) récupéré par l'application en cours d'exécution au
moment voulu, pas intégré au build de l'application, de nouveaux
Gramplets peuvent donc apparaître dans la boutique sans que personne ne
mette à jour Gramps Connect lui-même.
Des exemples déjà présents dans le catalogue incluent :
| Gramplet | Ce qu'il fait |
|---|---|
| Age-at-Death Histogram | Histogramme plotly interactif de l'âge au décès sur tout l'arbre (ou la liste actuellement filtrée) |
| All Relationships | Calculs de relations sur tout l'arbre |
| Attributes / Backlinks / Children / Citations / Coordinates / Events / Gallery / Notes | Panneaux de détail dans le style des Gramplets de bureau pour la fiche sélectionnée |
| Born In | Recherche par lieu de naissance |
| Crossing Relationships | Trouve les relations qui croisent les générations de façon inhabituelle |
| GOQL Query | Taper une clause where GOQL brute, et choisir des expressions de champs comme colonnes |
| Hello Table | L'exemple de départ minimal |
| Interactive Search | Un Gramplet de recherche en direct, au fil de la frappe |
| People Explorer | Un Gramplet plus riche pour parcourir les individus |
| Phonebook Surname Sort | Tri des noms de famille insensible aux accents (par ex. « Müller » se trie à côté de « Muller ») |
| Relationship / Relationships | Comment deux personnes sélectionnées sont apparentées |
| Selected Record | Réagit à la fiche actuellement ouverte |
| To-Do / Todo Tracker | Suit les tâches de recherche à faire sur tout l'arbre |
| Tree Statistics | Un tableau de bord des comptes de fiches et pourcentages dérivés |
Le code d'un Gramplet s'exécute dans un bac à sable avec un ensemble de
fonctions et d'objets déjà disponibles — aucun import nécessaire pour
atteindre l'arbre. Le répertoire
gramplet_examples/
du dépôt est un parcours numéroté à travers l'API, du plus simple au
plus avancé ; la même référence est disponible depuis l'application
elle-même via le bouton d'aide (i) « Écrire un Gramplet » dans
l'éditeur de Gramplets.
matches = people(
"gender == Person.MALE and birth.date.sortval >= Date('Jan 1, 1900')",
limit=25,
)
columns("Person")
for person in matches:
row(person)-
people(where=None, order=None, limit=50)récupère les Individus correspondant à une requête, comme fiches complètes, en un seul appel — et il existe unfamilies()/events()/places()/repositories()/sources()/citations()/media()/notes()/tags()pour chaque autre type d'objet, même signature, juste une table différente. -
where=s'écrit en GOQL — exactement la même syntaxe que le champ de recherche de n'importe quelle vue de liste, y compris atteindre à travers des relations (birth.date.sortval) même quand le champ filtré n'est pas un de ceux affichés. - Pas besoin de
awaitbien que ce soit un véritable appel réseau sous le capot — il est inséré automatiquement. -
row(*values)ajoute une ligne au tableau. Passer un objet Person/Event/Place/… entier (plutôt qu'un champ choisi à la main) le représente comme un lien cliquable qui montre déjà son nom/titre complet et son ID Gramps — cliquer dessus ouvre une fenêtre pour voir cette fiche en Liste, Carte, Graphe, ou Chronologie.
Chaque fiche renvoyée par people()/families()/etc. est un
« DataDict » Gramps — un dict ordinaire des champs de l'objet, mais
avec un accès par point en plus : person.primary_name.first_name est
la même valeur que person["primary_name"]["first_name"]. Y recourir
chaque fois qu'il faut calculer quelque chose à partir d'un champ ou
montrer quelque chose que la propre représentation de row() ne fait
pas apparaître. row(), html(), et print() peuvent chacun être
appelés autant de fois que voulu, dans n'importe quel ordre — tout
apparaît dans l'ordre où cela a été ajouté.
total_people = db.get_number_of_people()
women = db.get_number_of("person", where="gender == Person.FEMALE")
no_birth_date = db.get_number_of("person", where="birth.date.sortval is None")db.get_number_of_<type>() est un comptage économique sur tout
l'arbre — le total revient dans un en-tête de réponse, aucune fiche
n'est réellement téléchargée. db.get_number_of(object_type, where=...) est l'équivalent conditionnel, pour quand le comptage
lui-même a besoin d'un filtre.
Une condition where= peut atteindre à travers une relation pour
filtrer dessus — l'événement de naissance d'une personne, le père
d'une famille, le lieu d'un événement — même si people()/
families()/etc. renvoient toujours un objet entier, indépendant, sans
moyen de redemander directement un champ lié :
born_and_died_same_place = people(
"birth.place.title == death.place.title and birth.place.title is not None",
order=[{"column": "surname", "direction": "asc"}],
limit=50,
)Récupérer un champ lié pour l'affichage (plutôt que simplement
filtrer dessus) est une recherche séparée, à la main, via les propres
méthodes de db — db.get_event_from_handle(...), et ainsi de
suite — suivant la même indirection par handle que le propre modèle de
données de Gramps utilise en interne.
Les collections fonctionnent aussi : any(c.given_name == 'Steve' for c in children) correspond à une famille si un enfant quelconque satisfait
une condition, et len([c for c in children if c.gender == Person.MALE]) > 1 compte seulement les fils. Voir
GOQL pour l'histoire complète des
collections, y compris sa limite actuelle (un saut dans une collection,
pas d'enchaînement supplémentaire au-delà de ce qu'elle contient).
record = get_selected()
if record is not None:
if isinstance(record, Person):
...get_selected() renvoie la fiche actuellement ouverte dans le propre
panneau de détail de la vue — None quand rien n'est sélectionné, ou
en s'exécutant depuis l'aperçu de l'éditeur autonome (qui n'a aucun
contexte de vue du tout). Un seul appel réseau au premier appel d'une
exécution, puis mémorisé pour le reste. isinstance(record, Person)
(etc.) indique quel type de fiche a été obtenu, ce qui importe pour un
Gramplet attaché aux vues « Toutes ». Pour que cela se mette à jour en
direct en cliquant à travers la liste, activer « Réexécuter
automatiquement quand la fiche sélectionnée change » dans le propre
éditeur du Gramplet — désactivé (par défaut), get_selected()
fonctionne toujours, mais ne reflète que ce qui était sélectionné la
dernière fois que le Gramplet s'est exécuté pour une autre raison.
Un Gramplet peut superposer sa propre requête à tout filtre actuellement appliqué à la vue — soit tapé dans le propre champ de recherche de la vue, soit appliqué via le sélecteur « Filtres » (un filtre enregistré ou une Règle personnalisée) — filtrer la liste pour la réduire à une branche de l'arbre restreint donc la propre sortie du Gramplet de la même façon :
rows = filter(
"person",
where=and_filters(get_filter(), "birth.date.sortval is not None and death.date.sortval is not None"),
)Cela nécessite views: [...] défini dans le manifeste (get_filter()
ne renvoie qu'un where_expr correspondant au propre type d'objet de
la vue) et listensToFilter: true pour que le Gramplet se réexécute
réellement quand le filtre change.
pygal, matplotlib et plotly sont tous préinstallés — un simple import
(ou, pour plotly, from plotly.subplots import make_subplots)
fonctionne hors ligne, aucun %pip install nécessaire. print(fig)
sur une Figure plotly, une chaîne SVG de pygal, ou une figure
matplotlib est reconnu automatiquement et rendu comme un diagramme —
aucun to_html()/intégration manuel nécessaire.
Pour les données de diagramme, préférer filter(object_type, what=[...], where=..., limit=...) à people()/families()/etc.
quand seuls un ou deux champs sont nécessaires — people() signifie un
appel réseau d'objet complet par fiche, ce qui est gaspillé pour un
diagramme qui ne trace, disons, que l'âge au décès.
col1, col2 = st.columns([2, 1])
with col1:
row("Colonne plus large")
with col2:
row("Colonne plus étroite")st.columns(spec) dispose spec régions côte à côte — un entier pour
ce nombre de colonnes de largeur égale, ou une liste de poids
(st.columns([2, 1])) pour des largeurs proportionnelles — et renvoie
une région par colonne. Tout ce qui est écrit à l'intérieur de
with col: (row()/html()/print(), ou un autre appel st.*, y
compris un st.columns() imbriqué) atterrit dans cette colonne plutôt
qu'au niveau supérieur ; col.write(x) fonctionne de la même façon
sans bloc with.
%pip install unidecode
from unidecode import unidecode%pip install est une syntaxe magique de Jupyter, réécrite en un
véritable appel await micropip.install([...]) avant l'exécution du
code — cela doit être sur sa propre ligne, tout en haut, exactement
comme dans un notebook (l'écrire à l'intérieur d'un if, par exemple,
ne fonctionnera pas). Cela ne fonctionne que pour les paquets Python
pur, sans code compilé/extension C ; pygal/matplotlib/plotly n'en ont
pas besoin car ils sont préinstallés, mais la plupart des petits
utilitaires Python purs sur PyPI s'installent bien de cette façon.
Un dossier par Gramplet dans
gramplet-store/,
nommé d'après son id (le nom du dossier et le propre champ id du
manifeste doivent correspondre) :
gramplet-store/
<slug>/
manifest.json # requis
code.py # requis -- le code source Python du Gramplet
icon.png # optionnel (png/jpg/jpeg/svg/webp)
Champs de manifest.json :
| Champ | Requis | Signification |
|---|---|---|
id |
oui | Doit correspondre au nom du dossier. |
name |
oui | Affiché comme titre de l'entrée dans la boutique. |
description |
oui | Affichée sous le nom dans la boutique. |
version |
oui | Semver simple, par ex. "1.0.0" — l'incrémenter à chaque changement de code.py ou du manifeste, pour que les copies installées puissent détecter qu'une mise à jour est disponible. |
author |
oui | Affiché dans la boutique. |
category |
oui | Une courte étiquette ("example", "detail", "utility", "chart", …) utilisée pour grouper/filtrer les entrées. |
views |
non | À quelles vues de type d'objet ("person", "family", …) ce Gramplet peut être ajouté. Omettre pour « tous les types ». |
listensToSelection |
non | Si ce Gramplet doit se réexécuter quand la fiche sélectionnée change. |
listensToFilter |
non | Si ce Gramplet doit se réexécuter quand le filtre actif change (champ de recherche ou sélecteur Filtres). |
Après avoir ajouté ou modifié une entrée, régénérer catalog.json —
le seul fichier que l'application récupère réellement :
npm --prefix app run build:gramplet-catalogCela valide chaque entrée (champs requis présents, id correspond à
son dossier, version ressemble à du semver, code.py non vide) et
échoue bruyamment au premier problème plutôt que de publier un
catalogue défectueux.
Les Gramplets sont en lecture seule contre l'arbre aujourd'hui — ils
peuvent interroger (filter()/get_object()) mais pas modifier ou
créer d'objets. Il n'y a pas non plus encore d'équivalent pour les
autres types d'add-ons de Gramps (outils, rapports). Voir
Feuille de route et limitations connues pour l'état actuel
de tout cela.
Gramps Connect is part of the family of Gramps-based software.
Using the app
- Overview
- Installing
- Deploying
- Messaging
- GOQL (advanced search)
- Gramplets & Add-on Store
- Data Model & Editing
- FAQ
Building & contributing