Repository navigation
Gramplets.es
🌐 English · Deutsch · Français · 简体中文
Los Gramplets son el sistema de complementos de Gramps Connect: pequeños
programas de Python, asociados a una vista (Persona, Familia o cualquier
otro tipo de objeto, o «Todas»), que consultan el árbol y muestran un
resultado: una tabla, un diagrama, texto simple o HTML. Lo que los hace
poco habituales es dónde se ejecutan: cada uno es Python real, que se
ejecuta directamente en la pestaña del navegador con
Pyodide (CPython compilado a WebAssembly), sobre
wheels compiladas localmente del propio modelo de datos gramps.gen.lib
de Gramps. No se instala nada en su equipo y nada se ejecuta en un
servidor; véase
Arquitectura.
Eso también significa que todos los Gramplets son editables. Abra uno desde el editor de Gramplets de la aplicación y estará viendo (y podrá cambiar) su código fuente real, no una caja negra detrás de un panel de configuración. Cambie el aspecto de un diagrama, ajuste lo que busca una consulta o escriba uno nuevo desde cero, todo sin salir de la aplicación.
La tienda de Gramplets es un catálogo navegable dentro de la aplicación.
Instale, actualice o quite cualquier entrada con un clic. Se publica de
forma independiente de las versiones de Gramps Connect: el catálogo es
contenido estático
(gramplet-store/
en el repositorio) que la aplicación obtiene en tiempo de ejecución, no
algo incluido en su compilación, así que pueden aparecer Gramplets nuevos
en la tienda sin que nadie tenga que actualizar el propio Gramps Connect.
Entre los ejemplos que ya hay en el catálogo están:
| Gramplet | Qué hace |
|---|---|
| Age-at-Death Histogram | Histograma interactivo de plotly de la edad al fallecer en todo el árbol (o en la lista filtrada actual) |
| All Relationships | Cálculos de relaciones en todo el árbol |
| Attributes / Backlinks / Children / Citations / Coordinates / Events / Gallery / Notes | Paneles de detalles al estilo de los Gramplets de escritorio para el registro seleccionado |
| Born In | Búsqueda por lugar de nacimiento |
| Crossing Relationships | Encuentra relaciones que cruzan generaciones de formas poco habituales |
| GOQL Query | Escriba una cláusula where de GOQL en bruto y elija expresiones de campo como columnas |
| Hello Table | El ejemplo inicial mínimo |
| Interactive Search | Un Gramplet de búsqueda en vivo, mientras escribe |
| People Explorer | Un Gramplet más completo para explorar personas |
| Phonebook Surname Sort | Ordenación de apellidos que ignora los acentos (p. ej. «Müller» se ordena junto a «Muller») |
| Relationship / Relationships | Cómo están emparentadas dos personas seleccionadas |
| Selected Record | Reacciona al registro que esté abierto en ese momento |
| To-Do / Todo Tracker | Lleva el control de las tareas de investigación pendientes en todo el árbol |
| Tree Statistics | Un tablero con recuentos de registros y porcentajes derivados |
En cada vista de lista (Personas, Familias, Eventos, …), los Gramplets que ha añadido a esa vista aparecen como pestañas en un panel plegable Gramplets bajo la tabla. El panel empieza plegado, y un Gramplet solo se ejecuta mientras el panel está abierto: al expandirlo se ejecuta la pestaña seleccionada. La primera ejecución de una sesión también carga el entorno de Python del navegador, así que puede tardar unos segundos; las siguientes son rápidas. Mantener el panel plegado no cuesta nada, ya que no se carga Python hasta que lo abre.
El código de un Gramplet se ejecuta en un entorno aislado con un conjunto
de funciones y objetos ya disponibles: no hace falta importar nada para
llegar al árbol. El directorio
gramplet_examples/
del repositorio es un recorrido numerado por la API, del más sencillo al
más avanzado; la misma referencia está disponible dentro de la propia
aplicación mediante 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, order=None, limit=50)obtiene las Personas que coinciden con una consulta, como registros completos, en una sola llamada; y hay unfamilies()/events()/places()/repositories()/sources()/citations()/media()/notes()/tags()para cada uno de los demás tipos de objeto, con la misma firma, solo que con otra tabla. -
where=se escribe en GOQL: exactamente la misma sintaxis que el cuadro de búsqueda de cualquier vista de lista, incluida la posibilidad de recorrer relaciones (birth.date.sortval) aunque el campo por el que filtra no sea uno de los que muestra. - No hace falta
awaitaunque por debajo sea una llamada de red real: se inserta automáticamente. -
row(*values)añade una fila a la tabla. Pasar un objeto Persona/Evento/Lugar/... completo (en lugar de un campo elegido a mano) lo muestra como un enlace en el que se puede hacer clic, que ya muestra su nombre/título completo y su ID de Gramps; al hacer clic se abre una ventana emergente para ver ese registro en Lista, Mapa, Gráfico o Cronograma.
Cada registro que devuelven people()/families()/etc. es un
«DataDict» de Gramps: un dict normal con los campos del objeto, pero con
acceso por punto encima: person.primary_name.first_name es el mismo
valor que person["primary_name"]["first_name"]. Recurra a esto siempre
que quiera calcular algo a partir de un campo o mostrar uno que la
propia representación de row() no muestra. row(), html() y
print() se pueden llamar tantas veces como quiera, en cualquier orden:
todo aparece en el orden en que lo añadió.
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 y no se descarga realmente
ningún registro. db.get_number_of(object_type, where=...) es el
equivalente condicional, para cuando el propio recuento necesita un
filtro.
Una condición where= puede recorrer una relación para filtrar por
ella (el evento de nacimiento de una persona, el padre de una familia, el
lugar de un evento), aunque people()/families()/etc. siempre devuelven
un objeto completo y aislado, sin forma de pedir directamente un campo
relacionado:
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,
)Obtener un campo relacionado para mostrarlo (en lugar de solo filtrar
por él) es una búsqueda aparte, a mano, mediante los propios métodos de
db (db.get_event_from_handle(...), etc.), siguiendo la misma
indirección por handles que usa internamente el modelo de datos de Gramps.
Las colecciones también funcionan: any(c.given_name == 'Steve' for c in children) coincide 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. Consulte GOQL para ver
todo lo relativo a las colecciones, incluido su límite actual (un salto
dentro de una colección, sin seguir encadenando más allá de lo que
contiene).
record = get_selected()
if record is not None:
if isinstance(record, Person):
...get_selected() devuelve el registro que esté abierto en ese momento en
el panel de detalles de la vista: None cuando no hay nada seleccionado,
o cuando se ejecuta desde la vista previa del editor independiente (que no
tiene ningún contexto de vista). Hace una petición de red en la primera
llamada de una ejecución y la memoriza para el resto. isinstance(record, Person) (etc.) le indica qué tipo de registro ha obtenido, lo cual
importa en un Gramplet asociado a «Todas» las vistas. Para que se
actualice en vivo mientras hace clic por la lista, active «Re-run
automatically when the selected record changes» en el propio editor del
Gramplet; si lo deja desactivado (por defecto), get_selected() sigue
funcionando, pero solo refleja lo que estuviera seleccionado la última vez
que el Gramplet se ejecutó por algún otro motivo.
Un Gramplet puede añadir su propia consulta sobre el filtro que esté aplicado en ese momento a la vista (escrito en el propio cuadro de búsqueda de la vista o aplicado mediante el selector «Filtros»: un filtro guardado o una regla personalizada), de modo que filtrar la lista hasta una rama del árbol restringe 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 views: [...] en el manifiesto (get_filter() solo
devuelve un where_expr que corresponda al tipo de objeto de la propia
vista) y listensToFilter: true para que el Gramplet realmente se vuelva
a mostrar cuando cambie el filtro.
pygal, matplotlib y plotly vienen incluidos: un simple import (o, para
plotly, from plotly.subplots import make_subplots) funciona sin
conexión, sin necesidad de %pip install. print(fig) sobre una Figure
de plotly, una cadena SVG de pygal o una figura de matplotlib se reconoce
automáticamente y se muestra como diagrama, sin necesidad de
to_html()/incrustación manual.
Para los datos de un diagrama, es preferible
filter(object_type, what=[...], where=..., limit=...) a
people()/families()/etc. cuando solo necesita uno o dos campos:
people() implica una petición de red del objeto completo por registro,
lo cual es un desperdicio para un diagrama que solo representa, por
ejemplo, la edad al fallecer.
col1, col2 = st.columns([2, 1])
with col1:
row("Wider column")
with col2:
row("Narrower column")st.columns(spec) dispone spec regiones lado a lado (un entero para
ese número de columnas de igual ancho, o una lista de pesos
(st.columns([2, 1])) para anchos proporcionales) y 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 en lugar de al nivel superior;
col.write(x) funciona igual sin un bloque with.
%pip install unidecode
from unidecode import unidecode%pip install es sintaxis «mágica» de Jupyter, que se reescribe como una
llamada real await micropip.install([...]) antes de que se ejecute su
código: debe ir en su propia línea, al principio, exactamente como en un
cuaderno (escribirlo dentro de un if, por ejemplo, no funcionará). Solo
funciona con paquetes puramente de Python, sin código compilado ni
extensiones en C; pygal/matplotlib/plotly no lo necesitan porque vienen
incluidos, pero la mayoría de las pequeñas utilidades puramente de Python
de PyPI se instalan bien así.
Una carpeta por Gramplet en
gramplet-store/,
con el nombre de su id (el nombre de la carpeta y el campo id del
manifiesto deben coincidir):
gramplet-store/
<slug>/
manifest.json # obligatorio
code.py # obligatorio -- el código fuente Python del Gramplet
icon.png # opcional (png/jpg/jpeg/svg/webp)
Campos de manifest.json:
| 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). |
Después de añadir o editar una entrada, regenere catalog.json, el único
archivo que la aplicación obtiene realmente:
npm --prefix app run build:gramplet-catalogEsto valida cada entrada (campos obligatorios presentes, id igual a su
carpeta, version con aspecto de semver, code.py no vacío) y falla de
forma ruidosa en el primer problema en lugar de publicar un catálogo roto.
Hoy los Gramplets son de solo lectura sobre el árbol: pueden consultar
(filter()/get_object()) pero no editar ni crear objetos. Tampoco hay
todavía un equivalente para otros tipos de complementos de Gramps
(herramientas, informes). Consulte
Hoja de ruta y limitaciones conocidas para ver en qué punto
están.
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