Repository navigation
Writing Gramplets.fr
🌐 English · Deutsch · Español · Nederlands · 简体中文
Comment écrire votre propre Gramplet et le publier dans la boutique de Gramplets. Seul le propriétaire d'un arbre ou un administrateur du site peut créer ou modifier des Gramplets (voir Qui peut utiliser les Gramplets).
Le code d'un Gramplet s'exécute dans un bac à sable où un ensemble de fonctions et d'objets est déjà disponible ; aucun import n'est nécessaire pour accéder à l'arbre.
Le répertoire
gramplet_examples/
du dépôt est une visite numérotée de l'API, du plus simple au plus avancé.
La même référence est disponible dans l'application, via le bouton d'aide
(i) « Writing a Gramplet » de 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, what=None, order=None, limit=50) récupère les
Personnes correspondant à une requête, sous forme d'enregistrements
complets, en un seul appel. Avec what=["gramps_id", "primary_name"], il
ne récupère que ces champs.
Il existe families() / events() / places() / repositories() /
sources() / citations() / media() / notes() / tags() pour chacun
des autres types d'enregistrement, avec la même signature.
where= s'écrit en GOQL, la même syntaxe que le champ de
recherche de n'importe quelle liste. Il peut traverser les relations
(birth.date.sortval), même quand le champ sur lequel vous filtrez n'est
pas l'un de ceux que vous affichez.
Pas besoin d'await, même s'il s'agit en coulisse d'un vrai appel
réseau. Il est inséré automatiquement.
-
row(*values)ajoute une ligne au tableau. Passer un objet Person/Event/Place/... entier, plutôt que l'un de ses champs, l'affiche comme un lien cliquable avec son nom ou titre complet et son ID Gramps. Un clic ouvre une fenêtre pour voir cet enregistrement en Liste, Carte, Graphe ou Chronologie. -
name_of(person)est le nom d'une personne en texte, prénom puis noms de famille (« Root Ancestor »), comme les étiquetterow(), pour un titre ou une phrase. Il accepte aussi l'un de ses noms, par exemplename_of(person.alternate_names[0]). -
row(),html()etprint()peuvent chacun être appelés autant de fois que vous voulez, dans n'importe quel ordre. Tout apparaît dans l'ordre où vous l'avez ajouté.
Chaque enregistrement renvoyé par people(), families() et les autres
est un « DataDict » Gramps : un simple dict des champs de
l'enregistrement, avec l'accès par point en plus.
person.primary_name.first_name est la même valeur que
person["primary_name"]["first_name"]. Utilisez-le quand vous voulez
calculer quelque chose à partir d'un champ, ou en afficher un que row()
n'affiche pas.
Afficher un enregistrement n'échoue jamais. print(person) ou
f"{person}" montre un enregistrement entier comme l'étiquette row()
(« Root Ancestor [I0001] »), une date comme une date, et tout le reste (un
résultat de filter(), person.primary_name) sous forme de ses champs
simples.
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 décompte bon marché de tout l'arbre. Le
total arrive dans un en-tête de réponse, donc aucun enregistrement n'est
téléchargé. db.get_number_of(object_type, where=...) fait la même chose
avec un filtre.
Une condition where= peut traverser 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.
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,
)people(), families() et les autres renvoient toujours
l'enregistrement lui-même, sans moyen de demander directement un champ
d'un enregistrement lié. Le récupérer pour l'afficher passe par une
recherche séparée avec les méthodes de db, comme
db.get_event_from_handle(...), en suivant les mêmes handles que le
propre modèle de données de Gramps.
any(c.given_name == 'Steve' for c in children) correspond à une famille
si l'un des enfants remplit une condition, et
len([c for c in children if c.gender == Person.MALE]) > 1 ne compte que
les fils. Voir GOQL pour plus de détails,
y compris la limite actuelle : un saut dans une collection, sans chaîner
au-delà de ce qu'elle contient.
record = get_selected()
if record is not None:
if isinstance(record, Person):
...get_selected() renvoie l'enregistrement ouvert dans le panneau de
détail de la vue. Il vaut None quand rien n'est sélectionné, ou lors
d'une exécution depuis l'aperçu de l'éditeur, qui n'a pas de vue. Il fait
une requête réseau à son premier appel dans une exécution, puis garde le
résultat pour le reste de celle-ci.
isinstance(record, Person) (etc.) vous dit quel type d'enregistrement
vous avez reçu, ce qui compte pour un Gramplet rattaché à « Toutes » les
vues.
Pour que le Gramplet se mette à jour en direct pendant que vous parcourez
la liste, activez « Re-run automatically when the selected record
changes » dans son éditeur. Si c'est désactivé (par défaut),
get_selected() fonctionne quand même, mais ne reflète que ce qui était
sélectionné lors de la dernière exécution du Gramplet.
Un Gramplet peut superposer sa propre requête au filtre appliqué à la vue, qu'il soit saisi dans le champ de recherche ou choisi dans Filtres (un filtre enregistré ou une règle personnalisée). Filtrer la liste sur une branche de l'arbre restreint alors la 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 demande deux réglages dans le manifeste :
-
views: [...], carget_filter()ne renvoie qu'unwhere_exprcorrespondant au propre type d'enregistrement de la vue ; -
listensToFilter: true, pour que le Gramplet se réexécute quand le filtre change.
pygal, matplotlib et plotly sont fournis, donc un simple import (ou,
pour plotly, from plotly.subplots import make_subplots) fonctionne hors
ligne, sans %pip install.
print(fig) sur une Figure plotly, une chaîne SVG de pygal ou une figure
matplotlib est reconnu et dessiné comme un diagramme, sans to_html() ni
intégration manuelle.
Pour les données d'un diagramme, passez what=[...] quand vous n'avez
besoin que d'un ou deux champs. people() seul télécharge tous les champs
de tous les enregistrements, ce qui est du gaspillage pour un diagramme
qui ne montre, par exemple, que l'âge au décès.
col1, col2 = st.columns([2, 1])
with col1:
row("Wider column")
with col2:
row("Narrower column")st.columns(spec) dispose des zones côte à côte : un entier pour autant
de colonnes de même largeur, ou une liste de poids (st.columns([2, 1]))
pour des largeurs proportionnelles. Il renvoie une zone par colonne.
Tout ce qui est écrit dans with col: (row(), html(), print() ou un
autre appel st.*, y compris un st.columns() imbriqué) va dans cette
colonne. col.write(x) fait la même chose sans bloc with.
Gramplets → Nouveau Gramplet… ouvre l'éditeur de Gramplets prêt pour cette sorte. Dans un Gramplet existant, réglez Sorte sur Gramplet — opens in a window from the Gramplets menu.
À la place des réglages de Gramplet de vue, il y a un champ Catégorie,
qui le regroupe avec d'autres dans le menu Gramplets (par exemple
chart). Laissé vide, il va sous Other.
Le code s'exécute dès que la fenêtre s'ouvre. Pour demander d'abord une
saisie, dessinez-la avec les widgets st.* et faites le vrai travail
derrière un st.button. Ils suivent les noms et arguments de
Streamlit, et
fonctionnent de la même façon dans les Gramplets de vue :
-
st.button,st.text_input,st.number_input,st.checkbox,st.selectbox,st.radio -
st.columns,st.progress - les encadrés de message
st.info/st.success/st.warning/st.error st.session_state
Pour un Gramplet qui dure longtemps, affichez une barre avec
st.progress(value, text), comme dans Streamlit. value va de 0 à 100
(ou de 0.0 à 1.0). .progress(...) sur la barre renvoyée la fait avancer
sur place et .empty() la retire, assez bon marché pour être appelé une
fois par ligne.
people(..., limit=None) (et les autres fonctions de liste) renvoient
toutes les correspondances au lieu des 50 premières :
surname = st.text_input("Surname", "Smith")
if st.button("Run"):
everyone = people(f"primary_name.surname_list[0].surname == '{surname}'", limit=None)
bar = st.progress(0, text="Checking people...")
for i, person in enumerate(everyone):
bar.progress((i + 1) / len(everyone))
...
bar.empty()Un Gramplet peut s'exécuter aussi longtemps qu'il le faut, et une page ouverte depuis des heures peut toujours en exécuter. Chaque requête d'un Gramplet utilise votre connexion actuelle, renouvelée en arrière-plan comme dans le reste de l'application. Ce n'est que si la connexion elle-même a pris fin qu'une exécution s'arrête, avec Your session has expired. Sign in again, then rerun this Gramplet.
%pip install unidecode
from unidecode import unidecode%pip install est une syntaxe à la Jupyter, réécrite en un vrai appel
await micropip.install([...]) avant l'exécution de votre code. Il doit
être sur sa propre ligne, en haut, comme dans un notebook ; à l'intérieur
d'un if, par exemple, il ne fonctionne pas.
Cela ne fonctionne que pour les paquets en Python pur, sans code compilé ni extension C. La plupart des petits utilitaires en Python pur de PyPI s'installent sans problème ainsi.
Un Gramplet (pas un Gramplet de vue, qui ne modifie jamais de données) peut modifier et supprimer des enregistrements une fois Autoriser les modifications de données activé dans l'éditeur de Gramplets. L'API est celle qu'utilise Gram.py Script dans Gramps de bureau : modifiez un enregistrement comme n'importe quel objet Python.
for person in people("len(citation_list) == 0", limit=None):
person.private = True-
Mark Unsourced People Private trouve d'abord les personnes à moindre
coût avec
filter(), puis ne récupère et ne modifie que celles qui en ont besoin, dans un groupebegin_changes()nommé. -
Tag People ajoute à une liste (
person.tag_list.append(tag.handle); les enregistrements stockent les étiquettes sous forme de handles, pas de noms) et utilise unst.selectbox()dans lequel il faut choisir avant que quoi que ce soit change.
-
record.field = valuemodifie un champ, y compris imbriqué (person.primary_name.first_name = "Chris"). De même pour les modifications de listes (.append(),.pop(), ...) et toute méthodeset_*(), par exempleperson.set_privacy(True). -
delete(record)supprime un enregistrement. Il ne retire pas les références à celui-ci dans les autres enregistrements. -
begin_changes("message")/end_changes()regroupent les modifications faites entre les deux sous ce message. Ils sont facultatifs : sans eux, toute l'exécution forme un seul groupe, nommé d'après le Gramplet.
Les enregistrements issus de people() et des autres fonctions au
pluriel, de get_raw_object(), get_object(), get_selected() et
db.* peuvent être modifiés. Les résultats de filter() ne le peuvent
pas, car ils ne contiennent qu'une partie des champs d'un enregistrement.
Demander deux fois le même enregistrement dans une exécution renvoie le même objet, avec vos modifications dedans.
Dans l'éditeur de Gramplets, chaque exécution est un essai à blanc. Chaque enregistrement modifié est d'abord vérifié par rapport à son schéma Gramps, la même vérification que fait le serveur ; une valeur invalide arrête l'exécution et nomme le champ.
Un essai à blanc peut différer de la vraie exécution sur un point : les
recherches (filter(), count(), where=) s'exécutent sur le serveur,
elles voient donc encore l'arbre tel qu'il était avant les modifications
de l'exécution.
Dans une vraie exécution, Exécuter enregistre chaque groupe au moment
où le code le ferme, à end_changes() ou à la fin de l'exécution. Voir
Modifier des données pour ce que voit
l'utilisateur, y compris Annuler.
Les widgets de la sortie (st.button("Apply"), etc.) réexécutent le code
dans le mode d'où provient le résultat actuel. Un Gramplet peut ainsi
montrer dans un essai à blanc ce qu'il changerait, et l'appliquer après
Exécuter.
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 # required
code.py # required -- the Gramplet's Python source
icon.png # optional (png/jpg/jpeg/svg/webp)
| 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). |
kind |
non |
"view" (par défaut) pour un Gramplet de vue ; "window" pour un Gramplet lancé depuis le menu Gramplets. views/listensTo* ne s'appliquent qu'aux Gramplets de vue. |
allowChanges |
non |
true pour l'installer avec Autoriser les modifications de données activé (la boutique affiche un badge Modifie vos données). Autorisé seulement avec "kind": "window". |
Après avoir ajouté ou modifié une entrée, régénérez catalog.json, le
seul fichier que télécharge l'application :
npm --prefix app run build:gramplet-catalogCela vérifie chaque entrée (champs obligatoires présents, id
correspondant à son dossier, version ressemblant à du semver, code.py
non vide, kind valide) et échoue clairement au premier problème plutôt
que de publier un catalogue cassé.
Gramps Connect is part of the family of Gramps-based software.
Getting started
Using the app
- Working Together
- Messaging
- Search & Filters
- GOQL (advanced search)
- Charts
- Maps
- Stories & Photos
- Data Model & Editing
- Gramplets & Add-on Store
- Administration & Settings
Building & contributing