Skip to content

Writing Gramplets.nl

Doug Blank edited this page Oct 19, 2026 · 1 revision

🌐 English · Deutsch · Español · Français · 简体中文

Gramplets schrijven

Hoe u een eigen Gramplet schrijft en in de Gramplet-winkel publiceert. Alleen de eigenaar van een stamboom of een sitebeheerder kan Gramplets maken of bewerken (zie Wie Gramplets kan gebruiken).

Aan de slag

De code van een Gramplet draait in een sandbox waarin al een reeks functies en objecten beschikbaar is, dus er zijn geen imports nodig om bij de stamboom te komen.

De map gramplet_examples/ in de repository is een genummerde rondleiding door de API, van eenvoudig naar gevorderd. Dezelfde uitleg is in de app beschikbaar via de helpknop (i) "Writing a Gramplet" in de Gramplet-editor.

De kleinste nuttige Gramplet

matches = people(
    "gender == Person.MALE and birth.date.sortval >= Date('Jan 1, 1900')",
    limit=25,
)

columns("Person")
for person in matches:
    row(person)

Records ophalen

people(where=None, what=None, order=None, limit=50) haalt de Personen op die bij een zoekopdracht passen, als volledige records, in één aanroep. Met what=["gramps_id", "primary_name"] haalt het alleen die velden op.

Er is een families() / events() / places() / repositories() / sources() / citations() / media() / notes() / tags() voor elk ander soort record, met dezelfde signatuur.

De zoekopdracht schrijven

where= wordt geschreven in GOQL, dezelfde syntaxis als het zoekvak van elke lijst. Het kan over relaties heen reiken (birth.date.sortval), ook als het veld waarop u filtert er niet een is dat u toont.

Er is geen await nodig, ook al is dit onder de motorkap een echte netwerkaanroep. Die wordt automatisch voor u ingevoegd.

Resultaten tonen

  • row(*values) voegt een rij aan de tabel toe. Een heel Person-/Event-/Place-/...-object doorgeven, in plaats van een van de velden, toont het als klikbare link met de volledige naam of titel en de Gramps-ID. Klikken opent een venster om dat record te bekijken in Lijst, Kaart, Graaf of Tijdlijn.
  • name_of(person) is de naam van een persoon als tekst, voornaam en dan achternamen ("Root Ancestor"), zoals row() ze labelt, voor een kop of een zin. Het accepteert ook een van de namen van die persoon, bijvoorbeeld name_of(person.alternate_names[0]).
  • row(), html() en print() kunt u elk zo vaak aanroepen als u wilt, in elke volgorde. Alles verschijnt in de volgorde waarin u het toevoegde.

Werken met records

Elk record dat people(), families() en de rest teruggeven, is een Gramps-"DataDict": een gewone dict met de velden van het record, met punttoegang erbovenop. person.primary_name.first_name is dezelfde waarde als person["primary_name"]["first_name"]. Gebruik het wanneer u iets uit een veld wilt berekenen, of een veld wilt tonen dat row() niet toont.

Een record afdrukken

Een record afdrukken mislukt nooit. print(person) of f"{person}" toont een heel record zoals row() het labelt ("Root Ancestor [I0001]"), een datum als datum, en al het andere (een resultaat van filter(), person.primary_name) als de gewone velden.

Tellen zonder de stamboom te downloaden

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>() is een goedkope telling over de hele stamboom. Het totaal komt terug in een header van het antwoord, dus er worden geen records gedownload. db.get_number_of(object_type, where=...) doet hetzelfde met een filter.

Over relaties heen reiken

In een zoekopdracht

Een where=-voorwaarde kan over een relatie heen reiken om erop te filteren: de geboortegebeurtenis van een persoon, de vader van een gezin, de plaats van een gebeurtenis.

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,
)

Om te tonen

people(), families() en de rest geven altijd het record zelf terug, zonder manier om direct om een veld van een gerelateerd record te vragen. Er een terugkrijgen om te tonen is een aparte opzoeking via de methoden van db, zoals db.get_event_from_handle(...), met dezelfde handles die het eigen datamodel van Gramps gebruikt.

Collecties

any(c.given_name == 'Steve' for c in children) past bij een gezin als een van de kinderen aan een voorwaarde voldoet, en len([c for c in children if c.gender == Person.MALE]) > 1 telt alleen de zonen. Zie GOQL voor meer, inclusief de huidige grens: één stap een collectie in, niet verder doorketenen dan wat erin zit.

Reageren op het geselecteerde record

record = get_selected()
if record is not None:
    if isinstance(record, Person):
        ...

get_selected() geeft het record terug dat in het detailpaneel van de weergave open staat. Het is None als er niets is geselecteerd, of als u draait vanuit het voorbeeld van de editor, dat geen weergave heeft. Het doet bij de eerste aanroep in een run één netwerkverzoek en onthoudt het resultaat voor de rest van de run.

isinstance(record, Person) (enz.) vertelt u wat voor record u hebt gekregen, wat ertoe doet voor een Gramplet die aan "Alle" weergaven is gekoppeld.

Bijwerken als de selectie verandert

Om de Gramplet live te laten bijwerken terwijl u door de lijst klikt, zet u "Re-run automatically when the selected record changes" aan in de editor. Staat dat uit (de standaard), dan werkt get_selected() nog steeds, maar toont het alleen wat geselecteerd was toen de Gramplet voor het laatst draaide.

Reageren op het actieve filter

Een Gramplet kan een eigen zoekopdracht leggen bovenop het filter dat op de weergave is toegepast, of dat nu in het zoekvak is getypt of onder Filters is gekozen (een opgeslagen filter of een aangepaste regel). Filtert u de lijst tot één tak van de stamboom, dan wordt de uitvoer van de Gramplet op dezelfde manier beperkt:

rows = filter(
    "person",
    where=and_filters(get_filter(), "birth.date.sortval is not None and death.date.sortval is not None"),
)

Daarvoor zijn twee manifestinstellingen nodig:

  • views: [...], omdat get_filter() alleen een where_expr teruggeeft die past bij het eigen soort record van de weergave;
  • listensToFilter: true, zodat de Gramplet opnieuw draait als het filter verandert.

Grafieken

pygal, matplotlib en plotly zijn meegeleverd, dus een gewone import (of, voor plotly, from plotly.subplots import make_subplots) werkt offline, zonder %pip install.

print(fig) op een plotly-Figure, een SVG-string van pygal of een matplotlib-figuur wordt herkend en als grafiek getekend, zonder handmatig to_html() of insluiten.

Geef voor grafiekgegevens what=[...] mee als u maar een of twee velden nodig hebt. people() alleen downloadt elk veld van elk record, wat verspilling is voor een grafiek die bijvoorbeeld alleen de leeftijd bij overlijden toont.

Naast elkaar

col1, col2 = st.columns([2, 1])
with col1:
    row("Wider column")
with col2:
    row("Narrower column")

st.columns(spec) zet gebieden naast elkaar: een geheel getal voor zoveel even brede kolommen, of een lijst met gewichten (st.columns([2, 1])) voor verhoudingsgewijze breedtes. Het geeft één gebied per kolom terug.

Alles wat binnen with col: wordt geschreven (row(), html(), print() of een andere st.*-aanroep, inclusief een geneste st.columns()) komt in die kolom. col.write(x) doet hetzelfde zonder with-blok.

Een Gramplet in een eigen venster

Gramplets → Nieuwe Gramplet… opent de Gramplet-editor voor dit soort. Zet bij een bestaande Gramplet Soort op Gramplet — opens in a window from the Gramplets menu.

In plaats van de instellingen voor Weergave-Gramplets is er een veld Categorie, dat hem in het menu Gramplets bij andere groepeert (bijvoorbeeld chart). Leeg gelaten komt hij onder Other.

Om invoer vragen

De code draait zodra het venster opent. Om eerst om invoer te vragen, tekent u die met de st.*-widgets en doet u het echte werk achter een st.button. Ze volgen de namen en argumenten van Streamlit en werken in Weergave-Gramplets net zo:

  • st.button, st.text_input, st.number_input, st.checkbox, st.selectbox, st.radio
  • st.columns, st.progress
  • de berichtvakken st.info / st.success / st.warning / st.error
  • st.session_state

Voortgang tonen

Toon bij een Gramplet die lang draait een balk met st.progress(value, text), zoals in Streamlit. value is 0–100 (of 0.0–1.0). .progress(...) van de teruggegeven balk verplaatst hem ter plekke en .empty() verwijdert hem, goedkoop genoeg om per rij aan te roepen.

people(..., limit=None) (en de andere lijstfuncties) geven elke treffer terug in plaats van de eerste 50:

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()

Lange runs

Een Gramplet kan zo lang draaien als nodig, en een pagina die uren open staat, kan ze nog steeds uitvoeren. Elk verzoek van een Gramplet gebruikt uw huidige aanmelding, die net als in de rest van de app op de achtergrond wordt vernieuwd. Alleen als de aanmelding zelf is verlopen, stopt een run, met Your session has expired. Sign in again, then rerun this Gramplet.

Een pakket van derden installeren

%pip install unidecode
from unidecode import unidecode

%pip install is Jupyter-syntaxis, die vóór het uitvoeren van uw code wordt herschreven tot een echte aanroep await micropip.install([...]). Het moet op een eigen regel bovenaan staan, zoals in een notebook; binnen een if bijvoorbeeld werkt het niet.

Dit werkt alleen voor pure-Python-pakketten zonder gecompileerde code of C-extensies. De meeste kleine pure-Python-hulpmiddelen op PyPI installeren zo zonder problemen.

Gegevens wijzigen vanuit code

Een Gramplet (geen Weergave-Gramplet, die nooit gegevens wijzigt) kan records wijzigen en verwijderen zodra Gegevenswijzigingen toestaan in de Gramplet-editor aan staat. De API is dezelfde die Gram.py Script in Gramps Desktop gebruikt: wijzig een record zoals elk ander Python-object.

for person in people("len(citation_list) == 0", limit=None):
    person.private = True

Voorbeelden in de winkel

  • Mark Unsourced People Private vindt eerst goedkoop personen met filter(), en haalt dan alleen de personen op die het nodig hebben en wijzigt die, binnen één benoemde begin_changes()-groep.
  • Tag People voegt iets aan een lijst toe (person.tag_list.append(tag.handle); records slaan labels op als handles, niet als namen) en gebruikt een st.selectbox() waarin u moet kiezen voordat er iets verandert.

Wijzigen, verwijderen en groeperen

  • record.field = value wijzigt een veld, ook geneste (person.primary_name.first_name = "Chris"). Lijstbewerkingen (.append(), .pop(), ...) en elke set_*()-methode doen dat ook, bijvoorbeeld person.set_privacy(True).
  • delete(record) verwijdert een record. Verwijzingen ernaar in andere records worden niet verwijderd.
  • begin_changes("message") / end_changes() groeperen de wijzigingen ertussen onder dat bericht. Ze zijn optioneel: zonder hen is de hele run één groep, genoemd naar de Gramplet.

Welke records kunnen worden gewijzigd

Records uit people() en de andere meervoudsfuncties, get_raw_object(), get_object(), get_selected() en db.* kunnen worden gewijzigd. Resultaten van filter() niet, omdat die maar een deel van de velden van een record bevatten.

Twee keer om hetzelfde record vragen in een run geeft hetzelfde object terug, met uw wijzigingen erin.

Proefruns tijdens het ontwikkelen

In de Gramplet-editor is elke run een proefrun. Elk gewijzigd record wordt eerst gecontroleerd tegen zijn Gramps-schema, dezelfde controle die de server doet; een ongeldige waarde stopt de run en noemt het veld.

Een proefrun kan op één punt van de echte run verschillen: zoekopdrachten (filter(), count(), where=) draaien op de server, dus die zien de stamboom nog zoals hij was vóór de wijzigingen van de run.

Wanneer wijzigingen worden opgeslagen

In een echte run slaat Uitvoeren elke groep op zodra de code die afsluit, bij end_changes() of aan het einde van de run. Zie Gegevens wijzigen voor wat de gebruiker ziet, inclusief Ongedaan maken.

Widgets in de uitvoer (st.button("Apply") enzovoort) voeren de code opnieuw uit in de modus waaruit het huidige resultaat komt. Zo kan een Gramplet in een proefrun tonen wat hij zou wijzigen, en het na Uitvoeren toepassen.

Publiceren in de winkel

Mappenstructuur

Eén map per Gramplet in gramplet-store/, genoemd naar de id. De mapnaam en het eigen id-veld van het manifest moeten overeenkomen.

gramplet-store/
  <slug>/
    manifest.json   # required
    code.py         # required -- the Gramplet's Python source
    icon.png        # optional (png/jpg/jpeg/svg/webp)

manifest.json

Veld Verplicht Betekenis
id ja Moet overeenkomen met de mapnaam.
name ja Getoond als titel van het item in de winkel.
description ja Getoond onder de naam in de winkel.
version ja Gewone semver, bijv. "1.0.0" — verhoog die telkens wanneer code.py of het manifest verandert, zodat geïnstalleerde kopieën kunnen zien dat er een update beschikbaar is.
author ja Getoond in de winkel.
category ja Een kort label ("example", "detail", "utility", "chart", ...) om items te groeperen/filteren.
views nee Aan welke weergaven per objecttype ("person", "family", ...) deze Gramplet kan worden toegevoegd. Weglaten voor "elk type".
listensToSelection nee Of deze Gramplet opnieuw moet draaien als het geselecteerde record verandert.
listensToFilter nee Of deze Gramplet opnieuw moet draaien als het actieve filter verandert (zoekvak of keuzelijst Filters).
kind nee "view" (de standaard) voor een Weergave-Gramplet; "window" voor een Gramplet die vanuit het menu Gramplets draait. views/listensTo* gelden alleen voor Weergave-Gramplets.
allowChanges nee true om hem te installeren met Gegevenswijzigingen toestaan ingeschakeld (de winkel toont een badge Wijzigt uw gegevens). Alleen toegestaan met "kind": "window".

De catalogus opnieuw bouwen

Maak na het toevoegen of bewerken van een item catalog.json opnieuw, het enige bestand dat de app ophaalt:

npm --prefix app run build:gramplet-catalog

Dit controleert elk item (verplichte velden aanwezig, id past bij de map, version lijkt op semver, code.py niet leeg, kind geldig) en faalt duidelijk bij het eerste probleem in plaats van een kapotte catalogus te publiceren.

Clone this wiki locally