Repository navigation
Writing Gramplets.nl
🌐 English · Deutsch · Español · Français · 简体中文
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).
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.
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) 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.
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.
-
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"), zoalsrow()ze labelt, voor een kop of een zin. Het accepteert ook een van de namen van die persoon, bijvoorbeeldname_of(person.alternate_names[0]). -
row(),html()enprint()kunt u elk zo vaak aanroepen als u wilt, in elke volgorde. Alles verschijnt in de volgorde waarin u het toevoegde.
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 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.
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.
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,
)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.
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.
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.
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.
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: [...], omdatget_filter()alleen eenwhere_exprteruggeeft die past bij het eigen soort record van de weergave; -
listensToFilter: true, zodat de Gramplet opnieuw draait als het filter verandert.
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.
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.
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.
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
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()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.
%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.
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-
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 benoemdebegin_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 eenst.selectbox()waarin u moet kiezen voordat er iets verandert.
-
record.field = valuewijzigt een veld, ook geneste (person.primary_name.first_name = "Chris"). Lijstbewerkingen (.append(),.pop(), ...) en elkeset_*()-methode doen dat ook, bijvoorbeeldperson.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.
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.
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.
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.
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)
| 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". |
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-catalogDit 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.
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