Repository navigation
Writing Gramplets.es
🌐 English · Deutsch · Français · Nederlands · 简体中文
Cómo escribir su propio Gramplet y publicarlo en la tienda de Gramplets. Solo el propietario de un árbol o un administrador del sitio puede crear o editar Gramplets (vea Quién puede usar los Gramplets).
El código de un Gramplet se ejecuta en un entorno aislado con un conjunto de funciones y objetos ya disponibles, así que no hacen falta imports para llegar al árbol.
El directorio
gramplet_examples/
del repositorio es un recorrido numerado por la API, de lo más sencillo a
lo más avanzado. La misma referencia está disponible en la aplicación,
desde el botón de ayuda (i) «Writing a Gramplet» del editor 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) obtiene las
Personas que cumplen una consulta, como registros completos, en una sola
llamada. Con what=["gramps_id", "primary_name"] obtiene solo esos
campos.
Hay families() / events() / places() / repositories() /
sources() / citations() / media() / notes() / tags() para cada
uno de los demás tipos de registro, con la misma firma.
where= se escribe en GOQL, la misma sintaxis que el cuadro de
búsqueda de cualquier lista. Puede cruzar relaciones
(birth.date.sortval), aunque el campo por el que filtra no sea uno que
muestre.
No hace falta await, aunque por debajo es una llamada de red real. Se
inserta automáticamente.
-
row(*values)añade una fila a la tabla. Pasar un objeto Person/Event/Place/... completo, en lugar de uno de sus campos, lo muestra como un enlace con su nombre o título completo y su ID de Gramps. Al hacer clic se abre una ventana para ver ese registro en Lista, Mapa, Grafo o Cronología. -
name_of(person)es el nombre de una persona como texto, nombre de pila y luego apellidos («Root Ancestor»), tal como los etiquetarow(), para un encabezado o una frase. También acepta uno de sus nombres, por ejemploname_of(person.alternate_names[0]). -
row(),html()yprint()pueden llamarse tantas veces como quiera, en cualquier orden. Todo aparece en el orden en que lo añadió.
Cada registro devuelto por people(), families() y los demás es un
«DataDict» de Gramps: un dict simple con los campos del registro, con
acceso por punto encima. person.primary_name.first_name es el mismo valor
que person["primary_name"]["first_name"]. Úselo cuando quiera calcular
algo a partir de un campo, o mostrar uno que row() no muestra.
Imprimir un registro nunca falla. print(person) o f"{person}" muestra
un registro completo como lo etiqueta row() («Root Ancestor [I0001]»),
una fecha como fecha, y cualquier otra cosa (un resultado de filter(),
person.primary_name) como sus campos 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>() es un recuento barato de todo el árbol. El
total llega en una cabecera de la respuesta, así que no se descarga ningún
registro. db.get_number_of(object_type, where=...) es lo mismo con un
filtro.
Una condición where= puede cruzar una relación para filtrar por ella: el
evento de nacimiento de una persona, el padre de una familia, el lugar de
un evento.
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() y los demás siempre devuelven el propio registro,
sin forma de pedir directamente un campo de un registro relacionado.
Obtenerlo para mostrarlo es una consulta aparte con los métodos de db,
como db.get_event_from_handle(...), siguiendo los mismos handles que usa
el propio modelo de datos de Gramps.
any(c.given_name == 'Steve' for c in children) encaja con una familia si
algún hijo cumple una condición, y
len([c for c in children if c.gender == Person.MALE]) > 1 cuenta solo
los hijos varones. Vea GOQL para más
detalles, incluido el límite actual: un salto dentro de una colección, sin
encadenar más allá de lo que contiene.
record = get_selected()
if record is not None:
if isinstance(record, Person):
...get_selected() devuelve el registro abierto en el panel de detalle de la
vista. Es None cuando no hay nada seleccionado, o al ejecutar desde la
vista previa del editor, que no tiene vista. Hace una petición de red en
su primera llamada de una ejecución y recuerda el resultado durante el
resto.
isinstance(record, Person) (etc.) le dice qué tipo de registro ha
recibido, lo que importa para un Gramplet asociado a «Todas» las vistas.
Para que el Gramplet se actualice en vivo mientras recorre la lista, active
«Re-run automatically when the selected record changes» en su editor. Con
eso desactivado (lo predeterminado), get_selected() sigue funcionando,
pero solo refleja lo que estaba seleccionado la última vez que se ejecutó
el Gramplet.
Un Gramplet puede superponer su propia consulta al filtro aplicado a la vista, ya sea escrito en el cuadro de búsqueda o elegido en Filtros (un filtro guardado o una regla personalizada). Filtrar la lista a una rama del árbol restringe entonces la salida del Gramplet de la misma manera:
rows = filter(
"person",
where=and_filters(get_filter(), "birth.date.sortval is not None and death.date.sortval is not None"),
)Esto necesita dos ajustes en el manifiesto:
-
views: [...], ya queget_filter()solo devuelve unwhere_exprque coincide con el propio tipo de registro de la vista; -
listensToFilter: true, para que el Gramplet se vuelva a ejecutar cuando cambie el filtro.
pygal, matplotlib y plotly vienen incluidos, así que un simple import (o,
para plotly, from plotly.subplots import make_subplots) funciona sin
conexión, sin %pip install.
print(fig) sobre una Figure de plotly, una cadena SVG de pygal o una
figura de matplotlib se reconoce y se dibuja como diagrama, sin to_html()
ni incrustación manual.
Para los datos de un diagrama, pase what=[...] cuando solo necesite uno o
dos campos. people() por sí solo descarga todos los campos de todos los
registros, lo que es un desperdicio para un diagrama que solo representa,
por ejemplo, la edad al morir.
col1, col2 = st.columns([2, 1])
with col1:
row("Wider column")
with col2:
row("Narrower column")st.columns(spec) dispone regiones una al lado de otra: un entero para
ese número de columnas de igual anchura, o una lista de pesos
(st.columns([2, 1])) para anchuras proporcionales. Devuelve una región
por columna.
Todo lo que se escriba dentro de with col: (row(), html(), print()
u otra llamada st.*, incluido un st.columns() anidado) va a esa
columna. col.write(x) hace lo mismo sin bloque with.
Gramplets → Nuevo Gramplet… abre el editor de Gramplets preparado para este tipo. En un Gramplet existente, ponga Clase en Gramplet — opens in a window from the Gramplets menu.
En lugar de los ajustes de Gramplet de vista hay un campo Categoría,
que lo agrupa con otros en el menú Gramplets (por ejemplo chart). Si se
deja vacío, va a Other.
El código se ejecuta en cuanto se abre la ventana. Para pedir datos
primero, dibújelos con los widgets st.* y haga el trabajo real tras un
st.button. Siguen los nombres y argumentos de
Streamlit, y funcionan
igual en los Gramplets de vista:
-
st.button,st.text_input,st.number_input,st.checkbox,st.selectbox,st.radio -
st.columns,st.progress - los cuadros de mensaje
st.info/st.success/st.warning/st.error st.session_state
Para un Gramplet de larga duración, muestre una barra con
st.progress(value, text), como en Streamlit. value va de 0 a 100 (o de
0.0 a 1.0). .progress(...) de la barra devuelta la mueve en su sitio y
.empty() la quita, lo bastante barato como para llamarlo una vez por
fila.
people(..., limit=None) (y las demás funciones de lista) devuelven todas
las coincidencias en lugar de las 50 primeras:
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 puede ejecutarse todo el tiempo que necesite, y una página abierta durante horas puede seguir ejecutándolos. Cada petición que hace un Gramplet usa su sesión actual, renovada en segundo plano como en el resto de la aplicación. Solo si la propia sesión ha terminado se detiene una ejecución, con Your session has expired. Sign in again, then rerun this Gramplet.
%pip install unidecode
from unidecode import unidecode%pip install es sintaxis al estilo de Jupyter, que se reescribe como una
llamada real await micropip.install([...]) antes de ejecutar su código.
Debe ir en su propia línea, al principio, como en un notebook; dentro de un
if, por ejemplo, no funciona.
Solo funciona con paquetes puramente de Python, sin código compilado ni extensiones en C. La mayoría de las pequeñas utilidades en Python puro de PyPI se instalan bien así.
Un Gramplet (no un Gramplet de vista, que nunca cambia datos) puede cambiar y eliminar registros una vez activado Permitir cambios en los datos en el editor de Gramplets. La API es la misma que usa Gram.py Script en Gramps de escritorio: cambie un registro como cambiaría cualquier objeto de Python.
for person in people("len(citation_list) == 0", limit=None):
person.private = True-
Mark Unsourced People Private encuentra primero a las personas de
forma barata con
filter(), y luego obtiene y cambia solo las que lo necesitan, dentro de un grupobegin_changes()con nombre. -
Tag People añade a una lista (
person.tag_list.append(tag.handle); los registros guardan las etiquetas como handles, no como nombres) y usa unst.selectbox()en el que hay que elegir antes de que cambie nada.
-
record.field = valuecambia un campo, también los anidados (person.primary_name.first_name = "Chris"). Lo mismo hacen las modificaciones de listas (.append(),.pop(), ...) y cualquier métodoset_*(), por ejemploperson.set_privacy(True). -
delete(record)elimina un registro. No quita las referencias a él de otros registros. -
begin_changes("message")/end_changes()agrupan los cambios que hay entre ellos bajo ese mensaje. Son opcionales: sin ellos toda la ejecución es un grupo, con el nombre del Gramplet.
Se pueden cambiar los registros de people() y las demás funciones en
plural, get_raw_object(), get_object(), get_selected() y db.*. Los
resultados de filter() no, ya que solo contienen algunos campos de un
registro.
Pedir el mismo registro dos veces en una ejecución devuelve el mismo objeto, con sus cambios dentro.
En el editor de Gramplets, cada ejecución es una prueba. Cada registro cambiado se comprueba primero contra su esquema de Gramps, la misma comprobación que hace el servidor; un valor no válido detiene la ejecución e indica el campo.
Una prueba puede diferir de la ejecución real en una cosa: las búsquedas
(filter(), count(), where=) se ejecutan en el servidor, así que
siguen viendo el árbol como estaba antes de los cambios de la ejecución.
En una ejecución real, Ejecutar guarda cada grupo cuando el código lo
cierra, en end_changes() o al terminar la ejecución. Vea
Cambiar datos para lo que ve el usuario,
incluido Deshacer.
Los widgets de la salida (st.button("Apply"), etc.) vuelven a ejecutar
el código en el mismo modo del que procede el resultado actual. Así, un
Gramplet puede mostrar en una prueba lo que cambiaría y aplicarlo tras
Ejecutar.
Una carpeta por Gramplet en
gramplet-store/,
con el nombre de su id. El nombre de la carpeta y el propio campo id
del manifiesto deben coincidir.
gramplet-store/
<slug>/
manifest.json # required
code.py # required -- the Gramplet's Python source
icon.png # optional (png/jpg/jpeg/svg/webp)
| Campo | Obligatorio | Significado |
|---|---|---|
id |
sí | Debe coincidir con el nombre de la carpeta. |
name |
sí | Se muestra como título de la entrada en la tienda. |
description |
sí | Se muestra bajo el nombre en la tienda. |
version |
sí | Semver simple, p. ej. "1.0.0"; increméntela cada vez que cambie code.py o el manifiesto, para que las copias instaladas puedan detectar que hay una actualización disponible. |
author |
sí | Se muestra en la tienda. |
category |
sí | Una etiqueta corta ("example", "detail", "utility", "chart", ...) que se usa para agrupar/filtrar entradas. |
views |
no | A qué vistas de tipo de objeto ("person", "family", ...) se puede añadir este Gramplet. Omítalo para «todos los tipos». |
listensToSelection |
no | Si este Gramplet debe volver a ejecutarse cuando cambie el registro seleccionado. |
listensToFilter |
no | Si este Gramplet debe volver a ejecutarse cuando cambie el filtro activo (cuadro de búsqueda o selector Filtros). |
kind |
no |
"view" (el valor predeterminado) para un Gramplet de vista; "window" para un Gramplet que se ejecuta desde el menú Gramplets. views/listensTo* solo se aplican a los Gramplets de vista. |
allowChanges |
no |
true para instalarlo con Permitir cambios en los datos activado (la tienda muestra una insignia Cambia sus datos). Solo se permite con "kind": "window". |
Después de añadir o editar una entrada, regenere catalog.json, el único
archivo que descarga la aplicación:
npm --prefix app run build:gramplet-catalogEsto comprueba cada entrada (campos obligatorios presentes, id coincide
con su carpeta, version parece semver, code.py no vacío, kind
válido) y falla de forma clara ante el primer problema en lugar de
publicar un catálogo roto.
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