|
| 1 | +# Addon Development |
| 2 | + |
| 3 | +[Index](01-overview.md) · [Next →](02-tutorials.md) |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +A Gramps **addon** extends the application without modifying core. You add a feature, ship it on your own schedule, and users install it from the in-app Plugin Manager — no fork of Gramps, no waiting on a core release to put new functionality in front of people. An addon is just a folder of Python on the plugin path, so the barrier to entry is low; the trade-off is that you build against Gramps' API and track it across versions. This is how most of Gramps' reports, tools, and gramplets are delivered, and the same door is open to you. |
| 8 | + |
| 9 | +Addons are discovered from the plugin directory; see [the addon list](https://gramps-project.org/wiki/index.php/6.0_Addons) for what ships today. |
| 10 | + |
| 11 | +This page is the **start point** for the section: first a map to every other page, then everything a first-time author needs to go from "Gramps is installed" to "my addon shows up in the menu" — anatomy, prerequisites, and a minimal working Gramplet. The normative MUST / SHOULD rules every addon is held to live in [Rules](16-guidelines.md). |
| 12 | + |
| 13 | +## The section at a glance |
| 14 | + |
| 15 | +**New to addon development?** Work through this page, then read in order — from your first loaded addon to a tested, rules-compliant one: |
| 16 | + |
| 17 | +*this page* → [Addon Kinds](03-addon-kinds.md) → [Fundamentals](04-fundamentals.md) → [Data access](05-data-access.md) → [Testing](07-testing.md) → [Rules](16-guidelines.md) |
| 18 | + |
| 19 | +**Looking for something specific?** Jump straight to it: |
| 20 | + |
| 21 | +| If you want to… | Go to | |
| 22 | +|-----------------|-------| |
| 23 | +| Install the tooling and see your first addon load | *this page, below* | |
| 24 | +| Follow an end-to-end walkthrough for your addon kind | [Tutorials](02-tutorials.md) | |
| 25 | +| Choose which kind of addon to build | [Addon Kinds](03-addon-kinds.md) | |
| 26 | +| Learn the cross-cutting basics — `.gpr.py`, discovery, `_()`, logging, lifecycle | [Fundamentals](04-fundamentals.md) | |
| 27 | +| Read from or write to the database | [Data access](05-data-access.md) | |
| 28 | +| Look up the `gramps.gen` API an addon may import | [API Reference](06-api-reference.md) | |
| 29 | +| Write and run tests | [Testing](07-testing.md) | |
| 30 | +| Debug an addon that isn't behaving | [Debug](08-debug.md) | |
| 31 | +| Diagnose a common failure mode | [Troubleshoot](09-troubleshoot.md) | |
| 32 | +| Pass the static checks (Black, ruff) | [Code Analysis](10-code-analysis.md) | |
| 33 | +| Translate your addon's strings | [Internationalization](11-internationalization.md) | |
| 34 | +| Package and submit your addon | [Packaging](12-packaging.md) | |
| 35 | +| List, announce, and support your published addon | [Community](13-community.md) | |
| 36 | +| Port across Gramps versions | [Compatibility](14-compatibility.md) | |
| 37 | +| See per-version changes that affect addons | [What's New](15-whats-new.md) | |
| 38 | +| Know the rules to follow — and to cite in review | [Rules](16-guidelines.md) | |
| 39 | +| See what's planned, or propose a change | [Roadmap](17-roadmap.md) | |
| 40 | + |
| 41 | +The one page to bookmark is [Rules](16-guidelines.md) — the normative MUST / SHOULD / MAY reference every addon is held to. |
| 42 | + |
| 43 | +## What an addon can extend (at a glance) |
| 44 | + |
| 45 | +Almost every part of the Gramps UI is a plugin point. The common kinds: |
| 46 | + |
| 47 | +| Kind | Adds | Shows up in | |
| 48 | +|------|------|-------------| |
| 49 | +| **Gramplet** | a lightweight widget over the current selection | Dashboard / sidebar | |
| 50 | +| **View** | a full alternative way to browse the tree | main view area | |
| 51 | +| **Report** | text or graphical output (PDF, HTML, ODF, …) | Reports menu | |
| 52 | +| **Tool** | an operation over the database | Tools menu | |
| 53 | +| **Importer / Exporter** | reading or writing an external format | File → Import / Export | |
| 54 | +| **Quick View** | a one-call report on a selected object | right-click menus | |
| 55 | + |
| 56 | +…plus filter rules, sidebars, map providers, relationship calculators, citation formatters, docgen output backends, and more. The full catalogue — with the registration fields and base class each kind needs — is [Addon Kinds](03-addon-kinds.md). |
| 57 | + |
| 58 | +## Anatomy of an addon |
| 59 | + |
| 60 | +An addon is a folder under Gramps' user plugin directory — one folder per addon — holding at minimum a registration file and an implementation module: |
| 61 | + |
| 62 | +| File | Purpose | |
| 63 | +|------|---------| |
| 64 | +| `<Addon>.gpr.py` | Registration: id, name, version, Gramps target, kind, entry point | |
| 65 | +| `<Addon>.py` | The implementation Gramps loads on demand | |
| 66 | +| `po/` | Translation catalogs (optional) | |
| 67 | +| `tests/` | Unit tests (optional, recommended) | |
| 68 | + |
| 69 | +At startup Gramps scans every `.gpr.py` and builds a metadata catalog from the `register(...)` call(s); the implementation module named by `fname` loads **lazily**, on first use. The consequence to remember: an error in `.gpr.py` hides the addon entirely, while an error in the implementation only surfaces when the addon is invoked. |
| 70 | + |
| 71 | +The registration declares the Gramps version it targets (`gramps_target_version`) — an addon on `maintenance/gramps60` expects the 6.0 API; see [Compatibility](14-compatibility.md) for cross-version concerns. |
| 72 | + |
| 73 | +What you build next depends on the **kind** — Gramplet, View, Report, Tool, Importer/Exporter, Quick View, and more — each adding its own registration fields and base class. Choose one in [Addon Kinds](03-addon-kinds.md); the full `.gpr.py` field reference and the discovery model are in [Fundamentals](04-fundamentals.md). |
| 74 | + |
| 75 | +## Prerequisites |
| 76 | + |
| 77 | +| Requirement | Why | |
| 78 | +|-------------|-----| |
| 79 | +| Gramps 6.0 installed and runnable | The target you're developing against | |
| 80 | +| Python 3.10+ | Matches Gramps 6.0's minimum | |
| 81 | +| A text editor or IDE | Any will do; Gramps doesn't impose one | |
| 82 | +| Familiarity with Python imports and packages | Addons are Python modules | |
| 83 | + |
| 84 | +You do **not** need to build Gramps from source for addon work. Addons load from the user plugin directory and are picked up at next start. |
| 85 | + |
| 86 | +## Where addons live |
| 87 | + |
| 88 | +Each addon is a folder under Gramps' user plugin directory, one folder per addon. The exact path is platform-specific; see [the Addons page](https://gramps-project.org/wiki/index.php/6.0_Addons) for the canonical locations. The folder name must be a valid Python import name (no spaces — addons share code via `import <FolderName>`); it need **not** match the registration `id`, which is an independent plugin key ([Rules](16-guidelines.md) → Structure). |
| 89 | + |
| 90 | +On Gramps 6.0, plugin discovery does **not** follow symlinks — the addon must be physically present under the plugin path, so the development loop is copying (or `rsync`ing) from your working tree on save. |
| 91 | + |
| 92 | +**Changed in 6.1**: plugin discovery follows symlinks (with realpath-based dedup against symlink loops), so you can `ln -s <working-tree>/<Addon>` into the user plugin directory and edit in place. Windows users: the 6.1 symlink test is skipped on Windows because the platform's symlink behavior is inconsistent without elevated privileges; the `rsync`/copy loop remains the safe default there. (gramps commit `9443dcbb30` on `maintenance/gramps61`.) |
| 93 | + |
| 94 | +## Your first addon: a minimal Gramplet |
| 95 | + |
| 96 | +A *Gramplet* is the lightest-weight addon kind — a sidebar widget. Two files are enough. |
| 97 | + |
| 98 | +### 1. Create the addon folder |
| 99 | + |
| 100 | +Make a folder named `HelloGramplet` under the user plugin directory. |
| 101 | + |
| 102 | +### 2. Add the registration file |
| 103 | + |
| 104 | +Save this as `HelloGramplet/HelloGramplet.gpr.py`: |
| 105 | + |
| 106 | +```python |
| 107 | +register( |
| 108 | + GRAMPLET, |
| 109 | + id="HelloGramplet", |
| 110 | + name=_("Hello Gramplet"), |
| 111 | + description=_("A minimal example Gramplet"), |
| 112 | + version="1.0.0", |
| 113 | + gramps_target_version="6.0", |
| 114 | + status=STABLE, |
| 115 | + fname="hellogramplet.py", |
| 116 | + gramplet="HelloGramplet", |
| 117 | + gramplet_title=_("Hello"), |
| 118 | +) |
| 119 | +``` |
| 120 | + |
| 121 | +The `id` is the addon's stable identifier. `fname` is the implementation module. `gramplet` is the class inside it that Gramps will instantiate. |
| 122 | + |
| 123 | +### 3. Add the implementation |
| 124 | + |
| 125 | +Save this as `HelloGramplet/hellogramplet.py`: |
| 126 | + |
| 127 | +```python |
| 128 | +from gramps.gen.const import GRAMPS_LOCALE as glocale |
| 129 | +from gramps.gen.plug import Gramplet |
| 130 | + |
| 131 | +_ = glocale.get_addon_translator(__file__).gettext |
| 132 | + |
| 133 | + |
| 134 | +class HelloGramplet(Gramplet): |
| 135 | + def init(self): |
| 136 | + self.set_text(_("Hello from your first Gramplet!")) |
| 137 | +``` |
| 138 | + |
| 139 | +`init()` is the construction hook — Gramps calls it once when the Gramplet is first shown. The `_ = glocale...` line binds the translation function for this module — see [Translation](#translation) below. |
| 140 | + |
| 141 | +### 4. Restart Gramps |
| 142 | + |
| 143 | +Plugin discovery happens at startup. After the restart, the new Gramplet appears under *View → Sidebar* (or the Dashboard, depending on view). |
| 144 | + |
| 145 | +## Reload / test cycle |
| 146 | + |
| 147 | +There is no hot-reload for addons. The development loop is: |
| 148 | + |
| 149 | +1. Edit the source. |
| 150 | +2. Sync the change into the plugin directory (or work directly there). |
| 151 | +3. Restart Gramps. |
| 152 | +4. Observe. |
| 153 | + |
| 154 | +For faster iteration on non-GUI logic, write a `unittest`-based test alongside the addon and run it without launching Gramps — see [Testing](07-testing.md) for the conventions. |
| 155 | + |
| 156 | +## Translation |
| 157 | + |
| 158 | +Wrap every user-visible string in `_()` so it can be translated: |
| 159 | + |
| 160 | +```python |
| 161 | +self.set_text(_("Hello from your first Gramplet!")) |
| 162 | +``` |
| 163 | + |
| 164 | +`_` is set up differently in the two files. In `.gpr.py` it is injected by the plugin loader — just use it, never import it. In the implementation module nothing is injected: bind it explicitly at the top of the file, as the walkthrough's `hellogramplet.py` does: |
| 165 | + |
| 166 | +```python |
| 167 | +from gramps.gen.const import GRAMPS_LOCALE as glocale |
| 168 | + |
| 169 | +_ = glocale.get_addon_translator(__file__).gettext |
| 170 | +``` |
| 171 | + |
| 172 | +Translation catalogues live in a per-addon `po/` directory — optional for a first experiment, required for an addon you intend to share; [Internationalization](11-internationalization.md) covers the workflow. |
| 173 | + |
| 174 | +## Next steps |
| 175 | + |
| 176 | +- [Tutorials](02-tutorials.md) — end-to-end walkthroughs per addon kind; read a similar addon's source as your second tutorial ([6.0 Addons](https://gramps-project.org/wiki/index.php/6.0_Addons) lists what exists). |
| 177 | +- [Addon Kinds](03-addon-kinds.md) — choose the kind of addon to build; registration fields and base class per kind. |
| 178 | +- [Fundamentals](04-fundamentals.md) — every `.gpr.py` field, the discovery model, and the lifecycle hooks the implementation overrides. |
| 179 | +- [Testing](07-testing.md) — unit-test conventions and the `tests/` package layout. |
| 180 | +- [Addons development](https://gramps-project.org/wiki/index.php/Addons_development) — cross-version porting notes and the wider development reference. |
0 commit comments